Docs
Dashboard
Guides / SessionsBeta

Extensions

Upload a Manifest V3 Chrome extension once and load it into the sessions that need it.

Upload a Chrome extension once, then start sessions with it: it is loaded into the session’s browser before the session is handed to you. Use it for tools your pages or your agent need inside the browser. Extensions are on the Hobby plan and up.

Upload an extension

Send an unpacked Manifest V3 extension as a zip, at most 10 MB: zip the extension’s folder, with manifest.json at the root (or in the zip’s one top folder). Send it as the raw body (application/zip or application/octet-stream), or as multipart/form-data with the zip in a file field.

cd my-extension && zip -r ../my-extension.zip . && cd ..curl -X POST https://api.boxline.dev/v1/extensions \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/zip" \  -H "Idempotency-Key: $(uuidgen)" \  --data-binary @my-extension.zip
Response 201 (shape)
{  "id": "ext_Q2x9L0aPz7NmK3Jb",  "name": "My extension",  "version": "1.2.0",  "description": "…",  "permissions": ["storage"],  "hostPermissions": ["https://example.com/*"],  "sizeBytes": 48213,  "files": 12,  "sha256": "…",  "createdAt": "2026-09-30T10:00:00.000Z"}

name comes from the manifest, from _locales when it uses __MSG_…__. GET /v1/extensions lists your extensions, newest first, and GET /v1/extensions/:id returns one. An Idempotency-Key makes a retried upload safe; uploads are compared by the bytes of the zip.

What is checked

An upload is refused with 400 invalid_extension, and a message saying why, when:

  • it is not a zip (a .crx is refused too: upload the zip of the extension’s folder), or it is damaged, encrypted, larger than 32 MB unpacked, or has more than 5,000 files;
  • manifest.json is missing or not JSON, its manifest_version is not 3, or its name (1 to 75 characters) or version (1 to 4 numbers with dots) is missing or malformed;
  • it asks for a refused permission (below), or its manifest has nacl_modules or plugins (native code);
  • a file is a native program or library (ELF, Mach-O or Windows executables, .so, .dll, .dylib, .exe, .node, .nexe), a symbolic link, or has an unsafe path (absolute, .., backslashes, over 240 characters).
PermissionWhy it is refused
nativeMessagingIt starts native programs on the machine.
debuggerIt opens a DevTools channel into every tab.
proxy, vpnProviderThey take the browser off the session's own proxy, which ad blocking, session proxies and their accounting rely on.
enterprise.*Managed-device access, which a session has no use for.

These apply to permissions and optional_permissions. Other permissions, and host permissions, are allowed; the extension’s permissions and hostPermissions show what it asked for.

Use it in a session

Start a session with extensions, a list of up to 10 extension ids. The session needs a browser.

curl -X POST https://api.boxline.dev/v1/sessions \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{"extensions": ["ext_Q2x9L0aPz7NmK3Jb"]}'
  • The extension is loaded before the session is returned, usually well under a second. The lifecycle event “extensions loaded” has data: { extensions: [{ id, name, sha256 }], ms }.
  • It runs in every tab, also in new browser contexts (Playwright’s browser.newContext()).
  • After a resume, or when the platform replaces a lost machine, it is loaded again on the new machine. Its files live in the workspace under .boxline/extensions/<id>/, and are installed fresh on each machine rather than carried along.
  • Extensions are set when the session starts. Updating a running session with extensions answers 400 not_updatable. An agent run takes them in its session: session: { extensions: ["ext_…"] }.
  • An extension cannot drive the browser itself: the browser’s DevTools, which the platform, Playwright and Puppeteer use, refuse connections from pages and extensions.
StatusWhen
400 invalid_requestMore than 10, a repeated id or a malformed id, or a session without a browser.
400 not_updatableextensions in an update of a running session. They are set when it starts.
404 not_foundAn id that is not in your project.
402 feature_not_in_planYour plan does not include extensions.
403 extension_deniedThe platform does not allow this extension.

Agent variables, credentials and extensions

Because an extension sees what is typed, an agent run with variables in a session with extensions is refused with 400 variables_with_extensions. If you trust the extensions with those values, send allowWithExtensions: true: the run then starts, and the session’s log gets a lifecycle warning “agent run with variables in a session with extensions”.

curl -X POST https://api.boxline.dev/v1/agent/runs \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -H "Idempotency-Key: $(uuidgen)" \  -d '{    "task": "Sign in to example.com with %email% and %password%",    "sessionId": "'"$SESSION_ID"'",    "variables": {"email": "ada@example.com", "password": {"value": "'"$SITE_PASSWORD"'", "origins": ["https://example.com"]}},    "allowWithExtensions": true  }'

Plain-English step variables and script env values carry the same risk in a session with extensions.

So do credentials: an agent run, a step, a script or a type action with credentials in a session with extensions gets the same 400 variables_with_extensions unless it sends allowWithExtensions: true, and the credential a browser profile links is not offered there without it. Keep passwords in sessions without extensions when you can.

Delete an extension

DELETE /v1/extensions/:id removes it. Sessions already running with it keep it in their current browser; when such a session resumes or recovers, it is skipped, with a lifecycle warning “extension missing” and a data.reason. The same happens to every extension when your plan no longer includes them, and to one that no longer passes the checks.

curl -X DELETE https://api.boxline.dev/v1/extensions/$EXTENSION_ID \  -H "x-api-key: $BOXLINE_API_KEY"

Plan, limits and errors

  • Extensions need the plan feature extensions: Hobby and up, not Free. See Plans and features.
  • Up to 100 extensions per project, 10 per session, and 30 uploads an hour per project.
  • Uploads from the console must come from the platform’s own sites; API keys are not affected.

The SDKs raise InvalidExtensionError, PayloadTooLargeError, LimitReachedError, ExtensionDeniedError, CrossSiteRequestError (a console upload from another site) and VariablesWithExtensionsError for these.

StatusCodeWhen
400invalid_extensionThe zip failed a check; the message says which (see below).
402feature_not_in_planYour plan does not include extensions (Free).
403extension_deniedThe platform does not allow this extension (by its zip's hash or its name).
409limit_reachedYour project already keeps 100 extensions. Delete one first.
413payload_too_largeThe zip is larger than 10 MB.
429rate_limitedMore than 30 uploads an hour for your project.

These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.