NeoCharts
Quick Start

Web Integration

Embed NeoCharts into a web app via a pre-built, compiled bundle.

Embed NeoCharts into an existing web app — React, Vue, or plain HTML/JS, any stack that can load a <script> tag.

The SDK is distributed as a pre-built, compiled bundle — your page only implements the window.NxtChartHost JS object that supplies data and receives trading callbacks, and dispatches CustomEvents to push live updates.

This is the complete integration — the full method set, alerts, and OI analysis, all inside the Terminal.

How the bundle loads into your page

The bundle is a compiled Flutter web app — flutter.js bootstraps a Flutter engine into the host <div> you provide, the same mechanism any Flutter web app uses to embed into a page. If step 4 below is unfamiliar, Flutter's own Embedding a Flutter app into a web page explains the general mechanism this bundle relies on — loadEntrypoint, the host element, and the engine lifecycle.


1. Receive the bundle

Access

NeoCharts is distributed directly to licensed partners, not published to a public CDN. Your IOURING team sends you the compiled web bundle as a .tar.xz archive — unpack it (tar -xJf neochart-web-<version>.tar.xz) into a directory containing main.dart.js, flutter.js, assets/, nxt-chart-host.d.ts, and (self-contained variant only) canvaskit/. No toolchain of any kind needed on your end; it's already built.

TypeScript

nxt-chart-host.d.ts types window.NxtChartHost and window._flutter for you — add it to your tsconfig.json's include, or reference it directly (/// <reference path="./build_web/nxt-chart-host.d.ts" />). Without it, TypeScript reports Property 'NxtChartHost' does not exist on type 'Window & typeof globalThis' the first time you assign to window.NxtChartHost.

If your ESLint config enforces no-underscore-dangle, access the global via bracket notation — window['_flutter'] — instead of window._flutter. It's still fully typed against the .d.ts above; only the access syntax changes.

Every release ships as two archives — ask your IOURING contact for the one that matches your deployment:

ArchiveContains canvaskit/?Typical sizeUse when
neochart-web-<version>.tar.xzYes, bundled locally~3.6 MBDefault choice. Works regardless of your network policy — nothing to configure or allowlist.
neochart-web-<version>-cdn.tar.xzNo~1 MBOnly if your deployment's browsers have confirmed, unrestricted outbound access to gstatic.com.

The -cdn variant depends on Google's CDN at runtime

canvaskit/ is the compiled Skia rendering engine the chart draws with — it isn't optional code, the chart cannot render without it. The -cdn archive omits the local copy and instead has the compiled app fetch it from https://www.gstatic.com/flutter-canvaskit/ the first time it loads in a browser.

If that request is blocked — a corporate firewall, an intranet/air-gapped deployment, or any network policy that doesn't allowlist Google's CDN — the chart fails silently: no error dialog, just a blank screen or a stuck loading spinner. Use the plain (self-contained) archive unless you've explicitly verified gstatic.com is reachable from where the chart will run.

Add your license key file

Put the neocharts-key.json IOURING sends you at <bundle>/assets/neocharts-key.json, and keep <bundle>/assets/neocharts_core.wasm from the bundle — it checks the license. Replace the key file whenever IOURING sends a new one.

Every domain the page runs on must be in your license. *.acme.com covers subdomains only, not acme.com itself; localhost and 127.0.0.1 always work. If your page sets a Content-Security-Policy, script-src must allow 'wasm-unsafe-eval'. See Licensing for details.


2. Serve the bundle

Place the bundle directory somewhere your own web server serves as static files, at a stable URL your page can load scripts from — e.g. copy it into your app's public//static/ assets directory as part of your build pipeline, or point your server/CDN config at it directly. The examples below assume it's reachable at /build_web/; use whatever path your setup actually serves it at.

Don't rely on a relative filesystem path (../build_web) to reach it — most dev servers and CDNs resolve script src against your page's own served root, not your project's folder layout, so a relative path that works when you open the file directly can 404 once it's actually served.

