Browse documentation
Guide Updated 18 August 2026

Embed a dashboard or widget

Drop a live Hodoflow dashboard, or a single widget, into any web page with one script tag and one custom element, themed with CSS custom properties.

View as Markdown

An embed is a read-only, live copy of one of your dashboards — or of a single widget — rendered inside someone else's page. It connects straight back to Hodoflow over a WebSocket, so it updates as new records land, exactly like the dashboard does in the app.

Everything about editing is absent by design. The embed is a viewer.

Outcome

A page outside Hodoflow shows one of your dashboards, or one widget, live, to visitors who are not signed in.

Before you start

  • A saved dashboard with at least one widget.
  • Somewhere you can edit HTML on the page that will host it.
  • The decision that this dashboard's data can be seen by anyone holding a token. Anyone with the token can render it without signing in.

1. Get the snippet

Open the dashboard, press Share, and turn on Public embedding. An Embed snippet appears with Copy snippet beneath it. Paste it where you want the dashboard to appear:

<script src="https://app.example.com/assets/embed/v1/dash-embed.js"></script>
<dash-dashboard token="YOUR_SHARE_TOKEN" host="https://app.example.com" live="true"></dash-dashboard>

Both https://app.example.com values are your Hodoflow host, filled in for you when you copy. The script registers the <dash-dashboard> element; the element does the rest.

The script URL is versioned. v1 stays compatible for the life of the major version, so the tag you paste today keeps working.

Checkpoint: the host page shows Connecting to dashboard… and then your widgets.

2. Verify the result

  • The embed renders the same widgets as the dashboard.
  • A new record produced by your workflow changes them without a page reload.
  • Opening the host page in a private window, signed out, still works.

Attributes

Attribute Purpose
token The dashboard's share token. Required.
host Your Hodoflow origin, used for both the script and the socket.
live "true" streams new data as it lands; "false" renders a static snapshot. Absent means live.
tz The timezone to render in, as an IANA name like America/Chicago. Absent means UTC.
preset A starting time range, using the same preset keys as the dashboard picker — last_7d, this_month, and so on.
from / to A starting custom window, as ISO 8601 timestamps. Both are required together, and take precedence over preset.

An older org attribute is still accepted for embeds that were written before tokens became self-sufficient. New embeds should leave it out — omitting it is what lets you rename your organization without breaking the integration.

Nothing outside that table is accepted, and no value in it is guessed at. An attribute Hodoflow does not recognise, or a value it cannot read, shows an error in place of the embed rather than rendering with a substitute — see What the embed shows.

Embed a single widget

<dash-widget> renders one widget on its own, outside any dashboard. Open the widget in the widget library, press Share, and turn on Public embed:

<script src="https://app.example.com/assets/embed/v1/dash-embed.js"></script>
<dash-widget token="YOUR_WIDGET_SHARE_TOKEN" host="https://app.example.com"></dash-widget>

One script tag registers both elements, so a page using both only loads it once.

The widget's own share token is separate from any dashboard's, and the two are never interchangeable: a dashboard token in a <dash-widget> renders Widget not found or not publicly shared.

Everything above applies — the same attributes minus org, the same theming properties, the same postMessage controls, the same part selectors. Two differences:

  • There is no dashboard header, and no grid. The element renders one card, filling whatever height you give it; with no height set it falls back to --dash-widget-h (320px).
  • The widget's own Filters are the only thing a runtime window is layered over, since there is no dashboard baseline above it.

A shared widget and a shared dashboard both occupy an embedded-asset slot on your plan.

Theming

The element renders inside a shadow root, so the host page's CSS cannot leak in and Hodoflow's cannot leak out. Theming happens through CSS custom properties you set on the element or any ancestor.

<style>
dash-dashboard {
--dash-accent: #10b981;
--dash-bg: #0b0f19;
--dash-card-bg: #131a2a;
--dash-text: #e5e7eb;
--dash-text-muted: #9ca3af;
--dash-border: #1f2937;
--dash-radius: 0.75rem;
--dash-font: "Inter", system-ui, sans-serif;
}
</style>

The full set:

