Documentation

FiChartJS SDK Documentation

SDK Overview

FiChartJS is a rendering component. Your application owns the data lifecycle. You fetch candles, decide when to backfill, stream updates, and react to user events. The SDK renders what you send and notifies you when it needs more data.

This design keeps business logic in your app and presentation logic in the chart.

Integration Paths

Choose one integration path depending on your stack:

  • React SDK: @fichartjs/sdk/react with <FiChart />
  • Vanilla SDK: load the CDN script https://cdn.fichartjs.com/sdk/v1.js

React quickstart

import React from 'react';
import { FiChart } from '@fichartjs/sdk/react';

export default function App() {
  return (
    <FiChart
      publicKey="pk_YOUR_KEY"
      config={{
        width: 900,
        height: 480,
      }}
      data={[
        {
          time: 1619827200000,
          open: 100,
          high: 102,
          low: 99,
          close: 101,
          volume: 100000,
        },
      ]}
      onLoadMore={nextBars => console.log(nextBars)}
    />
  );
}

Vanilla quickstart

<div id="fichart"></div>
<script src="https://cdn.fichartjs.com/sdk/v1.js"></script>
<script>
  const fiChart = new FiChart({
    publicKey: 'pk_YOUR_KEY',
    config: {
      width: 900,
      height: 480,
    },
  });
  fiChart.render('#fichart');
  fiChart.setData([
    {
      time: 1619827200000,
      open: 100,
      high: 102,
      low: 99,
      close: 101,
      volume: 100000,
    },
  ]);
</script>

Data Ownership Model

Use the SDK with a simple mental model:

  • setData replaces the full visible dataset.
  • updateCandle updates or appends the latest candle.
  • onLoadMore tells your app how many older bars are needed.
  • onTimeframeChange notifies your app that users changed timeframe.
  • setConfig replaces full chart config.
  • updateConfig patches part of chart config.

Method Reference

setData(data)

Use setData when you want a full snapshot render. This is the right method for initial load, full refresh, and backfill results after you merge older bars into your local state.

Example: Initial load

const fiChart = new FiChart({
  publicKey: 'pk_test_your_key',
});

fiChart.render('#fichart');
fiChart.setData(candles);

Example: After backfill merge

fiChart.onLoadMore(nextBars => {
  const older = buildOlderCandles(state.candles[0], nextBars);
  state.candles = [...older, ...state.candles];
  fiChart.setData(state.candles);
});

updateCandle(candle)

Use updateCandle for live streams. Send one candle at a time. If timestamp matches the last candle, update it. If timestamp is newer, append it.

Example: Tick stream

socket.onmessage = event => {
  const candle = normalizeTickToCandle(JSON.parse(event.data));
  fiChart.updateCandle(candle);
};

Example: Controlled simulation

startCandleStream(candle => {
  upsertLastCandle(state.candles, candle);
  fiChart.updateCandle(candle);
}, state.candles);

onLoadMore(callback)

onLoadMore is a pull signal from the chart. The callback receives the number of bars currently needed to satisfy left-side viewport and indicator requirements.

Example: Backfill with dedupe

fiChart.onLoadMore(nextBars => {
  const bars = Math.max(1, Math.ceil(Number(nextBars) || 0));
  const oldest = state.candles[0];
  const older = buildOlderCandles(oldest, bars);
  const knownTimes = new Set(state.candles.map(c => c.time));
  const dedupedOlder = older.filter(c => !knownTimes.has(c.time));
  if (!dedupedOlder.length) return;
  state.candles = [...dedupedOlder, ...state.candles];
  fiChart.setData(state.candles);
});

Example: Guard repeated calls

let isLoadingMore = false;

fiChart.onLoadMore(async nextBars => {
  if (isLoadingMore) return;
  isLoadingMore = true;
  try {
    const bars = Math.min(250, Math.ceil(Number(nextBars) || 0));
    const older = await fetchOlderBars(state.candles[0].time, bars);
    state.candles = [...older, ...state.candles];
    fiChart.setData(state.candles);
  } finally {
    isLoadingMore = false;
  }
});

onTimeframeChange(callback)

Use this callback to keep app state in sync with user selection. Typical uses: switch data source timeframe, reset stream cadence, and prune unsupported timeframe options when symbol changes.

Example: State sync

fiChart.onTimeframeChange(nextTimeframe => {
  appState.timeframe = nextTimeframe;
});

Example: Refetch per timeframe

fiChart.onTimeframeChange(async nextTimeframe => {
  const candles = await fetchCandles({
    symbol: appState.symbol,
    timeframe: nextTimeframe.id,
  });
  state.candles = candles;
  fiChart.setData(state.candles);
});

setConfig(config)

Use setConfig when you want to replace config as one full object. This is useful when switching to a saved chart layout profile.

