Write capture hooks
Run sandboxed code at lifecycle points during capture, with exactly the page operations you declare.
Most routes need no code: readiness, scrolling, and login cover the common cases. When a page needs a click to reveal its content, or a modal dismissed, a capture hook does it. Hooks run in a sandbox and can only perform the page operations their declared capabilities grant.
Declare the module and its exports
"hooks": {
"file": "linkedin-capture-hooks.js",
"exports": [
{ "name": "prepareCompanyPosts", "capabilities": ["browser.dom.read", "browser.dom.click"] }
]
}fileis the bundled module, listed in the manifest'sfiles.- Every hook a route uses must appear in
exportswith its capabilities. The list becomes the sandbox import table: an operation you did not declare does not exist inside the hook. - Publish validation checks each name is exported by the module.
Capabilities
| Capability | Page operations |
|---|---|
browser.dom.read | waitForVisible, count |
browser.dom.click | click |
browser.dom.type | type (refuses password fields and credential-looking input) |
There is no navigation, no network, and no way to read cookies. Each operation is clamped by a timeout.
Attach it to a lifecycle point
{
"id": "company-posts",
"kind": "collection",
"login": "linkedin",
"ready": { "selector": "[data-view-name=\"feed-full-update\"]" },
"on": { "afterReady": { "hook": "prepareCompanyPosts", "onError": "continue" } },
"scroll": { "kind": "finite", "maxAttempts": 2 },
"binding": "company-posts"
}Three points exist: afterLoad (before the readiness wait), afterReady (after it, before scrolling), and beforeSnapshot (after scrolling settles). onError is per attachment: continue proceeds to the snapshot, fail aborts the capture. The same hook can be load-bearing on one route and best-effort on another.
Write the hook
The module is TypeScript under authoring/src/, bundled by plugin generate. A hook is a function that receives the page API:
export async function prepareCompanyPosts(page: CaptureApi): Promise<void> {
const sortMenu = 'button[aria-label="Sort order"]';
if ((await page.count(sortMenu)) === 0) return;
await page.click(sortMenu);
await page.waitForVisible('[role="menuitem"]');
await page.click('[role="menuitem"]:nth-of-type(2)');
}Write against CaptureApi from workspace.packages.universal.lib; it is the whole vocabulary. The bundle must be self-contained: a node: import fails plugin generate. Declare stdlib on the hooks block if you need URL, Intl, or base64; see Plugin runtime environment.
Reference: captureHooks, hookExport, lifecycle, hookRef.