An embed is a read-only, live copy of one of your dashboards, 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.
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.
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. |
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. Takes 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.
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-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…
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.
Any other failure renders as Connection error: followed by the reason.
Things to know before you ship one
Calendar presets in an embed resolve in UTC rather than a viewer's timezone, so Today in an embed means the UTC day. It can differ from the same dashboard viewed inside Hodoflow, where it follows your profile timezone.
Anyone with the token can render the dashboard without signing in. Treat the snippet as a credential, and use Regenerate token if it leaks — every existing embed stops working immediately, which is the point.
Turning Public embedding off revokes the whole thing. See Dashboards for the sharing controls.