Extension Server Protocol Reference

This page documents the HTTP protocol that extension servers must implement. Use this if you are building a custom extension server rather than using the GitHub template approach.

Endpoints

Extension servers implement these HTTP(S) endpoints:

{base_url}/manifest GET (required) {base_url}/modules/{module_id}/macros GET (optional) {base_url}/modules/{module_id}/sql_modules GET (optional) {base_url}/modules/{module_id}/proto_descriptors GET (optional)

The UI only fetches optional endpoints for features declared in the manifest.

Manifest

Endpoint: GET {base_url}/manifest

Returns server metadata, supported features, and available modules.

{ "name": "Acme Corp Extensions", "namespace": "com.acme", "features": [ {"name": "macros"}, {"name": "sql_modules"}, {"name": "proto_descriptors"} ], "modules": [ {"id": "default", "name": "Default"}, {"id": "android", "name": "Android"}, {"id": "chrome", "name": "Chrome"} ] }

Fields

Field Type Required Description
name string Yes Human-readable server name, shown in Settings and command palette source chips.
namespace string Yes Unique identifier in reverse-domain notation (e.g., com.acme). Used to enforce naming constraints on macros and SQL modules.
features array Yes Features this server supports. Each entry has a name field. Valid names: macros, sql_modules, proto_descriptors.
modules array Yes Available modules. Each entry has an id (used in URL paths and settings) and a name (human-readable display name).

For single-module servers, use [{"id": "default", "name": "Default"}].

Macros

Endpoint: GET {base_url}/modules/{module_id}/macros

Only fetched if features includes {"name": "macros"}.

{ "macros": [ { "id": "com.acme.StartupAnalysis", "name": "Startup Analysis", "run": [ {"id": "dev.perfetto.RunQuery", "args": ["SELECT 1"]}, {"id": "dev.perfetto.PinTracksByRegex", "args": [".*CPU.*"]} ] } ] }

Macro fields

Field Type Description
id string Unique identifier. Must start with the server's namespace followed by . (e.g., com.acme.StartupAnalysis).
name string Display name shown in the command palette.
run array Commands to execute in sequence. Each entry has a command id and an args array.

See the Commands Automation Reference for the full list of available command IDs and their arguments.

SQL modules

Endpoint: GET {base_url}/modules/{module_id}/sql_modules

Only fetched if features includes {"name": "sql_modules"}.

{ "sql_modules": [ { "name": "com.acme.startup", "sql": "CREATE PERFETTO TABLE _startup_events AS SELECT ts, dur, name FROM slice WHERE name GLOB 'startup*';" }, { "name": "com.acme.memory", "sql": "CREATE PERFETTO FUNCTION com_acme_rss_mb(upid INT) RETURNS FLOAT AS SELECT CAST(value AS FLOAT) / 1048576 FROM counter WHERE track_id IN (SELECT id FROM process_counter_track WHERE upid = $upid AND name = 'mem.rss') ORDER BY ts DESC LIMIT 1;" } ] }

SQL module fields

Field Type Description
name string Module name. Must start with the server's namespace followed by . (e.g., com.acme.startup). Users reference this with INCLUDE PERFETTO MODULE com.acme.startup;.
sql string SQL text. Can contain CREATE PERFETTO TABLE, CREATE PERFETTO FUNCTION, CREATE PERFETTO VIEW, or any valid PerfettoSQL.

Proto descriptors

Endpoint: GET {base_url}/modules/{module_id}/proto_descriptors

Only fetched if features includes {"name": "proto_descriptors"}.

{ "proto_descriptors": [ "CgdteV9wcm90bxIHbXlwcm90byI...", "Cghhbm90aGVyEghhbm90aGVyIi..." ] }

Proto descriptor fields

Field Type Description
proto_descriptors array of strings Base64-encoded FileDescriptorSet protocol buffer messages. These allow the UI to decode custom protobuf messages in traces.

Proto descriptors are not subject to namespace enforcement since protobuf messages have their own package-based namespacing.

Namespace enforcement

All macro IDs and SQL module names must start with the server's namespace value from the manifest, followed by a .. For example, a server with namespace com.acme can only serve:

The UI validates this and rejects extensions that violate the convention. This prevents naming conflicts when users configure multiple extension servers.

CORS requirements

All HTTPS extension servers must set CORS headers to allow the Perfetto UI to make cross-origin requests:

Access-Control-Allow-Origin: https://ui.perfetto.dev Access-Control-Allow-Methods: GET Access-Control-Allow-Headers: Authorization, Content-Type

If your server supports multiple Perfetto UI deployments, you can reflect the Origin request header instead of hardcoding a single origin.

GitHub-hosted servers do not need CORS configuration — raw.githubusercontent.com and the GitHub API already set appropriate headers.

CORS failures appear as network errors in the browser console. The affected server is skipped and other servers continue to load normally.

Authentication headers

The Perfetto UI constructs authentication headers based on the server's configured auth type:

Auth type Header
none No auth headers
github_pat Authorization: token <pat> (via GitHub API)
https_basic Authorization: Basic <base64(username:password)>
https_apikey (bearer) Authorization: Bearer <key>
https_apikey (x_api_key) X-API-Key: <key>
https_apikey (custom) <custom_header_name>: <key>
https_sso No header; request sent with credentials: 'include'

For SSO authentication, if a request returns HTTP 403, the UI loads the server's base URL in a hidden iframe to refresh the SSO session cookie, then retries the request once.

GitHub server URL construction

For GitHub-hosted extension servers, the UI constructs fetch URLs automatically:

Example: minimal static server

A complete extension server can be a set of static JSON files:

my-extensions/ manifest modules/ default/ macros sql_modules

Serve these with any static file server (nginx, Caddy, GCS, S3) that sets the required CORS headers. No dynamic server logic is needed for basic use cases.

Example: dynamic server (Python/Flask)

from flask import Flask, jsonify app = Flask(__name__) @app.route('/manifest') def manifest(): return jsonify({ "name": "My Extensions", "namespace": "com.example", "features": [{"name": "macros"}, {"name": "sql_modules"}], "modules": [{"id": "default", "name": "Default"}], }) @app.route('/modules/<module>/macros') def macros(module): return jsonify({ "macros": [ { "id": "com.example.ShowLongSlices", "name": "Show Long Slices", "run": [ { "id": "dev.perfetto.RunQueryAndShowTab", "args": ["SELECT * FROM slice ORDER BY dur DESC LIMIT 20"] } ] } ] }) @app.route('/modules/<module>/sql_modules') def sql_modules(module): return jsonify({ "sql_modules": [ {"name": "com.example.helpers", "sql": "CREATE PERFETTO TABLE ...;"} ] }) @app.after_request def add_cors(response): response.headers['Access-Control-Allow-Origin'] = 'https://ui.perfetto.dev' response.headers['Access-Control-Allow-Methods'] = 'GET' response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type' return response

See also