| # 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](/docs/visualization/extension-servers.md). |
| |
| ## 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. |
| |
| ```json |
| { |
| "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"}`. |
| |
| ```json |
| { |
| "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](/docs/visualization/commands-automation-reference.md) |
| 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"}`. |
| |
| ```json |
| { |
| "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"}`. |
| |
| ```json |
| { |
| "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: |
| |
| - 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 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: |
| |
| - **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) |
| |
| ```python |
| 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 |
| |
| - [Extension Server Setup Guide](/docs/visualization/extension-servers.md) — |
| Step-by-step setup using the GitHub template |
| - [Extension Servers](/docs/visualization/extension-servers.md) — |
| What extension servers are and how they work |
| - [Commands Automation Reference](/docs/visualization/commands-automation-reference.md) — |
| Available command IDs for macros |