Live Embeds
Embeds let you drop a Greppilot dashboard or a single chart into any website, internal wiki, Notion page, or React app. Unlike public snapshots, embeds are live: every time someone views the embed, Greppilot re-executes the underlying recipe against your data source and returns fresh results.
Two forms ship today:
- Dashboard embed — the full dashboard in a 4-column grid at
/embed/dashboard/:slug. - Widget embed — a single chart scoped to one component at
/embed/widget/:slug.
Embeds are public and read-only. There's no viewer authentication; possession of the slug is the capability. Revoke or regenerate at any time.
Embedding requires the Starter plan or higher. Free accounts can't create or serve embeds — existing rows will 404 if the owner downgrades, and resume when they re-upgrade.
Creating an Embed
There are three ways to open the embed card:
- Dashboard toolbar — click the Embed button next to Share.
- Per-chart footer — click the Code icon in any saved chart's action row to embed just that widget.
- From chat — ask the agent: "embed this dashboard on our wiki" or "give me an embed link for the revenue chart."
The modal has an empty state with a single Generate embed link button on first open. After generation it shows two snippet tabs — HTML and React — plus a live preview iframe and enable / regenerate / revoke controls.
Every embed is capped at 5 per account on Starter; unlimited on Pro and Enterprise.
HTML Embed
<iframe
src="https://app.greppilot.com/embed/dashboard/abc123def4567890"
width="100%"
height="600"
frameborder="0"
></iframe>
Drop this in a Notion embed block, a Confluence "HTML macro," a WordPress post, your marketing site, a status page — anywhere <iframe> renders. The embed page posts its scroll height to the parent via postMessage so you can auto-resize the iframe if you want.
For a single widget, use /embed/widget/:slug. Widget embeds accept two query params:
?theme=<themeId>— override the saved chart theme (e.g.?theme=dark).?hideTitle=1— hide the chart title bar when the host page provides its own heading.
React Embed
Install the package:
npm install @greppilot/embed-react
Drop in the component:
import { GreppilotDashboard, GreppilotWidget } from '@greppilot/embed-react'
export function AnalyticsPage() {
return (
<div>
<GreppilotDashboard slug="abc123def4567890" />
<GreppilotWidget slug="xyz789" hideTitle />
</div>
)
}
Both components wrap an iframe and listen for the postMessage resize signal, so they auto-size to their content. Props:
| Prop | Type | Description |
|---|---|---|
slug | string | The embed slug from the modal. Required. |
theme | string | Theme override; passed as ?theme=<id>. |
hideTitle | boolean | Widget-only. Hides the chart title. |
className / style | — | Styling hooks — apply rounded corners, min-height, etc. |
onLoad / onError | handlers | Forwarded to the underlying <iframe>. |
Self-hosted Greppilot
If you're on a self-hosted Greppilot instance, wrap your tree in a provider:
import { GreppilotProvider, GreppilotDashboard } from '@greppilot/embed-react'
<GreppilotProvider baseUrl="https://greppilot.yourcompany.com">
<GreppilotDashboard slug="..." />
</GreppilotProvider>
Managing Embeds
Every embed you own shows up under Settings → Profile → Embeds. From there you can:
- Toggle Enabled to pause without destroying the slug.
- Copy the public URL directly.
- Regenerate — rotates the slug in place. The old URL 404s immediately, breaking any existing
<iframe>pointers. Use this if you suspect the slug leaked. - Revoke — hard delete. The URL stops working and cannot be recovered.
Modal and settings page share the same actions; whichever flow you prefer is fine.
Public Endpoint
For programmatic access:
GET /api/public/embeds/:slug
Returns a sanitized live payload. No auth.
Example:
curl https://app.greppilot.com/api/public/embeds/abc123def4567890
Response shape:
{
"resourceType": "dashboard",
"name": "Revenue Overview",
"theme": "default",
"showBranding": true,
"components": [
{
"id": "comp-1",
"type": "bar-chart",
"config": { "title": "Monthly Revenue", "xField": "month", "yField": "revenue" },
"position": { "x": 0, "y": 0 },
"size": { "width": 2, "height": 2 },
"data": [{ "month": "Jan", "revenue": 12000 }],
"unavailable": false
}
]
}
Always stripped from the response: query.sql, dataSources[].sql, recipe, executionHistory, repairHistory, integrationId, config.sql, config.connector. Consumers never see your SQL, pipeline internals, or integration IDs.
Per-component unavailable: true signals that the recipe couldn't run for that component (integration disabled, credentials bad, schema drift, etc.). The top-level request still returns 200 so one broken chart doesn't take down the whole dashboard.
404 conditions:
- Unknown slug.
enabled: falseon the row.- Owner has downgraded to Free.
- Widget embed whose target component has been deleted from the dashboard.
Rate Limits
The public endpoint is protected by two layers:
- Per-slug: 120 requests / minute.
- Per-IP: 60 requests / minute across all slugs.
Embeds that blow through the limit get a 429; the iframe renders a placeholder rather than a broken frame.
Branding
Every embed shows a small grepp•ilot badge in the bottom-right corner by default. Enterprise accounts can hide it per-embed via the show_branding flag on the management API (UI toggle coming soon).
Compared to Public Snapshots
Snapshots (/shared/:slug) | Live Embeds (/embed/:slug) | |
|---|---|---|
| Data | Frozen at creation time | Re-executed on every view |
| Granularity | Full dashboard only | Dashboard or single widget |
| Plan | Any tier | Starter+ |
| Authentication | None | None |
| Data source load | Zero — reads the stored blob | Runs recipes on the owner's integrations |
| Use case | Point-in-time reports | Always-fresh dashboards on external surfaces |
Use snapshots when you want to lock a report at a specific moment (a quarterly review, a security incident timeline). Use live embeds when the audience needs current numbers (marketing pages, status pages, operational dashboards in other tools).