Browse documentation
Guide

Embeds

Drop a live Hodoflow dashboard 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, 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.