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. For the
window.open() (new browser tab) variant and for sharing / appStateHash
details, see Deep linking to the Perfetto UI.
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:
- The host repeatedly posts the string
'PING'to the UI window. - The UI replies with the string
'PONG'. The reply is sent to'*'and is sent only once the UI's message listener is registered anddocument.readyState === 'complete'. - The host listens for
'message'events; on the firstdata === '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:
iframe.contentWindow.postMessage({perfetto: {buffer, title}}, '*');Fields of the perfetto object:
| Field | Type | Required | Default | Meaning |
|---|---|---|---|---|
buffer |
ArrayBuffer |
Yes | - | Raw trace bytes, e.g. from fetch(...).then(r => r.arrayBuffer()). |
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. |
appStateHash |
string |
No | - | 40-char hex hash; restores saved UI state from GCS. See Deep linking to the Perfetto UI. |
localOnly |
boolean |
No | true |
Defaults to true for posted traces, which disables download and share. Set false to enable them. |
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(). |
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:
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. |
pid |
<n> |
Process id used to disambiguate the slice selection. |
tid |
<n> |
Thread id used to disambiguate the slice selection. |
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 (plus optional pid/tid), 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?
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 overhttp(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.devfollows 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: