Skip to content

Embedding

Troubleshooting

Organised by SYMPTOM, because that is what you have. Each entry says what you are looking at, why it happens, and the change that fixes it.

Nothing appears at all, not even a box

Almost always a stripped script tag. Hosted CMS editors, Markdown sanitisers and rich-text fields remove <script> silently, so your placeholder stays an empty div with no error anywhere. View the published page’s source: if embed.js is missing from it, that is your answer.

The fix is the iframe form, which needs no script of ours:

<iframe src="https://view.verticalprop.com/embed?symbol=BTCUSDT&datafeed=host"
        style="width:100%;height:480px;border:0" loading="lazy"></iframe>

Two other causes worth checking in order: the placeholder was added after the page loaded (call vpEmbed.scan(), see client-side routing), or your CSP has no frame-src entry for our origin, which the browser console will say out loud.

“This chart needs a datafeed”

Working as intended, and it is the one refusal that is a product decision rather than a fault. VerticalProp ships the charting engine; the market data is yours. A chart embedded on a site that is not ours has to be told where its bars come from.

The fix: name a feed your page provides
<div class="vp-chart" data-symbol="BTCUSDT" data-datafeed="myfeed" data-height="480"></div>

<script>
  vpEmbed.datafeed('myfeed', { /* onReady, resolveSymbol, getBars, … */ });
</script>

The datafeed page has a complete, copy-pasteable implementation over a public keyless endpoint, it runs from your own page on the first try.

The attribute is data-datafeed and there is deliberately no second flag to set. The loader adds datafeed=host to the frame URL itself, so there is no state where one is present and the other is not, which would be a chart silently drawing our bars under your symbol.

The frame is there, the chart is blank

A frame with our background and no candles means the chart asked for bars and got something it would not draw. In order of likelihood:

1. Bar times in seconds. period.from and period.to arrive in seconds; a bar’s time must be MILLISECONDS. Return seconds and every bar lands in 1970, the chart is drawing your data, in a window fifty years away. This is the single commonest cause and it produces no error.

2. Paging from from instead of countBack back from to. Anchoring on the start of the window returns too few bars whenever the window holds less history than the chart asked for.

3. A NaN or a string in one field. One bad extreme scales the whole visible range and empties the plot. The chart refuses a page that fails validation and names the field and the bar index in the console, open it.

4. Bars out of order, or duplicated. Pages must ascend by time. The chart refuses rather than drawing an unordered series, because a chart that is wrong in that way is wrong invisibly.

“This embed names a symbol that is not a valid ticker”

The symbol was refused before anything was fetched, so this is about the STRING rather than about your feed: nothing was asked for it. A symbol may be up to 32 characters of letters, digits and . _ : / - = ^, must start with a letter, a digit or ^, and may not contain ... Everything else is refused.

In practice it is one of three things. A space, usually from a template that interpolated a display name. An encoding slip, where %2F or %20 arrived instead of the character. Or a spelling with a character we do not accept, BTC/USDT@OKX being the common shape.

It is refused rather than substituted, and that is the design. Falling back to a default would draw a real chart of a different instrument under your caption, which on a page saying “here is the price of X” is a lie rather than a degradation. Everything else in the URL behaves the opposite way: a misspelled data-theme gives you the default theme rather than an error, because a chart that refused to render over a colour would be a broken embed on somebody’s page. Limits has the exact bounds.

A method I called did nothing

Every command answers, so the first move is to look at the answer: through the loader it is a REJECTED PROMISE, and an unawaited call throws that away silently. await it, or attach a .catch.

The reason is always there
chart.createOrderLine({ price: 42000, lineColour: '#f00' })
  .catch((err) => console.warn(err.message));
// unknown property "lineColour"

An unknown property is named, and a known one outside its domain is refused too, so lineStyle: 9 does not draw an invisible line. The whole call is refused rather than the offending key, which is why a typo shows up as a line that never appeared rather than a line that looks slightly wrong.

The other cause is timing on the far side of the chart, not of the frame: a price needs a plot to sit in, so lines placed before the first bars are drawn have no scale to be positioned against. Place them from dataLoaded. Commands sent before the chart exists at all are buffered and replayed, so that half is not something you have to handle.

The chart is squashed, or the page has a scrollbar

In the loader form the frame’s height comes from the chart, over postMessage. If your CSS sets a height on the iframe with !important, or the placeholder sits in a flex or grid parent that overrides it, the handshake is fighting your stylesheet and losing.

Set data-height and let the loader own it, or use the iframe form and own it entirely. Do not do both.

The embed collapses to zero height

Site builders, Webflow, Framer, Squarespace, frequently wrap custom HTML in a container with no intrinsic height, and an iframe inside it computes to nothing. Set the height on the builder’s element as well as on the iframe.

