GraphJSON charts can live outside the dashboard. Choose a static iframe for a fixed configuration or generate an iframe URL from your server when filters must change dynamically.
Choose an embed mode#
| Mode | Configuration | Graph data | Best for |
|---|---|---|---|
| Static iframe URL | Fixed when exported | Loads live | Public metrics, internal portals, fixed reports |
| Visualization API | Built by your server | Loads live | Per-user views and dynamic product analytics |
| Data API | Built by your server | Returned as JSON | Custom chart components |
All three use the same collection, range, aggregation, filters, and customization concepts.
Static iframe#
Configure a chart in the visualizer and select Export → As static iframe.
<iframe
src="https://graphjson.com/embed?p=..."
title="Completed checkouts over the last 30 days"
width="100%"
height="360"
loading="lazy"
style="border: 0; border-radius: 12px;"
></iframe>
The URL stores the chart configuration, not a frozen image. The iframe queries fresh data when it loads.
Use a meaningful title for accessibility and reserve a height so the page does not shift while the chart loads.
Dynamic iframe#
Generate dynamic URLs on your server:
export async function createAccountChart(accountId) {
const payload = {
api_key: process.env.GRAPHJSON_API_KEY,
collection: "product_events",
IANA_time_zone: "America/Los_Angeles",
graph_type: "Single Line",
start: "30 days ago",
end: "now",
filters: [
["event", "=", "report_exported"],
["account_id", "=", accountId]
],
metric: null,
aggregation: "Count",
compare: "30 days ago",
granularity: "Day",
customizations: {
title: "Reports exported",
showYAxis: true,
showDots: false
}
};
const response = await fetch(
"https://api.graphjson.com/api/visualize/iframe",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
}
);
const body = await response.json();
if (!response.ok) {
throw new Error(body.error || `GraphJSON returned ${response.status}`);
}
return body.url;
}
Render the returned URL in the browser:
<iframe
src={chartUrl}
title="Reports exported"
className="analytics-chart"
/>
Enforce authorization#
Derive accountId from the authenticated server session. Do not trust a browser-supplied account ID without confirming the user can access it.
Security: Construct the GraphJSON payload on your server. If you let the browser choose an arbitrary collection or filter and merely attach the API key, a user may query data outside their authorization boundary.
A safe handler:
- authenticates the application user
- resolves their allowed account
- chooses a known collection and chart definition
- adds the allowed account filter
- calls GraphJSON with the server-side key
- returns only the generated iframe URL
Cache thoughtfully#
Generating an iframe URL does not need to happen on every browser render when the configuration is unchanged. Cache by a safe key such as:
chart_definition + account_id + reporting_timezone
Do not share one user’s cached URL with another user. Remember that relative time ranges continue to move when the iframe loads, so a cached URL can still display fresh data.
Treat the URL as sensitive#
The embed URL is a bearer capability. Anyone who obtains it can load that chart.
- do not include unfiltered sensitive data
- do not log complete embed URLs in public telemetry
- avoid placing them in public source code unless the chart is intended to be public
- replace exposed URLs when the underlying view is sensitive
Use the data API for native UI#
Choose the data API when an iframe cannot match your design system, interaction, or accessibility requirements. Your server can request the same aggregate and return a reduced, authorized result to your component.
Before releasing an embed to customers, follow Production embedded analytics for tenant isolation, caching, loading states, accessibility, and operational tests.