Self-contained bundle: canvaskit/ must also be served at your site root

This only applies to the plain (self-contained) archive, not the -cdn one.

The compiled app looks for its local CanvasKit copy at a path relative to your page's own URL — not relative to wherever you put the rest of the bundle. Concretely: if your page is at https://yourapp.com/dashboard, the engine requests https://yourapp.com/canvaskit/..., regardless of whether the rest of the bundle lives at /build_web/.

So alongside serving the bundle at whatever path you chose, also make canvaskit/ (the subfolder inside the bundle) reachable at your site's root canvaskit/ path — copy it there as an extra step in your build pipeline, or add a server/CDN rule that serves it from root. Skipping this produces a blank chart with 404s for canvaskit.js/canvaskit.wasm in your browser console — the chart itself gives no error dialog.


3. Implement window.NxtChartHost

Set this up before loading the bundle (step 4) — the engine reads it as soon as it boots.

yourApi, yourBroker, and yourTickFeed below (e.g. yourApi.fetchOHLCV(...)) are placeholder names standing in for your own broker/market-data code — not real functions this SDK provides. Replace each with whatever already fetches that data in your app.

window.NxtChartHost = {
  // ── Session-constant getters — read once at startup ──────────────────
  get symbolInfo() {
    return JSON.stringify({
      id: 'RELIANCE',
      name: 'Reliance Industries',
      lotSize: 1,
      precision: 2,
      tickSize: 0.05,
      exchange: 'NSE',
      expiry: '', // e.g. '2025-03-27' for a derivative; empty for this equity
      strike: '', // e.g. '23000' for an option; empty for this equity
      optType: '', // e.g. 'CE' or 'PE' for an option; empty for this equity
      weekly: '', // e.g. 'Y' marks a weekly expiry; empty for this equity
      undID: '', // e.g. 'NIFTY' for a derivative; empty for this equity
    });
  },
  get underlyingSymbolInfo() {
    return ''; // empty for equities
  },
  get optionSymbols() {
    return '[]';
  },
  get futureSymbols() {
    return '[]';
  },
  get indexSymbols() {
    return '[]';
  },
  get marketTimings() {
    // Keyed by exchange code -- add one entry per exchange any symbol
    // reachable this session might resolve to (e.g. also 'NFO' once
    // options are in play; see the marketTimings reference doc).
    return JSON.stringify({
      NSE: {
        timezone: 'Asia/Kolkata',
        sessions: [
          ['0915-1530'], // Monday
          ['0915-1530'], // Tuesday
          ['0915-1530'], // Wednesday
          ['0915-1530'], // Thursday
          ['0915-1530'], // Friday
          [], // Saturday
          [], // Sunday
        ],
        holidays: [],
        special: {},
      },
    });
  },
  get hasOCO() {
    return true;
  },
  get isMarketOrderSupported() {
    return true;
  },
  get storageKey() {
    return `user_${currentUser.id}`; // stable, user-scoped key
  },

  // Optional theming getters: see /docs/charting/theming

  // ── Request/response methods ──────────────────────────────────────────
  loadData(symbolId, from, to, intervalSeconds, requiredBars) {
    return yourApi
      .fetchOHLCV({ symbolId, from, to, interval: intervalSeconds })
      .then(JSON.stringify);
  },
  fetchOptionDetails(underlyingSymbolId) {
    return yourApi.fetchOptionChain(underlyingSymbolId);
  },
  chartTopOptions() {
    return yourApi.fetchTopOptions();
  },
  atmSymbols() {
    return Promise.resolve(
      JSON.stringify(['NIFTY24DEC24000CE', 'NIFTY24DEC24000PE']),
    );
  },
  searchSymbols(query) {
    return yourApi.searchSymbols(query);
  },
  fetchOIAnalysis(underlyingSymbolId, expiry, timeFrom, timeTo) {
    return yourApi.fetchOIAnalysis(underlyingSymbolId, expiry, timeFrom, timeTo);
  },
  fetchOIChange(underlyingSymbolId, expiries, timeFrom, timeTo) {
    return yourApi.fetchOIChange(underlyingSymbolId, expiries, timeFrom, timeTo);
  },
  fetchOI(underlyingSymbolId, expiries) {
    return yourApi.fetchOI(underlyingSymbolId, expiries);
  },
  fetchPcrIntraday() {
    return yourApi.fetchPcrIntraday();
  },
  fetchAtmStraddleIntraday() {
    return yourApi.fetchAtmStraddleIntraday();
  },
  fetchAtmIvIntraday() {
    return yourApi.fetchAtmIvIntraday();
  },
  fundsData() {
    return yourApi.fetchFunds();
  },

  // ── Parameterized replacements (xxxFor) ─────────────────────────────────
  // Scoped to an explicit id instead of "whatever's currently charted."
  // `underlyingSymbolInfoFor` is required — the SDK calls it on every chart
  // load. The other 7 aren't called yet (see the reference table below),
  // but still need a real body: `implements ChartInterface` on every
  // platform won't compile without one.
  underlyingSymbolInfoFor(symbolId) {
    return yourApi.fetchUnderlyingSymbolInfo(symbolId); // nullable
  },
  optionSymbolsFor(underlyingId) {
    return yourApi.fetchOptionSymbols(underlyingId);
  },
  futureSymbolsFor(underlyingId) {
    return yourApi.fetchFutureSymbols(underlyingId); // nullable
  },
  atmSymbolsFor(underlyingId) {
    return yourApi.fetchAtmSymbols(underlyingId); // nullable
  },
  chartTopOptionsFor(underlyingId) {
    return yourApi.fetchTopOptions(underlyingId);
  },
  fetchPcrIntradayFor(underlyingId) {
    return yourApi.fetchPcrIntraday(underlyingId);
  },
  fetchAtmStraddleIntradayFor(underlyingId) {
    return yourApi.fetchAtmStraddleIntraday(underlyingId);
  },
  fetchAtmIvIntradayFor(underlyingId) {
    return yourApi.fetchAtmIvIntraday(underlyingId);
  },

  // ── Trading actions & lifecycle — fire-and-forget ──────────────────────
  placeOrder(params) {
    yourBroker.placeOrder(JSON.parse(params));
  },
  modifyOrder(params) {
    yourBroker.modifyOrder(JSON.parse(params));
  },
  cancelOrder(orderID) {
    yourBroker.cancelOrder(orderID);
  },
  placeOCOOrder(params) {
    yourBroker.placeOCOOrder(JSON.parse(params));
  },
  modifyOCOOrder(params) {
    yourBroker.modifyOCOOrder(JSON.parse(params));
  },
  cancelOCOOrder(groupId) {
    yourBroker.cancelOCOOrder(groupId);
  },
  groupAdjustOrders(params) {
    yourBroker.groupAdjustOrders(JSON.parse(params));
  },
  createAlert(params) {
    yourBroker.createAlert(JSON.parse(params));
  },
  modifyAlert(params) {
    yourBroker.modifyAlert(JSON.parse(params));
  },
  deleteAlert(alertId) {
    yourBroker.deleteAlert(alertId);
  },
  closeRequested() {
    // Called when the chart wants to be dismissed (e.g. an in-chart close
    // action) — decide what that means for your page (unmount, navigate
    // away, etc.)
    yourApp.closeChart();
  },

  // ── Market data subscription control ────────────────────────────────────
  subscribeMarketData(symbolsJson) {
    yourTickFeed.subscribe(JSON.parse(symbolsJson));
  },
  unsubscribeMarketData() {
    yourTickFeed.unsubscribeAll();
  },
};

