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

FieldTypeRequiredDescription
namestringYesHuman-readable server name, shown in Settings and command palette source chips.
namespacestringYesUnique identifier in reverse-domain notation (e.g., com.acme). Used to enforce naming constraints on macros and SQL modules.
featuresarrayYesFeatures this server supports. Each entry has a name field. Valid names: macros, sql_modules, proto_descriptors.
modulesarrayYesAvailable 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

FieldTypeDescription
idstringUnique identifier. Must start with the server's namespace followed by . (e.g., com.acme.StartupAnalysis).
namestringDisplay name shown in the command palette.
runarrayCommands 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

FieldTypeDescription
namestringModule 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;.
sqlstringSQL 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

FieldTypeDescription
proto_descriptorsarray of stringsBase64-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:

  • Macro IDs like com.acme.StartupAnalysis, com.acme.MemoryCheck
  • SQL module names like com.acme.startup, com.acme.memory.helpers

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 typeHeader
noneNo auth headers
github_patAuthorization: token <pat> (via GitHub API)
https_basicAuthorization: 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_ssoNo 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:

  • Unauthenticated (public repos): Uses raw.githubusercontent.com to avoid GitHub API rate limits.
    https://raw.githubusercontent.com/{repo}/{ref}/{path}/manifest
    
  • Authenticated (private repos): Uses the GitHub Contents API.
    https://api.github.com/repos/{repo}/contents/{path}/manifest?ref={ref}
    
    With header: Accept: application/vnd.github.raw+json

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