Skip to content
Data
Skill

/querying-canvas-data

Get PostHog data into a canvas correctly: the host-injected `ph` SDK (loadInsight, query, capture, state, connectors, openExternal, navigate), the data hierarchy (saved insights first, typed query nodes second, inline HogQL last), verifiability (insight-backed metrics link their

From plugin
posthog-posthog
40k163 skills11 agents1 command3 MCP
Install
$ npx -y skills add posthog/posthog --skill querying-canvas-data --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/querying-canvas-data

Context preview

The summary Claude sees to decide when to auto-load this skill.

Get PostHog data into a canvas correctly: the host-injected `ph` SDK (loadInsight, query, capture, state, connectors, openExternal, navigate), the data hierarchy (saved insights first, typed query nodes second, inline HogQL last), verifiability (insight-backed metrics link their

SKILL.md

querying-canvas-data.SKILL.md
name: querying-canvas-data
description: >
  Get PostHog data into a canvas correctly: the host-injected `ph` SDK (loadInsight, query,
  capture, state, connectors, openExternal, navigate), the data hierarchy (saved insights first, typed query nodes
  second, inline HogQL last), verifiability (insight-backed metrics link their saved insight in
  PostHog; ad-hoc queries expose the exact query that ran), per-insight-type result shapes,
  progressive per-query loading, date-range wiring, live third-party data through the viewer's own
  connections (ph.connectors), and event capture from a canvas. Use whenever a canvas shows
  metrics, charts, tables, any PostHog data, or data from GitHub or an MCP server, or needs to send
  analytics events.

Querying canvas data

The `ph` bridge is the only way a canvas talks to PostHog. Import it with `import { ph } from "@posthog/canvas-sdk"` — a platform-provided module, so it needs no `dependencies` entry. The same object is also installed as the `window.ph` global, which existing canvases use; prefer the import in new code. Its typed surface is [references/canvas-sdk.d.ts](references/canvas-sdk.d.ts). Never initialize it: credentials stay in the host, and `fetch()`, posthog-js, and hand-rolled clients cannot reach PostHog from the sandbox. External requests and resources require a non-PostHog origin declared in `capabilities.network.origins`, and work only in the published canvas — the edit-mode preview blocks all direct network access. This includes external stylesheets; remote scripts remain blocked.

Data hierarchy — back every metric with a saved insight

1. **Preferred — save an insight, load it by reference.** Use the PostHog MCP insight tools to create/save an insight that computes the metric with an insight query type (TrendsQuery, FunnelsQuery, RetentionQuery, PathsQuery, or the web-analytics kinds WebOverviewQuery / WebStatsTableQuery — not raw SQL). Confirm its numbers, note the `short_id`, and render it with `await ph.loadInsight(shortId, { dateRange })`. These are proven queries — numbers match the PostHog UI exactly (sessionization, unique users, breakdowns, bounce rate). Never fabricate a query or guess event/property names; discover and save them via MCP first. 2. **Secondary — an ad-hoc typed node**: `ph.query({ kind: "TrendsQuery", series: [...], dateRange: {...} })` when saving an insight genuinely doesn't fit. 3. **Last resort — inline HogQL**: `ph.query("SELECT …")`, only when no insight kind can express the metric; you then own the SQL and its date window.

For web-analytics boards specifically, use the web-analytics query kinds — raw HogQL subtly gets bounce rate, sessionization, channel attribution, and unique-visitor counts wrong.

Whatever tier you use, **declare it in the project's `capabilities`** before publishing: every `ph.loadInsight` short id in `capabilities.posthog.insights`, every `ph.capture` event name in `captureEvents`, and `inlineQueries: true` for any `ph.query` use. The host rejects undeclared calls at runtime, and validation fails on undeclared literals.

Verifiability — every claim must be checkable in PostHog

A number a viewer cannot verify is a number they cannot trust. Every data-backed figure a canvas shows — a KPI, a chart, a table, a stated conclusion — must carry the verification affordance for its tier:

1. **Insight-backed metrics link the real insight in PostHog.** For a metric loaded from a saved insight (the preferred tier), render a "View in PostHog" affordance that calls `ph.openExternal(insightUrl)` from a click. Mint the URL at authoring time with the `generate-app-url` MCP tool (path template `/insights/{id}` with the insight's short id) and bake the returned URL into the source verbatim — never hand-build one. `ph.openExternal` only opens `https://*.posthog.com` URLs and only from a user gesture, so wire it to a button or link, never to load or render. Do not also bake the insight's saved query text into the source: canvas source is readable by every canvas viewer, while access to the insight itself is enforced by PostHog — the link is where a viewer inspects the query, with their own permissions applied. 2. **Ad-hoc queries disclose the exact query that ran, viewable in place.** For a figure computed by `ph.query` (a typed node or inline HogQL), show the query behind it — the HogQL text, or the typed query node pretty-printed as JSON — in a modal or a collapsed disclosure attached to the card (a Quill `Dialog` or `Collapsible` in a React canvas, a `<details>` element in an HTML one). Render it from the same constant or builder you pass to `ph.query`, so the displayed query can never drift from the executed one. This discloses nothing beyond what the viewer already runs: `ph.query` executes as the signed-in viewer.

These are not optional polish: a canvas that presents PostHog data without them is incomplete. Keep the affordances compact — a small link icon per insight-backed card, a "View query" disclosure per ad-hoc card, or one shared modal listing every ad-hoc query the canvas runs, each labeled with the figure it backs.

For a status board, set `refresh` to the cache lifetime in seconds. Use a whole number from 30 to 86400 (one day); values outside that range, or fractional ones, fail at runtime:

await ph.loadInsight(shortId, { refresh: 30 })
await ph.query(queryNode, {}, { refresh: 30 })

Result shapes — read them correctly or every value renders 0

  • **Trends-style results** (insight query types, via `ph.loadInsight` or a typed node): `results`

is an array of **series objects**, not rows. Each series has `data: number[]` (per interval), `days: string[]` (ISO), `labels: string[]`, `count` (sum), `aggregated_value` (single-value total), `label`, and optional `compare_label: "current" | "previous"`. A KPI total is `results[0].count` (or `.aggregated_value`); a line chart plots `re

Read more
Ships withposthog-posthog

:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.

Get the whole plugin

Other skills on posthog-posthog.