The tables below are the full member reference — types, exact shapes, and links to the wire-format pages.

Session-constant getters (read once at startup)

null is accepted anywhere you see string | null below, but always return the empty form shown in the example above instead — see Always return empty, never null for why.

GetterTypeShape
symbolInfostringJSON — see symbolInfo
underlyingSymbolInfostring | nullsame shape as symbolInfo
optionSymbolsstringJSON array of symbolInfo-shaped objects
futureSymbolsstring | nullJSON array
indexSymbolsstring | nullJSON array — populating this hides the Volume indicator for symbols in the list (spot indices have no traded volume)
marketTimingsstringJSON — see marketTimings
hasOCOboolean
isMarketOrderSupportedboolean
storageKeystringstable, user-scoped — see Chart Persistence for what's stored against it and how

Optional theming getters

chartColors, primaryColor, onPrimaryColor and forcedBrightness — see Theming.

Request/response methods

MethodReturns
loadData(symbolId, from, to, intervalSeconds, requiredBars)Promise<string> (JSON bars, oldest-first)
fetchOptionDetails(underlyingSymbolId)Promise<string> (JSON array)
chartTopOptions()Promise<string> (JSON array)
atmSymbols()Promise<string | null>
searchSymbols(query)Promise<string> (JSON array)
fetchOIAnalysis(underlyingSymbolId, expiry, timeFrom, timeTo)Promise<string | null>
fetchOIChange(underlyingSymbolId, expiries, timeFrom, timeTo)Promise<string | null>
fetchOI(underlyingSymbolId, expiries)Promise<string | null>
fetchPcrIntraday()Promise<string> (JSON array)
fetchAtmStraddleIntraday()Promise<string> (JSON)
fetchAtmIvIntraday()Promise<string> (JSON array)
fundsData()Promise<string> (JSON — see fundsData)