Example: Full replacement

fiChart.setConfig({
  symbol: { code: 'MSFT', name: 'Microsoft' },
  timeframe: { id: 'D1', label: '1D' },
  timeframes: [
    { id: 'H1', label: '1H' },
    { id: 'D1', label: '1D' },
  ],
  theme: { lineColor: '#0094FF' },
});

updateConfig(partialConfig)

Use updateConfig for partial patches. This method preserves existing config and merges only provided keys.

Example: Remove unsupported timeframes after symbol switch

fiChart.updateConfig({
  symbol: { code: 'BTC-USD', name: 'Bitcoin' },
  timeframes: [
    { id: 'M15', label: '15m' },
    { id: 'H1', label: '1H' },
  ],
  timeframesMenu: {
    Minute: [{ id: 'M15', label: '15m' }],
    Hour: [{ id: 'H1', label: '1H' }],
  },
});

Example: Visual tweak only

fiChart.updateConfig({
  theme: {
    lineColor: '#35C2FF',
  },
});

Config Props Reference

This is the full config shape you can pass to setConfig and patch with updateConfig.

type FiChartConfig = {
  title: string;
  xKey: string;
  yKey: string;
  decimals: number;
  tool: string;
  screenshotFileFormat: string;
  excludeWeekend: boolean;
  session: string;
  timezone: string;
  fullscreen: boolean;
  showFullscreenButton: boolean;
  initialCandlesWindow: number;
  mobileThreshold: number;
  theme: {
    colors: string[];
    bgColor: string;
    borderRadius: number;
    primary: string;
    lineColor: string;
    lineWidth: number;
    lineCircleSize: number;
    candleUpColor: string;
    candleDownColor: string;
    fontFamily: string;
    fontSize: number;
    fontWeight: number;
    color: string;
    padding: [number, number, number, number];
    title: {
      fontSize: number;
      marginBottom: number;
      color: string;
      colorHover: string;
    };
    subtitle: {
      fontSize: number;
      marginBottom: number;
      color: string;
      colorHover: string;
    };
    header: {
      angleDownSize: number;
      angleDownColor: string;
      angleDownColorHover: string;
      toolbarBorderColor: string;
      buttonFontSize: number;
      buttonColor: string;
      buttonColorHover: string;
      buttonColorActive: string;
      buttonBgColorActive: string;
      buttonBorderColor: string;
      buttonBorderColorActive: string;
      marginRight: number;
      background: string;
    };
    legend: {
      fontSize: number;
      color: string;
      itemMarginRight: number;
      itemMarginBottom: number;
      marginBottom: number;
      circle: {
        positionTop: number;
        size: number;
        marginRight: number;
        lineWidth: number;
      };
    };
    grid: { lineColor: string; showY: boolean; showX: boolean };
    axes: {
      fontSize: number;
      color: string;
      emphasizeColor: string;
      lineColor: string;
      tickColor: string;
      tickSize: number;
      tickMargin: number;
      lastValueTickBgSize: number;
      xEndShiftRatio: number;
      minTickWidthX: number;
      minTickSpaceY: number;
      hoverColor: string;
    };
    indicatorWindow: {
      splitLineColor: string;
      splitLineWidth: number;
      bgColor: string;
      color: string;
      settingsColor: string;
    };
    crosshair: {
      color: string;
      tickColor: string;
      tickBgColor: string;
      lineColor: string;
      lineDash: number[];
    };
    ohlcvOverlay: {
      keyColor: string;
      valueColor: string;
      fontSize: number;
      overlayMarginTop: number;
      overlayMarginBottom: number;
      overlayMarginRight: number;
      itemMarginBottom: number;
      itemMarginRight: number;
      colMarginRight: number;
    };
    menu: {
      bgColor: string;
      borderColor: string;
      borderWidth: number;
      borderRadius: number;
      padding: number;
      item: {
        padding: number;
        fontSize: number;
        color: string;
        colorHover: string;
        colorActive: string;
        bgColorHover: string;
        bgColorActive: string;
        borderRadius: number;
      };
    };
    input: {
      labelColor: string;
      labelBgColor: string;
      color: string;
      bgColor: string;
      bgColorHover: string;
      bgColorDarker: string;
      bgColorDarkerHover: string;
    };
    button: {
      borderColor: string;
      borderColorHover: string;
      borderColorActive: string;
      color: string;
      colorHover: string;
      colorActive: string;
    };
    popoverMenu: {
      zIndex: number;
      background: string;
      bgColor: string;
      borderColor: string;
      color: string;
      uppercaseColor: string;
    };
    indicatorsMenu: {
      titleColor: string;
      subtitleColor: string;
      indicatorTextColor: string;
      borderColor: string;
      settingsColor: string;
      buttonBgColor: string;
      buttonBorderColor: string;
      buttonBorderColorActive: string;
      labelColor: string;
      addIconColor: string;
      addIconColorActive: string;
      settingsColorActive: string;
      removeIconColor: string;
    };
    drawingToolbar: {
      colors: string[];
      iconColor: string;
      bgColor: string;
      borderColor: string;
      closeColor: string;
      closeBgColor: string;
      textColor: string;
      textColorActive: string;
      textBgColor: string;
    };
    switch: {
      activeBgColor: string;
      inactiveBgColor: string;
      activeColor: string;
      inactiveColor: string;
    };
    dragList: {
      topZIndex: number;
      borderColor: string;
      bgColorHover: string;
      handleBgColorHover: string;
      handleColor: string;
    };
    close: {
      bgColorActive: string;
      color: string;
      colorHover: string;
      colorActive: string;
    };
    indicators: {
      bb: {
        bgColor: string;
        upperlineColor: string;
        middlelineColor: string;
        lowerlineColor: string;
      };
      macd: {
        bgColor: string;
        histogramColor: string;
        histogramBgColor: string;
        signalLineColor: string;
        signalLineBgColor: string;
        histogramBarColor: string;
        histogramBarBgColor: string;
      };
      roc: { zeroLineColor: string };
      rsi: {
        bgColor: string;
        upperLineColor: string;
        lowerLineColor: string;
        middleLineColor: string;
      };
      stochastic: {
        bgColor: string;
        upperLineColor: string;
        lowerLineColor: string;
        middleLineColor: string;
      };
    };
  };
  indicators: {
    showMenu: boolean;
    currentScreen: string;
    currentScreenMeta: unknown[];
    mainChart: unknown[];
    windows: unknown[];
    defaultConfig: Record<string, unknown>;
  };
};

