Embedding
Quickstart
A working chart on your page, then the one thing that decides whether it draws anything. About two minutes, no account, no key.
What you are about to add
An iframe served from our origin. It is not a script bundle: nothing of ours runs in your page’s realm, your CSP gains one frame-src entry, and a bug of ours cannot break your page. The chart itself is what you see below.
1. Paste the snippet
A placeholder plus the loader. The loader's only job is to build the frame and keep its height in step with the chart, data-datafeed names the object you are about to write.
<div class="vp-chart"
data-symbol="BTCUSDT"
data-datafeed="myfeed"
data-height="480"></div>
<script src="https://view.verticalprop.com/embed.js"></script>2. Give it data, this is the step that matters
Here is a complete working feed against Binance’s public host, keyless, CORS-open, and runnable from your page on the first try. Copy it as-is and the chart above is what you get.
<div class="vp-chart"
data-symbol="BTCUSDT"
data-datafeed="binance"
data-height="480"></div>
<script src="https://view.verticalprop.com/embed.js"></script>
<script>
// Chart resolutions → Binance intervals.
var STEP = {
'1': '1m', '5': '5m', '15': '15m', '30': '30m',
'60': '1h', '240': '4h', '1D': '1d', '1W': '1w', '1M': '1M'
};
var sockets = {};
vpEmbed.datafeed('binance', {
onReady: function () {
return { supported_resolutions: Object.keys(STEP) };
},
resolveSymbol: function (name) {
return {
name: name,
description: name,
session: '24x7', // crypto never closes
timezone: 'Etc/UTC',
pricescale: 100, // BTCUSDT quotes to 2 decimal places
minmov: 1,
has_intraday: true,
supported_resolutions: Object.keys(STEP),
data_status: 'streaming',
provider: 'Binance' // shown as attribution on the chart
};
},
getBars: function (symbol, resolution, period) {
var url = new URL('https://data-api.binance.vision/api/v3/klines');
url.searchParams.set('symbol', symbol);
url.searchParams.set('interval', STEP[resolution] || '1d');
// endTime, NOT startTime: the chart needs `countBack` bars ENDING at
// `to`, and asking from `from` returns too few whenever the window
// holds less history than the chart asked for.
url.searchParams.set('endTime', String(period.to * 1000));
url.searchParams.set('limit', String(Math.min(1000, period.countBack)));
return fetch(url).then(function (r) { return r.json(); }).then(function (rows) {
// Binance already stamps kline open times in MILLISECONDS, which is
// what a bar wants — even though period.from/to arrived in seconds.
return rows.map(function (k) {
return {
time: k[0],
open: Number(k[1]), high: Number(k[2]),
low: Number(k[3]), close: Number(k[4]), volume: Number(k[5])
};
});
});
// Past the listing date Binance returns [], which is exactly how the
// chart learns to stop asking for older history.
},
subscribeBars: function (symbol, resolution, uid, push) {
var stream = symbol.toLowerCase() + '@kline_' + (STEP[resolution] || '1d');
var ws = new WebSocket('wss://data-stream.binance.vision/ws/' + stream);
ws.onmessage = function (ev) {
var k = JSON.parse(ev.data).k;
push({
time: k.t,
open: Number(k.o), high: Number(k.h),
low: Number(k.l), close: Number(k.c), volume: Number(k.v)
});
};
sockets[uid] = ws;
},
unsubscribeBars: function (uid) {
if (sockets[uid]) { sockets[uid].close(); delete sockets[uid]; }
}
});
</script>async, and that is deliberate. This snippet calls vpEmbed.datafeed(...) from an inline script, and embed.js assigns that global when it executes. With async the inline script wins the race, throws, and the chart sits on “Loading market data…” forever, which reads exactly like the venue being down. Snippets that only place a <div> keep async and are correct.3. Then one of these
| If you want to… | Go to |
|---|---|
| Understand the datafeed properly, streaming, search, symbol metadata | Use your own data |
| Avoid loading a third-party script at all | Without our script |
| Draw orders, stops, targets and positions on the chart | Orders and positions |
| Change theme, interval, chart type or hide the chrome | URL parameters |
| Use React, Vue, Svelte, WordPress or a CMS that strips scripts | Framework recipes |
| Answer a security review | Security model |
If nothing drew
Four causes account for nearly all of it, in order of likelihood.
| What you see | Almost always |
|---|---|
An explanation naming data-datafeed | You are on your own site with no feed. That is the BYOD rule, step 2. |
| “Loading market data…” forever | async on the loader beside an inline vpEmbed call, or your getBars never resolves. |
| An empty plot in the window you asked about | bar.time in seconds. It is MILLISECONDS; period.from/to are seconds. |
| Nothing at all, no frame | Your CMS stripped the <script> tag. Use the plain iframe form. |
The full list, with what each one looks like, is on Troubleshooting.