Property Default Controls
--dash-accent #6366f1 Bar fills, line strokes, sparklines, markdown links, pivot totals
--dash-accent-contrast #fff Text drawn on top of accent-filled surfaces
--dash-font system sans-serif stack Font family for the whole embed
--dash-text #111827 Primary text
--dash-text-muted #6b7280 Secondary text
--dash-text-subtle #9ca3af Tertiary text
--dash-bg #ffffff Root background
--dash-bg-muted #f9fafb Muted surfaces
--dash-border #e5e7eb Borders
--dash-border-soft #f3f4f6 Internal dividers
--dash-error #dc2626 Error text
--dash-success #16a34a Positive values
--dash-shadow 0 1px 2px rgba(0,0,0,0.05) Default shadow
--dash-card-bg inherits --dash-bg Widget card background
--dash-card-border inherits --dash-border Widget card border
--dash-card-shadow inherits --dash-shadow Widget card shadow
--dash-radius 0.5rem Corner radius
--dash-grid-row-h 70px Grid row height
--dash-grid-gap 0.75rem Gap between widgets
--dash-padding 1rem Outer padding
--dash-widget-h 320px Height a <dash-widget> falls back to

--dash-accent is re-read shortly after mount as well as during it, so setting it from JavaScript after the element has appeared still takes effect. Pie charts deliberately ignore it and keep their multi-colour palette, since one accent cannot distinguish segments.

For anything the properties do not reach, every meaningful node carries a part attribute, so ::part() selectors work from the host page: root, header, title, grid, widget, widget-header, widget-body, table, feed, pivot, markdown, and the state nodes.

Controlling the embed at runtime

The embed listens for two postMessage messages on window, which is how a host page's own controls can drive it without re-mounting the element.

Change the time range:

window.postMessage({ type: 'dash:time_range', preset: 'last_7d' }, '*');
window.postMessage({
type: 'dash:time_range',
from: '2026-07-01T00:00:00Z',
to: '2026-07-31T23:59:59Z'
}, '*');

from and to together win over preset. A preset of all_time or custom clears the range. If Hodoflow rejects the range, the element dispatches a dash:filter_rejected event you can listen for on the element itself.

Pause and resume live updates:

window.postMessage({ type: 'dash:live', live: false }, '*');

Any live value other than false resumes. A runtime toggle overrides the live attribute.

What the embed shows

While connecting: Connecting to dashboard… (or Connecting to widget…).

If the token is wrong, or the dashboard is no longer shared: Dashboard not found or not publicly shared. — which is also what you will see right after using Regenerate token without updating the snippet.

If the dashboard has no widgets yet: No widgets on this dashboard yet.

If an attribute is wrong, the error appears in place of the embed and it names what is wrong — Invalid tz attribute: "America/Chigago"., Invalid live attribute: "yes". Use "true" or "false"., Unknown attribute: "timezone". Nothing renders, deliberately: a bad zone or a window Hodoflow could not read would otherwise show numbers that are quietly wrong, and an embed has no viewer who could tell.

Any other failure renders as Connection error: followed by the reason.

Common problems

Dashboard not found or not publicly shared. The token is wrong, the dashboard is no longer shared, or the token was regenerated without updating the snippet.

Widget not found or not publicly shared. The same, for <dash-widget> — or a dashboard token pasted into a <dash-widget> (or the reverse). The two token spaces are separate.

Connection error: followed by a reason. The host attribute is wrong, or the host page is blocking the WebSocket.

Nothing but Connecting to dashboard…. The script loaded but the socket did not open. Check host matches your Hodoflow origin exactly, including the scheme.

No widgets on this dashboard yet. The dashboard is empty. Add widgets and save.

Today shows different numbers than the app. With no tz attribute, calendar presets in an embed resolve in UTC, so Today means the UTC day. Set tz="America/Chicago" (or whichever zone the page's readers are in) to match what you see inside Hodoflow. Rolling windows like last_7d are the same in every zone and are unaffected.

An invalid attribute error you did not expect. Only the documented attributes are accepted. Framework-added attributes (class, style, data-*, aria-*, and directives like v-/x-/ng-) are left alone; a plain unrecognised name is treated as a typo, because from outside there is no way to tell one from the other.

A token leaked. Use Regenerate token — every existing embed stops working immediately, which is the point. Turning Public embedding off revokes the whole thing.

Next step

Build a dashboard for the sharing controls and the widget reference.

Related documentation