Use setConfig when replacing the whole object, and updateConfig for partial patches.

Yahoo Finance Browser-Direct Examples

Yahoo endpoints can work in browser demos but are not guaranteed for production stability. Expect endpoint changes, request limits, and occasional CORS issues.

Example: Browser-direct candle fetch

async function fetchYahooCandles(symbol, interval = '1d', range = '6mo') {
  const endpoint = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(
    symbol,
  )}?interval=${interval}&range=${range}`;

  const res = await fetch(endpoint);
  const body = await res.json();
  const result = body?.chart?.result?.[0];
  if (!result) return [];

  const quote = result?.indicators?.quote?.[0] || {};
  const timestamps = result?.timestamp || [];

  return timestamps
    .map((ts, i) => ({
      time: ts * 1000,
      open: quote.open?.[i],
      high: quote.high?.[i],
      low: quote.low?.[i],
      close: quote.close?.[i],
      volume: quote.volume?.[i] || 0,
    }))
    .filter(c =>
      [c.open, c.high, c.low, c.close].every(v => typeof v === 'number'),
    );
}

Example: Yahoo + SDK flow

const candles = await fetchYahooCandles('AAPL', '1d', '1y');
state.candles = candles;
fiChart.setData(state.candles);

Troubleshooting

Duplicate backfill candles

Dedupe by time before prepending. Repeated onLoadMore calls can occur while panning.

Timeframe mismatch after symbol change

Use updateConfig to patch timeframes and timeframesMenu immediately after symbol change.

CSP Iframe Blocked

FiChartJS embed runs in an iframe from https://fichartjs.com, so your site CSP must allow that iframe origin.

Minimum requirement:

Content-Security-Policy: frame-src 'self' https://fichartjs.com;

If this is missing, browsers block the iframe request and the chart can remain on loading. Check browser console for errors like: Refused to frame 'https://fichartjs.com/...' because it violates Content Security Policy directive "frame-src ..."

If you already have a CSP header, append https://fichartjs.com to your existing frame-src directive.

Common setups

Next.js (next.config.mjs / next.config.js)

const csp = "default-src 'self'; frame-src 'self' https://fichartjs.com;";

const nextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: csp,
          },
        ],
      },
    ];
  },
};

export default nextConfig;

Express / Node.js

app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; frame-src 'self' https://fichartjs.com;",
  );
  next();
});

Nginx

add_header Content-Security-Policy "default-src 'self'; frame-src 'self' https://fichartjs.com;" always;

Apache (.htaccess or vhost)

Header always set Content-Security-Policy "default-src 'self'; frame-src 'self' https://fichartjs.com;"

Vercel (vercel.json)

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; frame-src 'self' https://fichartjs.com;"
        }
      ]
    }
  ]
}

Netlify (netlify.toml)

[[headers]]
  for = "/*"
  [headers.values]
    Content-Security-Policy = "default-src 'self'; frame-src 'self' https://fichartjs.com;"

Cloudflare (dashboard)

Set a response header rule for Content-Security-Policy and include:

frame-src 'self' https://fichartjs.com