Parameterized replacements (xxxFor)

Scoped to an explicit id instead of the ambient members above. underlyingSymbolInfoFor is called on every chart load — implement it before shipping. The other 7 aren't called by the SDK yet, but still need a real body to compile; see each method's own page for the "not yet called" detail.

MethodReturnsDocs
underlyingSymbolInfoFor(symbolId)Promise<string | null>underlyingSymbolInfoFor
optionSymbolsFor(underlyingId)Promise<string> (JSON array)optionSymbolsFor
futureSymbolsFor(underlyingId)Promise<string | null>futureSymbolsFor
atmSymbolsFor(underlyingId)Promise<string | null>atmSymbolsFor
chartTopOptionsFor(underlyingId)Promise<string> (JSON array)chartTopOptionsFor
fetchPcrIntradayFor(underlyingId)Promise<string> (JSON array)fetchPcrIntradayFor
fetchAtmStraddleIntradayFor(underlyingId)Promise<string> (JSON)same as above
fetchAtmIvIntradayFor(underlyingId)Promise<string> (JSON array)same as above

Trading actions & lifecycle (fire-and-forget)

MethodParams
placeOrder(params)JSON string — see placeOrder params
modifyOrder(params)JSON string — same shape as placeOrder
cancelOrder(orderID)string
placeOCOOrder(params)JSON string — see placeOCOOrder params
modifyOCOOrder(params)JSON string — same shape as placeOCOOrder
cancelOCOOrder(groupId)string
groupAdjustOrders(params)JSON string — see groupAdjustOrders params
createAlert(params)JSON string — see createAlert params
modifyAlert(params)JSON string — same shape as createAlert
deleteAlert(alertId)string
closeRequested()— called when the chart wants to be dismissed (e.g. an in-chart close action); decide what that means for your page (unmount, navigate away, etc.)

Market data subscription control

subscribeMarketData(symbolsJson) / unsubscribeMarketData() — called when the chart starts or stops needing live ticks for the given symbol(s). subscribeMarketData fires with the complete current set every time it changes (tile switches, new positions, indicator settings, etc.), not just once at session start — implement it as a replace, never an accumulate, or you'll leak stale symbols. unsubscribeMarketData means "no longer need any live data at all" (session teardown), not "the set changed."


4. Load the bundle

