| # Perfetto UI embedding API reference | |
| This page is a reference for the `postMessage` and URL parameter surface used to | |
| embed the Perfetto UI (`ui.perfetto.dev`) inside an `<iframe>` on a host page. | |
| For a task-oriented walkthrough of the embedding flow, see | |
| [Embedding the Perfetto UI](/docs/visualization/embedding-the-ui.md). For the | |
| `window.open()` (new browser tab) variant and for sharing / `appStateHash` | |
| details, see [Deep linking to the Perfetto UI](/docs/visualization/deep-linking-to-perfetto-ui.md). | |
| NOTE: This is a reference, not a tutorial. Fields and message types not listed | |
| here are not part of the supported surface. | |
| ## Message channel | |
| The host page communicates with the embedded UI via `window.postMessage`. The | |
| UI's message handler only acts on messages whose `event.source` is one of: | |
| | `event.source` | When | | |
| | ------------------------- | ---------------------------------------------------------------- | | |
| | `window.parent` | The UI runs inside the host's `<iframe>` (the embedding case). | | |
| | `window.opener` | The host launched the UI with `window.open()` (new-tab case). | | |
| | A window this UI opened | `event.source.opener === window`. | | |
| For iframe embedding, the UI's `window.parent` is the host page, so the host | |
| posts messages to `iframe.contentWindow` and the UI accepts them. | |
| Because the channel is not buffered, a handshake is required before posting a | |
| trace: | |
| 1. The host repeatedly posts the string `'PING'` to the UI window. | |
| 2. The UI replies with the string `'PONG'`. The reply is sent to `'*'` and is | |
| sent only once the UI's message listener is registered **and** | |
| `document.readyState === 'complete'`. | |
| 3. The host listens for `'message'` events; on the first `data === 'PONG'` from | |
| the UI window it stops pinging and posts the trace. | |
| A robust host pings on an interval (for example every 50-250ms) and clears the | |
| interval on the first `PONG`. | |
| A message with `{perfettoIgnore: true}` is ignored on purpose. This lets a host | |
| multiplex other traffic over the same channel. | |
| ## Opening a trace | |
| To open a trace, post an object with a single `perfetto` key. Pass exactly | |
| one of `buffer` or `stream`: | |
| ```js | |
| iframe.contentWindow.postMessage({perfetto: {buffer, title}}, '*'); | |
| ``` | |
| A `ReadableStream` must be transferred. Backpressure propagates from trace | |
| parsing to the stream producer, bounding how far the producer can run ahead: | |
| ```js | |
| iframe.contentWindow.postMessage( | |
| {perfetto: {stream, title, bytesTotal}}, '*', [stream]); | |
| ``` | |
| Fields of the `perfetto` object: | |
| | Field | Type | Required | Default | Meaning | | |
| | -------------- | -------------------------------------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- | | |
| | `buffer` | `ArrayBuffer` | One of `buffer` or `stream` | - | Raw trace bytes, e.g. from `fetch(...).then(r => r.arrayBuffer())`. | | |
| | `stream` | `ReadableStream<ArrayBuffer \| ArrayBufferView>` | One of `buffer` or `stream` | - | Transferred stream of raw trace chunks. | | |
| | `bytesTotal` | `number` | No | `0` | Total stream size for progress reporting; `0` means unknown. | | |
| | `title` | `string` | Yes | - | Trace title shown in the UI. | | |
| | `fileName` | `string` | No | - | Suggested file name if the user downloads the trace. | | |
| | `url` | `string` | No | - | Sharing URL. See sharing details in [Deep linking to the Perfetto UI](/docs/visualization/deep-linking-to-perfetto-ui.md). | | |
| | `appStateHash` | `string` | No | - | 40-char hex hash; restores saved UI state from GCS. See [Deep linking to the Perfetto UI](/docs/visualization/deep-linking-to-perfetto-ui.md). | | |
| | `shareable` | `boolean` | No | `false` | If `true`, the UI may share the trace (e.g. upload it for a permalink). | | |
| | `downloadable` | `boolean` | No | `false` | If `true`, the user may download the trace. | | |
| | `localOnly` | `boolean` | No | `true` | Legacy. `false` sets both `shareable` and `downloadable` to `true`. Explicit `shareable`/`downloadable` take precedence. | | |
| | `keepApiOpen` | `boolean` | No | `false` | If `true`, the listener stays active so the host can post more traces later. If `false`/omitted, the handler removes its own message listener after the first trace (avoids duplicate posts, b/182502595). | | |
| | `pluginArgs` | `{[pluginId: string]: {[key: string]: unknown}}` | No | - | Passed to plugins' `onTraceLoad()`. | | |
| Streamed traces are not retained after parsing, so they cannot be downloaded, | |
| shared, or cached. `fileName`, `url`, `shareable`, `downloadable`, `localOnly`, | |
| and `pluginArgs` therefore do not apply to streams. | |
| ### Bare ArrayBuffer shorthand | |
| A bare `ArrayBuffer` (`event.data instanceof ArrayBuffer`) is also accepted. It | |
| is treated as `{title: 'External trace', buffer}`. | |
| ## Scroll to time range | |
| Post the following message after the trace is loaded to scroll/zoom the viewport | |
| to a time range: | |
| ```js | |
| iframe.contentWindow.postMessage( | |
| {perfetto: {timeStart, timeEnd, viewPercentage}}, '*'); | |
| ``` | |
| | Field | Type | Required | Meaning | | |
| | ---------------- | -------- | -------- | ------------------------------------------------ | | |
| | `timeStart` | `number` | Yes | Range start, **absolute trace time in seconds** (not relative to trace start; clamped to trace bounds). | | |
| | `timeEnd` | `number` | Yes | Range end, **absolute trace time in seconds**. | | |
| | `viewPercentage` | `number` | No | Fraction of the viewport the range should fill, in `(0.0, 1.0]`. Out-of-range values are ignored and replaced by `0.5`. | | |
| The handler retries internally (roughly 20 times at 200ms intervals) until the | |
| trace is ready, so this message can be posted shortly after the trace without | |
| waiting for an explicit "loaded" signal. | |
| ## String commands | |
| The handler understands these string messages: | |
| | Message | Effect | | |
| | ----------------------- | --------------------------------------- | | |
| | `'PING'` | Replies with `'PONG'` (sent to `'*'`). | | |
| | `'SHOW-HELP'` | Opens the help dialog. | | |
| | `'RELOAD-CSS-CONSTANTS'`| Reloads CSS constants. | | |
| ## URL parameters | |
| Set these on the iframe `src`. The route is hash-based: | |
| `https://ui.perfetto.dev/#!/?key=val&...`. | |
| | Parameter | Value | Effect | | |
| | ----------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | | |
| | `mode` | `embedded` | Enables embedded mode: the sidebar is **disabled entirely** (not just hidden) and the file-drop handler is not installed. Use this when embedding. | | |
| | `hideSidebar` | `true` | Hides the sidebar visually without fully disabling it. | | |
| | `url` | `<https url>` | UI fetches a public trace itself (requires CORS allowing the UI origin). Alternative to `postMessage` for public traces. | | |
| | `s` | `<hash>` | Loads a permalink (saved state). | | |
| | `visStart` | `<ns>` | Initial viewport start, raw **nanosecond** timestamp (as in SQL tables). Pair with `visEnd`. | | |
| | `visEnd` | `<ns>` | Initial viewport end, raw **nanosecond** timestamp. | | |
| | `ts` | `<ns>` | Timestamp of the slice to select on load, in **nanoseconds**. Linking is by `ts`+`dur`, **not** by `id` (ids are unstable). | | |
| | `dur` | `<ns>` | Duration of the slice to select on load, in **nanoseconds**. | | |
| | `query` | `<sql>` | Runs a SQL query on load (URL-encode the value). | | |
| | `startupCommands` | `<url-encoded JSON array>` | Runs UI commands after load, e.g. `[{id:'dev.perfetto.PinTracksByRegex', args:['.*CPU [0-3].*']}]`. | | |
| | `enablePlugins` | `<comma,list>` | Enables specific plugins by id. | | |
| NOTE: `visStart`/`visEnd` and `ts`/`dur` are raw **nanoseconds**, whereas the | |
| `timeStart`/`timeEnd` `postMessage` fields are **seconds**. | |
| NOTE: Slice selection is by `ts`+`dur`, never by `id`, because ids are | |
| unstable across runs. | |
| ## Origin trust | |
| If the posting origin is trusted, the trace opens immediately. The trusted set | |
| is: | |
| - Same-origin requests. | |
| - `localhost`, `127.0.0.1`, and `[::1]` (so local dev embedding works with no | |
| prompt). | |
| - A few hardcoded Google origins. | |
| - Origins the user previously saved via "Always trust". | |
| If the origin is **not** trusted, the UI shows a modal: | |
| > `<origin>` is trying to open a trace file. Do you trust the origin and want to | |
| > proceed? | |
| with the options **No**, **Yes**, and **Always trust**. "Always trust" persists | |
| the origin in `localStorage`. | |
| Embedding from a production domain therefore shows users a one-time consent | |
| prompt, unless you self-host the UI (same-origin => trusted). | |
| Strings in `title` and `url` are sanitized to the character set | |
| `[A-Za-z0-9.\-_#:/?=&;%+$ ]`. | |
| ## Constraints | |
| - Does not work from `file://` URLs (browser security). Serve over `http(s)`. | |
| - The host page must **not** be served with | |
| `Cross-Origin-Opener-Policy: same-origin`, which breaks the opener | |
| relationship. | |
| - The UI is client-only. Posted traces stay in browser memory and are never | |
| uploaded. | |
| - `ui.perfetto.dev` follows the latest release. If you need a fixed version, | |
| self-host the UI build to pin it. Self-hosting also makes your origin | |
| same-origin, so the consent modal is skipped. | |
| ## Source | |
| The behavior above is defined in: | |
| - [/ui/src/frontend/post_message_handler.ts](/ui/src/frontend/post_message_handler.ts) | |
| - [/ui/src/public/route_schema.ts](/ui/src/public/route_schema.ts) |