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:

  1. Dashboard toolbar — click the Embed button next to Share.
  2. Per-chart footer — click the Code icon in any saved chart's action row to embed just that widget.
  3. 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:

PropTypeDescription
slugstringThe embed slug from the modal. Required.
themestringTheme override; passed as ?theme=<id>.
hideTitlebooleanWidget-only. Hides the chart title.
className / styleStyling hooks — apply rounded corners, min-height, etc.
onLoad / onErrorhandlersForwarded 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: false on 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)
DataFrozen at creation timeRe-executed on every view
GranularityFull dashboard onlyDashboard or single widget
PlanAny tierStarter+
AuthenticationNoneNone
Data source loadZero — reads the stored blobRuns recipes on the owner's integrations
Use casePoint-in-time reportsAlways-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).