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.
Choose one integration path depending on your stack:
@fichartjs/sdk/react with <FiChart />https://cdn.fichartjs.com/sdk/v1.jsimport 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)}
/>
);
}<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>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.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.
const fiChart = new FiChart({
publicKey: 'pk_test_your_key',
});
fiChart.render('#fichart');
fiChart.setData(candles);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.
socket.onmessage = event => {
const candle = normalizeTickToCandle(JSON.parse(event.data));
fiChart.updateCandle(candle);
};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.
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);
});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.
fiChart.onTimeframeChange(nextTimeframe => {
appState.timeframe = nextTimeframe;
});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.
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.
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' }],
},
});fiChart.updateConfig({
theme: {
lineColor: '#35C2FF',
},
});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 endpoints can work in browser demos but are not guaranteed for production stability. Expect endpoint changes, request limits, and occasional CORS issues.
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'),
);
}const candles = await fetchYahooCandles('AAPL', '1d', '1y');
state.candles = candles;
fiChart.setData(state.candles);Dedupe by time before prepending. Repeated onLoadMore calls can occur while panning.
Use updateConfig to patch timeframes and timeframesMenu immediately after symbol change.
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.
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;app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; frame-src 'self' https://fichartjs.com;",
);
next();
});add_header Content-Security-Policy "default-src 'self'; frame-src 'self' https://fichartjs.com;" always;.htaccess or vhost)Header always set Content-Security-Policy "default-src 'self'; frame-src 'self' https://fichartjs.com;"vercel.json){
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; frame-src 'self' https://fichartjs.com;"
}
]
}
]
}netlify.toml)[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = "default-src 'self'; frame-src 'self' https://fichartjs.com;"Set a response header rule for Content-Security-Policy and include:
frame-src 'self' https://fichartjs.com