kandlo Docs

1. Quickstart

Last updated · 8 min read

On this page

Put a live kandlo chart in a React app: first on your machine in about five minutes, then on your own domain.

How the pieces fit:

Text
your app (app.example.com)             chart page (charts.example.com)        feed server (feed.example.com)
  <KandloChart src=...>  -- postMessage -->  embed bridge + engine  -- Socket.IO -->  your market data
        <iframe>         <-- events -------                        <-- candles, ticks --
  • The chart page is apps/web/ from this package: a static site. It runs in an <iframe> with its own React, its own global CSS and its own globals, so nothing in your app can break it and nothing in it leaks into your app.
  • @kandlo/react creates that iframe and talks to it. It ships as a tarball in this package; it is not on a public package registry.
  • The feed server sends the chart its candles and ticks over Socket.IO. Serve your own market data shows how to serve your own.

Requirements: Node.js 18 or later for the kandlo tools, and a React 17+ app (any build tool) served over http(s). Your app's build tool may need a newer Node.js (current Vite needs 20.19 or later).


Part A: on your machine#

A1. Start a feed and the chart page#

In the kandlo folder:

Shell
npm install                              # once
node examples/custom-feed/server.mjs     # terminal 1: example feed on ws://localhost:8443
npm run serve                            # terminal 2: chart page on http://localhost:9000

Open http://localhost:9000/ and check that candles appear (EURUSD, BTCUSD, XAUUSD and IDX500, a new tick every 500 ms). The built bundles are part of the package, so no build step is needed.

Other feeds for local work:

  • npx cross-env FEED=otc npm run relay: an offline simulated market with 129 symbols. It also listens on port 8443, so stop the example feed first.
  • No feed at all: pass params={{ replay: 1 }} to the component below and the chart plays a recorded EUR/USD session.

A2. Install the component#

Copy the tarball into your app's repository, so every checkout and CI run has it, and install it from the file:

Shell
mkdir -p vendor
cp <kandlo>/packages/kandlo-react/dist/kandlo-react-1.0.0.tgz vendor/
npm install ./vendor/kandlo-react-1.0.0.tgz

package.json then records "@kandlo/react": "file:vendor/kandlo-react-1.0.0.tgz". In a git checkout of kandlo (rather than the release package), build the tarball first with npm run pack:react.

A3. Render a chart#

JSX
import { useRef } from 'react';
import { KandloChart } from '@kandlo/react';

export function PriceChart() {
  const chart = useRef(null);   // the chart handle once it is ready

  return (
    <div style={{ height: 480 }}>
      <KandloChart
        ref={chart}
        src="http://localhost:9000/"
        symbol="EURUSD"
        timeframe="M5"
        chartType="Candles"
        theme="dark-blue"
        chrome="minimal"
        onReady={(state) => console.log('chart ready', state.chart)}
        onError={(err) => console.warn(err.code, err.message)}
      />
    </div>
  );
}

Start your app's dev server and open it. The chart fills its container: give the container a height (without one it is 300 px tall).

Then try the handle:

JavaScript
await chart.current.addIndicator('rsi', { period: 21 });
await chart.current.setTimeframe('H1');
const candles = await chart.current.getCandles({ count: 100 });

Checked for this guide: A2 and A3 run as written in a new Vite app (the tarball installed with npm install, React 18), with the example feed: the chart reported ready on EURUSD M5, then addIndicator, setTimeframe('H1') and getCandles({ count: 100 }) all succeeded.

A complete app with symbol, timeframe, theme and layout selectors, an indicator button and a crosshair readout is in examples/react-vite/ (examples/react-vite/).

Server-rendered React apps#

<KandloChart/> can be imported from a server component (for example a page in the Next.js App Router): @kandlo/react marks the component with 'use client', so the framework renders it on the client. On the server it renders an empty <div data-kandlo-chart>; the iframe is created in the browser after hydration. Nothing in the package reads window or document at import time. Give the container a height in your server markup, as above.

Checked: a Next.js 15 App Router app (React 19) with <KandloChart/> in a server component page built with next build, and the started app drew the chart (getState reported ready on EURUSD M5).

If the chart does not appear, see Troubleshooting.


Part B: on your own domain#

The recommended setup uses three origins:

Origin Serves
https://app.example.com your app
https://charts.example.com the chart page (static files)
wss://feed.example.com the feed server

The chart page should have its own origin, different from your app's: the iframe sandbox only holds across origins. See Security.

B1. Configure the chart page#

Edit apps/web/index.html. Search for var AppData =: it is one long line inside an inline <script>. Right after that statement (your script must run before app.js), add:

HTML
<script>
  // Origins allowed to embed and drive the chart.
  AppData['embed-allowed-origins'] = ['https://app.example.com'];
  // Your feed server. wss:// because the page is served over https.
  AppData['relay-servers-list'] = [{ server: 'wss://feed.example.com', name: 'Primary' }];
</script>

The time axis shows each viewer's local time. To show one zone to everyone, add AppData['userTimeZoneOffset'] = 0; (UTC; the value is minutes east of UTC) to the same script. Keep serverTimeZoneOffset at 0, as shipped; see API reference.