<div id="nxtchart-mount" style="width: 100%; height: 100%;"></div>
<script src="/build_web/flutter.js" defer></script>
<script>
  window.addEventListener('load', function () {
    _flutter.loader.loadEntrypoint({
      entrypointUrl: '/build_web/main.dart.js',
      onEntrypointLoaded: async function (engineInitializer) {
        const appRunner = await engineInitializer.initializeEngine({
          hostElement: document.querySelector('#nxtchart-mount'),
          assetBase: '/build_web/',
        });
        await appRunner.runApp();
      },
    });
  });
</script>

In a component-based framework (React shown), you generally can't drop a raw <script> tag inside a component and expect it to behave the same way. Add flutter.js once to your app's HTML shell instead — public/index.html for Create React App, app/layout.tsx or pages/_document.tsx for Next.js — then boot the engine once and give each mount of your component its own chart view, using Flutter's multi-view embedding:

import { useEffect, useRef } from 'react';

// The engine can only be booted once per page, so every mount shares this promise.
let chartApp = null;

function bootChartEngine() {
  chartApp ??= new Promise((resolve) => {
    // Bracket notation sidesteps `no-underscore-dangle` -- `_flutter` is
    // Flutter's own bootstrap global (from flutter.js) and can't be renamed.
    window['_flutter'].loader.loadEntrypoint({
      entrypointUrl: '/build_web/main.dart.js',
      onEntrypointLoaded: async (engineInitializer) => {
        const appRunner = await engineInitializer.initializeEngine({
          assetBase: '/build_web/',
          multiViewEnabled: true,
        });
        resolve(await appRunner.runApp());
      },
    });
  });

  return chartApp;
}

function NeoChart() {
  const hostRef = useRef(null);

  useEffect(() => {
    // Your step 3 implementation, built for the current user's session.
    window.NxtChartHost = createNxtChartHost();

    let removeView = null;
    let unmounted = false;

    bootChartEngine().then((app) => {
      if (unmounted || !hostRef.current) return;
      const viewId = app.addView({ hostElement: hostRef.current });
      removeView = () => app.removeView(viewId);
    });

    return () => {
      unmounted = true;
      removeView?.();
    };
  }, []);

  return <div ref={hostRef} style={{ width: '100%', height: '100%' }} />;
}

One view per session

Each addView builds a fresh chart that reads window.NxtChartHost as the view is created; removeView disposes that chart along with its subscriptions and state. Mount NeoChart when a user's session starts and unmount it when the session ends (logout, account switch) — the next session then gets its own chart instead of the previous session's, without a page reload. The engine itself stays loaded, so a new view doesn't download anything again.

Keep it mounted while navigating within a session

Unmounting ends that chart — drawings, zoom and open panels are rebuilt from scratch on the next mount. If your router would otherwise unmount NeoChart for an in-session navigation — e.g. from /chart to /dashboard — hide it with CSS instead of removing it from the tree. Keep it at full size while hidden (e.g. position: fixed; inset: 0; opacity: 0; pointer-events: none; z-index: -1); avoid display: none, which collapses the chart's canvas to 0×0 and can leave it blank until the next repaint once it's shown again.

React 18 Strict Mode double-invokes effects

In development, Strict Mode runs the effect, its cleanup, then the effect again. The shared chartApp promise keeps the engine from booting twice, and the cleanup removes any view the first run added, so you end up with exactly one chart.

Define window.NxtChartHost first

Set window.NxtChartHost (step 3) before calling addView (or, with the single-page snippet above, before loadEntrypoint) — the chart reads its session-constant getters synchronously as the view is created. If any of these getters depend on data you fetch asynchronously (e.g. symbolInfo built from an API call), wait for that fetch before adding the view — don't assign window.NxtChartHost with getters that aren't ready yet.


5. Push live updates

Dispatch a CustomEvent on window whenever new data is available. detail is the JSON string for the matching schema on the corresponding Trading page.

Event nameWire format
nxtchart:positionspositionsStreamer
nxtchart:ordersordersStreamer
nxtchart:ocoOrdersocoOrdersStreamer
nxtchart:actionFeedbackactionFeedbackStreamer
nxtchart:alertsalertsStreamer
nxtchart:marketDatamarket data ticks — see Live ticks
window.dispatchEvent(new CustomEvent('nxtchart:orders', { detail: JSON.stringify(orders) }));