Prices render with the wrong number of decimals

pricescale in your resolveSymbol answer. 100 is two decimal places, 100000000 is eight. A pair quoted to eight places with the default renders as a column of identical numbers and a flat-looking series.

Gaps where the market was open, or none where it was shut

session and timezone. Use "24x7" for anything continuous; for a cash equity give the real session ("0930-1600") and the IANA zone it is expressed in. A session with no timezone shifts by an hour twice a year.

Community indicators do not appear

Three separate things, and the symptom is the same for all of them.

The ids are wrong. data-indicators takes published indicator ids, the last segment of an indicator’s page URL. A private or withdrawn one resolves to nothing and the chart draws without it rather than failing, which is deliberate but does look like silence.

There are more than five. That is the cap. Each is a fetch, a compile and a worker in the reader’s browser.

The isolation sandbox is unreachable. Indicators are other people’s code and run in a second frame on a separate domain with no network at all. Some corporate proxies and DNS filters block that domain. When they do, indicators do not run and the chart says so, there is deliberately no fallback that executes them somewhere less isolated, because a reader would have no way to tell which mode they were in.

The reader cannot connect their account

The control lives in the chart’s indicator dialog, the fx button, under Community. Two reasons it is not there:

You turned the toolbar off. With data-toolbar="0" there is no fx button, and therefore nowhere for it to live. A chrome-less embed is a picture of a market; keep the toolbar if you want readers to bring their own indicators.

The frame cannot tell which site it is on. Consent is bound to your site, so the frame has to know your site to ask for it. If your iframe carries referrerpolicy="no-referrer" in a browser without ancestorOrigins, Firefox, nothing can be derived and no control is offered, rather than one that leads to an explanation of why it cannot work.

If the popup itself is blocked, the chart says so. Consent must open from the click directly, so anything that delays it, including an extension, costs the gesture.

The chart is the wrong colour

data-theme takes light or dark, and there is no automatic mode: the frame cannot see your page’s theme, and guessing from the reader’s OS preference would put a dark chart on a light article half the time. Drive it from your own theme switch through the JavaScript API.

frame.contentWindow.postMessage(
  { ns: 'vp-embed', v: 1, type: 'setTheme', theme: 'dark' }, '*');

A colour I set was ignored

Three causes, and which one it is depends on which door you used. In data-colors, a misspelled role is dropped and the rest of the palette still applies, so the symptom is one colour missing and no error anywhere. Check the name against the ten roles first.

In setStyle, the same mistake refuses the WHOLE call and names the key, so look for a commandError in the console rather than at the chart. That difference is deliberate: a URL is read by every reader of your page and a method call is read by you.

And a value that is not #rgb, #rrggbb or #rrggbbaa is refused by both. rgb() and plain colour names are not accepted; the reason is on the styling page, and it is a security property rather than an oversight. If your whole palette vanished at once rather than one role, the colors= string was over its length cap and was refused entire.

**A setStyle outlives a setTheme**, which surprises people the other way round. Your palette is re-applied on top of each base theme rather than dropped, so a bg you set dark is still dark after switching the chart to light. That is what you want when the colours are your brand; if they were a tweak to one theme, set them again after the switch.

My toolbar button never appeared

Almost always the toolbar itself. It is off unless you ask for it, and with no header there is nowhere to place a button, so the call is refused rather than resolving and await chart.createToolbarButton(…) rejects with a message naming data-toolbar. Add data-toolbar="1" to the placeholder.

If it appeared and then stopped answering, check you kept the handle: remove() is idempotent, so a second call is silent, and a component that re-mounted and made a second button has two. The label is capped at 32 characters and a longer one is refused outright rather than trimmed, which reads as the button never being created.

postMessage does nothing

Every message needs the full envelope, ns, v, type, and no fields beyond the ones documented. An unknown field is what makes the whole message invalid, which is the check most re-implementations drop. Compare yours against the API page field by field.

Also: send it to frame.contentWindow, not to window, and only after the frame has posted ready. A message sent before the chart exists is dropped.

What the console will and will not tell you

Errors inside the frame appear in your console under the frame’s own document, most devtools group them separately, and the frame’s name is our origin. If you see nothing there at all, the frame did not load, which is a networking or CSP question rather than a chart one.

You cannot read into the frame from your page (contentWindow.document throws), and that is the browser enforcing the boundary rather than us. It is also the fastest way to confirm the isolation is real, see checking it yourself.

Still stuck

Write to support@verticalprop.com with the page URL and the snippet you pasted. If the chart is rendering something wrong rather than nothing, a screenshot of your console’s frame-scoped output is worth more than a description.