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:

  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:

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:

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

Source

The behavior above is defined in: