Chrome extension developers: If you’re adding ABP to a Chrome extension, see the Chrome Extension Guide. That guide requires reading Sections 1, 7, and 8 of this document first (core principles), then covers the extension-specific mechanics.
1. The Critical Rule
Every capability call MUST produce a complete, usable result for a program controlling the browser, with no human present.The consumer of your capabilities is a program — an AI agent or automated client — not a person sitting in front of a browser. When an agent calls
export.pdf, it expects a PDF. It cannot:
- Click buttons in a print dialog
- Dismiss alert boxes
- Interact with permission prompts
- Find files in a download bar
The Headless Test
Before shipping any capability, ask:“Would this produce a complete, usable result for a program controlling the browser, with no human present?”This accounts for the transport layer’s capabilities:
window.print()+ status message"Print dialog opened"— Fails (expects human to use print dialog)window.print()as transport signal (with content prepared in print container) — Passes (bridge intercepts, generates PDF)alert("Export complete!")— Fails (page hangs waiting for human to click OK)- Return
{ success: true, data: { ... } }— Passes (agent receives data directly) <a download>.click()— Fails (browser download bar, agent can’t access file)- Return file as BinaryData in response — Passes (agent receives file data inline)
The Transport Layer as Collaborator
The “fully programmatic” rule does not mean every capability must accomplish everything within the page’s JavaScript alone. ABP apps run inside a browser controlled by a transport layer — typically Puppeteer or Playwright via an ABP client. That transport layer has capabilities of its own:
The correct mental model: the app produces content; the agent handles delivery.
This is the same pattern across all delivery mechanisms. The app returns content (HTML, text, data), and the agent uses the appropriate tool for delivery —
pbcopy for clipboard, a client-provided tool for PDF, file write for downloads.
Why This Matters: A Real Failure
A web application had a “Save as PDF” feature. The existing feature worked by:- Extracting the relevant content from the page
- Opening a new window with styled HTML
- Calling
printWindow.print()on that isolated window
window.print() on the main page, capturing the full app UI (toolbar, sidebar, navigation, content panes) instead of the isolated content. The agent’s export.pdf capability returned:
- The agent never analyzed how the existing PDF feature worked — it didn’t extract the content-isolation logic
- The response contained zero bytes of PDF — just a status message
The Delivery vs. Content Production Principle
Browsers have many features designed to deliver content to humans: print dialogs, download bars, the clipboard, and share sheets. These are delivery mechanisms. An ABP capability must never use a delivery mechanism as its output path. Produce the content; return it to the agent; let the agent or host handle delivery.Every delivery mechanism involves two phases:
- Content production — generating the data (rendering HTML, converting formats, assembling a file)
- Delivery — routing the data to a destination (print dialog, download bar, clipboard, share sheet)
How to Recognize a Delivery Mechanism
The table above is not a closed list. New browser APIs appear regularly, and any of them could be a delivery mechanism in disguise. Apply this three-question test to any browser API you plan to use inside a capability handler:- Does the API route content to a destination outside the page? Clipboard, share sheets, download bars, and notifications all move data out of the page and into an OS-level surface. If yes, the agent — not the page — should control where that content goes.
- Does the API open a native OS dialog the page can’t fully control? Print dialogs, file pickers, permission prompts, and share sheets all produce UI that JavaScript cannot dismiss or interact with. If yes, an automated caller will hang or fail silently.
- Would the agent normally decide where this content goes? Agents choose file paths, clipboard targets, notification channels, and share destinations. If the API makes that choice for them (or forces a human to make it), it’s a delivery mechanism.
The Input-Side Mirror
The delivery-vs-content-production principle has a mirror on the input side. Browsers have APIs that acquire data from the user — file pickers (<input type="file">, showOpenFilePicker()), camera/microphone prompts (getUserMedia()), and drag-and-drop — all of which assume a human is present to select a file, grant a permission, or drag an item. An ABP capability that relies on these for input will hang or fail when called by an agent.
The fix mirrors the output side: accept input data as parameters. Instead of opening a file picker, accept the file content (or a URL) as a parameter. Instead of prompting for camera access to capture a photo, accept image data as a parameter. The one exception is capabilities that genuinely need live hardware access — camera, microphone, sensors — where the data cannot be supplied in advance. For those, declare the requirement in the manifest (see Permission-Gated Capabilities) and handle denial gracefully with a PERMISSION_DENIED error code.
The Self-Containment Principle
Every capability must be a self-contained, stateless operation. It receives input parameters, does its work, and produces output — all in a single call. It must NEVER depend on the agent calling other capabilities first to “set up” the right state.When a human uses a web app, the workflow is stateful:
- Enter or load data into the app
- App processes and displays the result
- Click “Export” or “Save as PDF”
state.setContent -> export.pdf. This is wrong. If export.pdf accepts a content parameter, it must handle everything internally:
- Agents may call capabilities in any order
- An agent calling
export.pdfwith content should get a PDF of that content, regardless of what the app is currently displaying - If capabilities depend on each other, the agent must understand implicit state — and that breaks when the page reloads, when multiple agents connect, or when calls are made in unexpected order
A Real-World Failure From Missing Self-Containment
An invoicing application exposed these capabilities:state.loadInvoice— Load an invoice into the editorui.switchView— Switch between dashboard and editor viewsexport.pdf— Export as PDF
- Called
state.loadInvoicewith invoice data -> app displayed the invoice in its editor - Called
export.pdfwith the same data -> PDF contained the entire app UI (dashboard sidebar, toolbar, editor pane)
export.pdf handler called window.print() on the current page instead of rendering the invoice data into an isolated print container. The ~316KB PDF was a screenshot of the web app interface, not a clean invoice document.
Had export.pdf been self-contained — taking the invoice data as a parameter, rendering it into a print container, and calling window.print() — the agent would have received a clean PDF regardless of whether state.loadInvoice was called first.
2. Step 1 — Inventory Your Features
Before writing any ABP code, create a feature inventory.For AI agents implementing ABP: This step means reading the app’s source code. Open the files. Find the functions behind each button. Trace the code path from click handler to output. Do NOT write ABP capability handlers from your imagination or from general knowledge of how such features “typically” work — your handlers must replicate what the app actually does.
- What it does (user-facing description)
- How it works technically — Read the source code. Find the function. What does it call? What DOM elements does it create or modify? What browser APIs does it use?
- What the output is (text, file, side effect, UI change)
Feature Inventory Template
Here’s what a completed inventory looks like. Note how the “Technical Implementation” column captures the actual code path — this is what your ABP handlers must replicate. Example: Invoice Generator appWhy This Step Matters
The technical implementation column is where most failures originate. If you don’t understand how “Save as PDF” works in your app, you’ll implement the ABP capability incorrectly. The #1 mistake AI agents make: They see a feature name like “Save as PDF” or “Export Image”, assume they know how it works, and write an ABP handler from scratch. But the app’s actual feature might isolate content into a new window, apply custom styles, use a specific library, or process data through a rendering pipeline. The agent’s handler skips all of this and produces wrong output. Read the code first. Always. How to analyze each feature:- Find the button or UI trigger for the feature
- Read its click handler or event listener
- Follow the function calls — what does it invoke?
- Note what DOM manipulation it does (creates elements, opens windows, modifies containers)
- Note what browser APIs it calls (
window.print(),canvas.toDataURL(),document.createElement('a'),fetch(), etc.) - Note what libraries it uses (
jsPDF,html2canvas,Chart.js,marked,Prism,SheetJS, etc.)
- Does the feature open a new window or iframe? -> You need to extract that content-preparation logic
- Does it call
window.print()? -> On what element/page? The main page or isolated content? - Does it trigger a download? -> You need to return the data in the ABP response instead
- Does it use
alert()/confirm()? -> You need ABP elicitation instead - Does it use a library? -> Your ABP handler should use the same library
- Does it use
navigator.clipboardornavigator.share()? -> These are delivery mechanisms. Expose the content-producing step as the ABP capability and skip the delivery step
3. Step 2 — Map Features to ABP Capabilities
Standard Namespaces
ABP uses dot-notation namespaces. Use these standard namespaces when your feature fits:
Use
camelCase for multi-word names: convert.markdownToHtml, not convert.markdown-to-html.
For vendor-specific features, use reverse-domain notation: com.mycompany.customFeature.
What NOT to Expose as Capabilities
Not every app feature should become an ABP capability. The purpose of ABP is to give agents access to data operations (convert, export, read, write) — not to let agents drive your UI. Do NOT expose:
The test: For each candidate capability, ask: “Can the agent accomplish its task without this capability, by passing the right parameters to data-producing capabilities?” If yes, don’t expose it.
Valid
state.* capabilities: Reading app state can be legitimate when the agent needs to discover what’s available (e.g., checking what documents are loaded, what configuration is active). But writing state (state.set*) is almost always a smell — it means your data capabilities aren’t self-contained.
5-Step Capability Mapping Process
For each feature in your inventory: Step A — Name it. Choose a capability name from the standard namespaces above. Examples: “Export as PDF” ->export.pdf. “Convert CSV to JSON” -> convert.csvToJson. “Generate thumbnail” -> generate.thumbnail.
Step B — Define inputs. What parameters does the agent need to provide? Look at what your existing feature’s code takes as input. If your export function is generateReport(data, options), your inputs are data (object, required) and options (object, optional).
Step C — Define outputs. What should the agent receive back? This must be actual data, not a status message. If the capability is convert.csvToJson, the output is the JSON data. If it’s convert.markdownToHtml, the output is { html }. For PDF, prefer returning HTML content and letting the agent or client handle PDF generation — see Section 6.
Step D — Identify the code path. Open the source files. Read the actual function that implements this feature. Trace through it: what functions does it call? What DOM elements does it modify? What browser APIs does it use? Your ABP handler must replicate this code path, not invent a new one. If the app’s export function calls renderStyledContent() to prepare output, your ABP handler must call renderStyledContent() too — not re-implement the rendering from scratch.
Step E — Gap analysis. Check for mismatches:
- Does the existing feature rely on UI that an agent can’t interact with? (print dialogs, alerts, downloads) -> Needs adaptation
- Does the existing feature modify the visible page? -> Your ABP handler should work on an isolated container
- Does the existing feature use browser APIs that need permissions? -> Declare requirements in the manifest
- Does the existing feature end with a delivery step (clipboard copy, download, share)? -> Strip the delivery step; your ABP handler returns the content, the agent handles routing
- Two clipboard features that copy different representations of the same data (e.g., full HTML document vs. email-ready fragment)
- Two export features that share a file format but differ in content (e.g., styled document vs. raw data export)
- Two download features that produce the same file type with different content or formatting
Worked Example
App: Invoice Generator with features from the inventory above.Convergence Example
The same Invoice Generator has two clipboard features:
Both features use clipboard (a delivery mechanism to strip), and both output “styled HTML.” Without a convergence check, they collapse into one capability — and one code path gets lost:
After convergence check (Step F): Compare
createFullInvoiceDocument() and createEmailFragment() — different functions, different output structures. The capability needs a format parameter:
Alternatively, these could be separate capabilities (
render.invoiceDocument and render.invoiceEmail). Either approach works — the key is that both code paths remain reachable.
4. Step 3 — Implement the ABP Interface
Implementation has three parts: manifest link, manifest file, andwindow.abp runtime.
Framework Environments (React, Next.js, Vue, Angular, Svelte, etc.)
Framework Environments (React, Next.js, Vue, Angular, Svelte, etc.)
The instructions below show vanilla HTML for clarity, but most production web apps use a framework. Both the manifest Guard with Then in your async module (the real runtime), after replacing Next.js App Router example — inject the bootstrap in a server component:Verification tip: After building your app, inspect the HTML output. If the script setting
<link> tag and the window.abp object must be available in the initial page load — before any framework hydration or lifecycle hooks execute. If either is missing at that point, the ABP client will fail to connect.Why lifecycle hooks are wrong for ABP setup:- ABP clients discover the manifest by fetching your page’s raw HTML (no JavaScript execution). A
<link>tag injected viauseEffect,onMounted, or similar hooks will never appear in that HTML. - ABP clients check for
window.abpafter page load. Lifecycle hooks run after framework hydration, which may be too late depending on the client implementation.
window.abp — assign at module scope, not inside lifecycle hooks:typeof window !== 'undefined' for SSR safety. You can still use a lifecycle hook for cleanup (removing window.abp on unmount during SPA navigation), but the initial assignment must happen at module scope.The Bootstrap + Upgrade pattern: Place a synchronous inline <script> (no async/defer) that creates a placeholder window.abp with identity properties and Promise-based proxy methods. When the real runtime loads in an async chunk, it replaces window.abp and resolves the proxy queue.window.abp:window.abp has async or defer, you need the bootstrap pattern.Part A: Manifest Link
Add this to your HTML<head>:
- Use
rel="abp-manifest"exactly (case-sensitive) hrefcan be relative (/abp.json) or absolute- Place it in
<head>, before or after other<link>tags
Part B: Manifest File
Createabp.json (or wherever your href points):
Part C: window.abp Runtime
Implement the window.abp object with the required methods: initialize(), shutdown(), call(), and listCapabilities().
The object has two parts — identity properties (synchronous, always present) and async methods (session lifecycle and capability invocation):
Capability Handler Patterns
Text Conversion (convert.*)
Return the converted content directly in the response:
File Export (export.* — non-PDF)
Return the file data as BinaryData in the response:
Image Processing (process.image)
Process an image using browser Canvas APIs and return the result:
PDF Export — See Section 6 below
The recommended pattern is simple: the app returns HTML content (via aconvert.* or render.* capability), and the agent or client generates a PDF from it. No PDF-specific logic is needed in the app. Section 6 covers this and more advanced patterns.
5. Step 4 — Review Against the Inventory
After implementing your ABP interface (Steps 1-3), step back and review the implementation against your original feature inventory. This is a semantic review — not a structural check (that’s the Validation Checklist), but a verification that your implementation faithfully represents everything your app can do. Steps 1-3 build the implementation going forward: inventory -> map -> implement. This step goes backward: from the finished implementation to the inventory, checking for anything lost along the way.Coverage
Walk your inventory row by row. For each feature, write down:- Which ABP capability produces this feature’s output
- What parameters you’d pass to reproduce it
Output Fidelity
For each capability, compare its actual output against the original feature’s output:- Same structure? If the original feature produces a full
<!DOCTYPE>document with embedded CSS, copy buttons, and a<script>tag, the capability must produce exactly that — not a stripped-down version. - Same code path? Does the ABP handler call the same underlying functions as the original feature?
- Same options? If the original feature has variants (e.g., light/dark theme, full/compact layout), can the capability produce all of them?
Convergence Verification
Where multiple inventory rows point to the same capability, call it once for each row (with the appropriate parameters) and compare:- Do the outputs match what each original feature produces?
- Are the differences between features preserved through the capability’s parameters?
- Or did distinct content-production paths collapse into one?
The Review Test
For each feature-to-capability pairing, this question should have a clear answer:“If an agent calls capability X with parameters Y, does it get the same content that a user gets when they click the corresponding feature button?”If the answer is “mostly” or “sort of,” something was lost. Go back to the inventory, compare the code paths, and fix the capability.
6. The PDF/Print Pattern
PDF is a common example of the delivery-vs-content-production principle from Section 1. The same principle applies to all delivery mechanisms — clipboard, downloads, share sheets — but PDF is covered separately because apps have multiple valid approaches.The Simple Pattern (Recommended)
The app exposes a content-producing capability that returns HTML (or other renderable content). The agent or client generates a PDF from that content using whatever tool is available. No PDF-specific logic is needed in the app.window.print(), no #print-container, no @media print CSS, no transport-layer coupling. If your app already has a content-producing capability, you probably don’t need a separate export.pdf capability at all.
Advanced: The window.print() Pattern
When the app needs its own CSS context for rendering (custom @media print styles, font preloading, complex page layout), it can use window.print() as a transport signal. Puppeteer-based clients can intercept this and generate a PDF via page.pdf().
@media print CSS rules to isolate the print container from the rest of the page UI. See Common Pitfalls for detailed examples.
Fallback: Server-Side or In-Browser Generation
If your app must work across all transports (including WebSocket and postMessage where there is no Puppeteer), generate the PDF server-side or with a JS library and return it asBinaryData:
Adapting an Existing Print Feature
If your app already has a “Save as PDF” feature, extract the content-preparation logic and expose it as a content-producing capability:Which Approach to Choose
7. Forbidden Patterns
Many of these forbidden patterns are delivery mechanisms — browser features that route content to humans. The delivery-vs-content-production principle (Section 1) explains why they fail: ABP capabilities must produce content, not deliver it. The remaining patterns involve blocking UI (dialogs, prompts) that agents cannot interact with. Do NOT use these browser APIs inside capability handlers.Elicitation Example (replacing confirm)
Programmatic Downloads (full example)
This is a common pattern in web apps. The entire Blob/objectURL/anchor approach must be replaced:Delivery Mechanisms: Clipboard and Share
Clipboard and share are delivery mechanisms — they route content to a destination that the agent should control. The fix is the same in both cases: produce the content, return it in the response, and let the agent handle routing. Clipboard — wrong:Permission-Gated Capabilities (declaring requirements)
If your capability uses a browser API that may trigger a permission prompt, you must:- Declare the requirement in your manifest capability
- Handle denial gracefully with a
PERMISSION_DENIEDerror code
PERMISSION_DENIED by checking the requirement’s resolution field and taking appropriate action.
8. Response Patterns
BinaryData Format
When returning binary or file content, use this structure:Expected Output by Capability Pattern
The Consistency Rule
All capabilities in the same namespace MUST produce consistent results from the agent’s perspective. Ifexport.html returns data.document with content, mimeType, and filename, then other export.* capabilities should also deliver files in the same shape.
For PDF, prefer returning HTML via a
convert.* capability and letting the agent or client generate the PDF, rather than creating an export.pdf capability. This keeps the app simple and follows the delivery-vs-content-production principle.Error Response Format
When a capability fails,call() returns { success: false, error }. The error object has this shape:
Standard Error Codes
“Maybe” means the handler should decide based on context — for example,
OPERATION_FAILED from a transient network error is retryable, but from a logic error it is not.
You may define app-specific error codes (e.g., CONVERSION_ERROR, RATE_LIMITED) as long as they follow the same { code, message, retryable } shape. Agents that don’t recognize a custom code will fall back to the retryable flag.
9. Validation Checklist
Run through this checklist before shipping your ABP implementation.Discovery
- HTML contains
<link rel="abp-manifest" href="...">in<head>(must be in server-rendered HTML — see Framework Environments) - Manifest URL is accessible (not 404)
- Manifest is valid JSON
- Manifest has required fields:
abp,app.id,app.name,app.version,capabilities - Each capability has a
namefield - In the built output HTML,
window.abpis set by a synchronous<script>(noasync/defer), or a synchronous bootstrap creates it before async chunks load
Runtime
-
window.abpis defined when the page loads (must be assigned at module scope, not in lifecycle hooks — see Framework Environments) -
window.abp.initialize()returnssessionId,protocolVersion,app,capabilities,features -
window.abp.call()routes to the correct handler for each capability -
window.abp.call()returns{ success: false, error: { code: 'NOT_INITIALIZED' } }if called beforeinitialize() -
window.abp.call()returns{ success: false, error: { code: 'UNKNOWN_CAPABILITY' } }for unknown capabilities -
window.abp.listCapabilities()returns an array of capabilities with at leastnameandavailablefields -
window.abp.shutdown()resets session state
Headless Test (per capability)
For each capability, verify:- The capability produces a complete result with no human present
- No
alert(),confirm(),prompt()calls - No
window.open()calls - No programmatic downloads (
<a download>, blob URL navigation) - No clipboard writes (
navigator.clipboard.*) — return content instead - No native share invocations (
navigator.share()) — return shareable data instead - No reliance on native permission prompts without declared requirements
Data Integrity
-
export.*capabilities deliver actual files (BinaryData or transport-captured), not status messages -
convert.*capabilities return the converted content, not “conversion complete” messages -
generate.*capabilities return the generated content, not “generation started” messages - Binary content has correct
mimeTypeandencodingfields - The
sizefield (when provided) matches the actual content size
Self-Containment
- Each capability operates on its input parameters, not on accumulated page/UI state
- No capability requires a previous capability call to “set up” state (e.g., no need to call
state.setContentbeforeexport.pdf) -
export.*handlers render the content parameter into a print container — not whatever the page is currently showing - No
ui.*orstate.set*capabilities that duplicate parameters already available on data-producing capabilities
Consistency
- All
export.*capabilities return files in the same response shape - All
convert.*capabilities return content in the same response shape - Error responses use consistent error codes (
INVALID_PARAMS,PERMISSION_DENIED,OPERATION_FAILED, etc.)
10. Quick Reference Example
A complete minimal ABP implementation showing all three files (HTML, manifest, runtime). This example uses a Markdown Converter, but the same structure applies to any web app — replace the capability names, input schemas, and handler logic with your app’s features. Notice that the app has no PDF-specific code. The agent gets HTML fromconvert.markdownToHtml, then uses a client-provided tool to produce a PDF when needed — the same way it would use pbcopy for clipboard.
index.html
abp.json
abp-runtime.js
convert.markdownToHtml to get HTML, then uses a client-side tool to generate a PDF from that HTML. The app never needs to know about PDF — it just produces content.