B2. Build the static site#

Shell
npm run build:static          # writes dist/

dist/ is a self-contained static site (about 11 MB): index.html, assets/, kandlo-assets/ and favicon.ico. The script stamps the bundle version into index.html and reports any asset path that would return 404. It is the same index.html you edited in B1; you can also edit dist/index.html after the build.

Rebuild the bundles first (npm run build && npm run build:interactions) only if you changed the chart's source code or themes.

B3. Upload it#

Serve dist/ from https://charts.example.com/ with any static host or object store. Response headers:

Path Header
index.html Cache-Control: no-cache (it carries the bundle version stamp)
index.html Content-Security-Policy: frame-ancestors https://app.example.com (who may frame the chart; see Security)
everything else normal caching

Do not send X-Frame-Options: DENY or SAMEORIGIN on index.html; either one blocks the embed.

Checked for this guide: dist/ served by a plain static file server (no rewriting, no fallbacks) with a frame-ancestors header, framed by another origin, drew 1000 H1 candles from the example feed with no 404 and no console error.

B4. Run the feed#

Deploy your feed server (Serve your own market data) behind TLS at feed.example.com, and limit it to the chart page's origin. With the example server:

Shell
PORT=8443 FEED_ALLOWED_ORIGINS=https://charts.example.com node examples/custom-feed/server.mjs

TLS termination (for wss://) is your proxy's or platform's job; the example listens on plain ws. The VAR=value command form is for POSIX shells; on Windows use npx cross-env PORT=8443 FEED_ALLOWED_ORIGINS=... node examples/custom-feed/server.mjs.

B5. Point your app at it#

JSX
<KandloChart src="https://charts.example.com/" symbol="EURUSD" timeframe="M5" />

Without React#

The same package works in any page through the plain API, which does not import React:

JavaScript
import { createKandloChart } from '@kandlo/react/vanilla';

const chart = await createKandloChart(document.getElementById('chart'), {
  src: 'https://charts.example.com/',
  symbol: 'BTCUSD',
  timeframe: 'H1',
  chrome: 'minimal',
});

chart.on('candle', (e) => console.log(e.candle.close, e.isNew));
await chart.addIndicator('ma', { period: 50 });
// later: chart.destroy();

Without any npm package, post the messages yourself; the protocol is in API reference.

Troubleshooting#

Look in two consoles: your app's, and the chart page's (in the browser's developer tools, select the chart frame in the console's context selector). The chart page reports its own problems with an [embed] prefix.

What you see Cause and fix
The chart is only 300 px tall The container has no height of its own, so the component's minimum applies. Give it, or its parent, a height.
onError with timeout, "no answer from the chart page" src is wrong, the chart page is not running, or your app's origin is not allowed. The chart console then says [embed] bridge disabled: <reason>; add your origin to embed-allowed-origins (B1).
timeout, "answered but did not report ready" The chart page runs but never got going. If its console says [embed] the chart has not started yet; still waiting, it has not reached the feed: check the feed is running and that the URL in relay-servers-list is reachable from the browser. The page keeps waiting and reports ready once the feed connects, but createKandloChart and <KandloChart> give up at readyTimeout; remount the chart after fixing the feed.
onReady fires, but state.hasData is false The feed connected but sent no candles within 30 seconds for that symbol and timeframe. See the checklist.
bad_option, "the host page has no http(s) origin" Your page was opened from file://. Serve it over http.
The chart shows another symbol than symbol The symbol is not in the feed's list. The chart console says [embed] ignored ?symbol=: unknown symbol: ....
The browser console says it refused to frame the page The chart page's Content-Security-Policy: frame-ancestors does not list your app, or it sends X-Frame-Options (B3).
It works locally, not deployed An https page cannot open ws://: use wss:// in relay-servers-list (B1), and allow the chart page's origin on the feed (B4).
A fix in index.html has no effect index.html is cached. Serve it with Cache-Control: no-cache (B3) and reload.

FAQ#

Is @kandlo/react on a public package registry? No. It ships as a tarball in this package and is installed from a file (A2).

Do I need React? No. @kandlo/react/vanilla works in any page, and the postMessage protocol works with no package at all.

Do I need a server? Two static sites (your app and the chart page) and a feed server. For a demo without a feed, pass params={{ replay: 1 }}.

Can the chart page share my app's origin? It works, but the iframe sandbox no longer protects your app. Use a separate origin, even a subdomain (Security).

Can I change the feed URL from my app? No. It is read once from the chart page's index.html (Serve your own market data).

Do indicators and drawings survive a reload? No. Re-apply them after ready; the component's indicators prop does it for you.

Can I put several charts on one page? Yes. Each <KandloChart/> is its own iframe with its own feed connection.

Which time zone does the axis show? Each viewer's local time, unless AppData['userTimeZoneOffset'] sets one (B1).

Are TypeScript types included? Yes, for both entry points (API reference).

Next#

Type to search all guides.