Troubleshooting

SymptomFix
Blank page, _flutter is not definedflutter.js's <script> tag didn't load before loadEntrypoint ran — check the build output path
Blank chart — init never completesEnsure every session-constant getter on window.NxtChartHost returns a value
Blank chart — no candlesVerify loadData resolves valid OHLCV JSON ordered oldest-first
Orders/positions not shownDispatch nxtchart:orders/nxtchart:positions before or shortly after loadData resolves
Alerts not shownDispatch nxtchart:alerts whenever your alert list changes
404s for /build_web/*The bundle isn't actually being served at that URL — check where you copied/pointed it in step 2
No data or live updates after logging out and back in without a page reloadThe previous session's chart is still mounted — give each session its own view (addView on login, removeView on logout), see step 4
Uncaught error thrown from a getter (e.g. symbolInfo)A getter depends on async data that hasn't loaded yet — delay loadEntrypoint until your own init finishes
"Your NeoCharts license is invalid or has expired." instead of the chartneocharts-key.json is missing from <bundle>/assets/, was edited (it is signed), the page's domain is not in your license, assets/neocharts_core.wasm wasn't deployed, or your CSP blocks 'wasm-unsafe-eval' — see Licensing
Blank chart, 404s for canvaskit.js/canvaskit.wasm at your site root (self-contained archive only)canvaskit/ needs to be served at your site root in addition to wherever the rest of the bundle lives — see the callout in step 2

Terminal

Web only

The terminal is only available through the compiled web bundle.

The Terminal is a multi-chart trading layout — the web SDK's landing screen. Every panel below is built from the same screens/blocs the single-chart and scalper views already use — the terminal only changes how they're arranged on screen.


Tile grid

2–8 independently-symboled, resizable chart tiles. Each tile owns its own chart state (bloc, drawing controller, symbol) — changing one tile's symbol, indicators, or drawings never affects the others.

ActionHow
Add a tileThe grid's "+" control (up to 8)
Remove a tileEach tile's own close button (down to 2)
Activate a tileTap/click anywhere on it — the left rail and its drawing tools then apply to that tile
ResizeDrag the divider between tiles

Left rail — drawing tools

A persistent icon rail, grouped into flyouts rather than one long flat list. Applies to whichever tile is currently active.

IconOpensTools
CursorFlyoutCross (deactivates the current drawing tool)
Lines & ChannelsFlyoutTrend Line, Extended Line, Ray, Arrow, Horizontal Line, Horizontal Ray, Vertical Line, Cross Line, Parallel Channel
FibonacciFlyoutFibonacci Retracement
PositionFlyoutLong Position, Short Position
ShapesFlyoutRectangle, Circle, Path
Text & NotesFlyoutText, Anchored Text
MeasureDirect — activates immediatelyMeasure

See Indicators & Drawing Tools for what each tool does.


Right dock

A toggle strip beside the tile grid — tapping the active icon again closes the dock.

PanelContent
Option ChainFull option chain for the active tile's underlying
Top 5Top options ranked by volume
AnalysisPCR, ATM straddle, and ATM IV intraday views

Bottom pane — "My Account"

A full-width account panel with its own tab strip, each tab a count-badged table.

TabColumns
PositionsSymbol, Qty, Avg, LTP, P&L
OrdersSymbol, Qty, Price, Trigger Price, Order Type, Status, Time — OCO orders show a summary row that expands to its Target/Stop Loss legs
GTT OrdersSymbol, Qty, Target (trigger + price), Stop Loss (trigger + price), Product
AlertsSymbol, Status, Trigger Price, Created

GTT Orders surfaces the same OCO (one-cancels-other) data the Orders tab's expandable rows use — ocoOrdersStreamer — as its own dedicated table.


See Trading for exact NxtChartHost member shapes and wire formats (including what drives the terminal's own Orders/Positions/Alerts tables), and How It Works for the data and trade flow.

On this page