> ## Documentation Index
> Fetch the complete documentation index at: https://www.1password.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Fill with the browser extension

> Load the 1Password extension into your agent's Chromium browser, call its API over CDP, and fill a granted login into a tab.

export const StatusBadge = ({children}) => <span className="op-status-badge not-prose">{children}</span>;

<StatusBadge>Partner preview</StatusBadge>

Your agent calls the 1Password browser extension in its Chromium browser. The extension can create access requests, check the person's decision, and fill a granted login into a tab. To fill, it fetches the login from 1Password, fills the form, and submits it. Your code receives a status, never the values.

## Load the extension

The extension runs in Chromium builds that support extensions, in a cloud browser or on a local machine. Nobody signs in to it, and in pages that [Agentic Mode](#turn-on-agentic-mode) covers it shows no 1Password interface.

1. Load the development build of the 1Password extension that 1Password sends you, or install it with the extension installation mechanism your browser stack uses. Unzip it and load the folder unpacked.
2. Switch to **Nightly**, **Beta**, or **Stable** from the Chrome Web Store when 1Password announces it in your partner channel, and keep it up to date.

<Note>
  Branded Google Chrome builds ignore `--load-extension`. If you load the extension unpacked from the command line, use Chromium or Chrome for Testing.
</Note>

Each release channel has its own extension ID:

| Channel | Extension ID |
| - | - |
| Development | `hjlinigoblmkhjejkmbegnoaljkphmgo` |
| [Nightly](https://chromewebstore.google.com/detail/1password-nightly-%E2%80%93-passw/gejiddohjgogedgjnonbofjigllpkmbf) | `gejiddohjgogedgjnonbofjigllpkmbf` |
| [Beta](https://chromewebstore.google.com/detail/1password-beta-%E2%80%93-password/khgocmkkpikpnmmkgmdnfckapcdkgfaf) | `khgocmkkpikpnmmkgmdnfckapcdkgfaf` |
| [Stable](https://chromewebstore.google.com/detail/1password-%E2%80%93-password-mana/aeblfdkhhhdcdjpifhhbdiojplfjncoa) | `aeblfdkhhhdcdjpifhhbdiojplfjncoa` |

After you load it, check the extension ID in `chrome://extensions`, and use that ID to find the service worker. An unpacked extension whose manifest has no `key` gets an ID derived from its folder path instead.

In a cloud browser, load the extension when you create the browser, not in the middle of a task. On some providers, adding an extension restarts the browser. Run one person per browser.

## Call the extension over CDP

The extension exposes its API as `globalThis.api` on its background service worker, with a version namespace per capability: `api.initialization.v1`, `api.agenticMode.v1`, and `api.agenticAutofill.v1`. See the [API reference](/agentic-autofill/partners/extension-api). Web pages can't see it, so `page.evaluate` won't find it.

Attach to the service worker target at:

```text theme={null}
chrome-extension://<extension-id>/background/background.js
```

Then evaluate your call in that target and await the result. Before your first call, wait for `api.initialization.v1.whenSettled()` to resolve to `"ready"`, because the extension may still be starting up. Pass the token and integration key as arguments, not by building them into a script string, and never log CDP traffic, because it carries both.

<CodeGroup>
  ```javascript Playwright theme={null}
  import { chromium } from "playwright";

  const EXTENSION_PATH = "/path/to/unpacked/1password-extension";
  const EXTENSION_ID = "hjlinigoblmkhjejkmbegnoaljkphmgo"; // Development build. Check chrome://extensions.

  // Local browser. For a remote browser: const browser = await chromium.connectOverCDP(cdpUrl);
  // then const context = browser.contexts()[0];
  const context = await chromium.launchPersistentContext("", {
    channel: "chromium",
    args: [
      `--disable-extensions-except=${EXTENSION_PATH}`,
      `--load-extension=${EXTENSION_PATH}`,
    ],
  });

  const isOnePassword = (worker) =>
    worker.url() === `chrome-extension://${EXTENSION_ID}/background/background.js`;
  let worker = context.serviceWorkers().find(isOnePassword);
  if (!worker) worker = await context.waitForEvent("serviceworker", isOnePassword);

  const created = await worker.evaluate(
    async (args) => {
      if ((await api.initialization.v1.whenSettled()) !== "ready") {
        return { success: false, error: { code: "internal" } };
      }
      return api.agenticAutofill.v1.createAccessRequest(args);
    },
    { accessToken, integrationKey, requests },
  );
  ```

  ```javascript Puppeteer theme={null}
  import puppeteer from "puppeteer";

  const EXTENSION_PATH = "/path/to/unpacked/1password-extension";
  const EXTENSION_ID = "hjlinigoblmkhjejkmbegnoaljkphmgo"; // Development build. Check chrome://extensions.

  // Local browser. For a remote browser: puppeteer.connect({ browserWSEndpoint })
  const browser = await puppeteer.launch({
    args: [
      `--disable-extensions-except=${EXTENSION_PATH}`,
      `--load-extension=${EXTENSION_PATH}`,
    ],
  });

  const target = await browser.waitForTarget(
    (t) =>
      t.type() === "service_worker" &&
      t.url() === `chrome-extension://${EXTENSION_ID}/background/background.js`,
  );
  const worker = await target.worker();

  const created = await worker.evaluate(
    async (args) => {
      if ((await api.initialization.v1.whenSettled()) !== "ready") {
        return { success: false, error: { code: "internal" } };
      }
      return api.agenticAutofill.v1.createAccessRequest(args);
    },
    { accessToken, integrationKey, requests },
  );
  ```

  ```text Raw CDP theme={null}
  1. Target.getTargets
     Find the target with type "service_worker" and
     url "chrome-extension://<extension-id>/background/background.js".

  2. Target.attachToTarget { targetId, flatten: true }
     Keep the returned sessionId.

  3. Runtime.evaluate on that session:
     {
       expression: "api.initialization.v1.whenSettled()",
       awaitPromise: true,
       returnByValue: true
     }
     Continue only if it returns "ready".

  4. Runtime.evaluate on that session:
     {
       expression: "api.agenticAutofill.v1.createAccessRequest(<arguments as JSON>)",
       awaitPromise: true,
       returnByValue: true
     }
  ```
</CodeGroup>

Keep the session attached for the length of a task. An extension service worker can suspend after about 30 seconds of inactivity, and an attached DevTools session keeps it running.

If your cloud browser provider drives the browser through its own automation API, you still need a CDP connection to the browser for these calls. Chromium accepts more than one DevTools client at a time.

## Find the tab ID

`fillCredential` takes a Chrome tab ID, the number the `chrome.tabs` API uses. It isn't a CDP target ID, and Playwright and Puppeteer don't expose it. Look it up inside the same service worker, with `chrome.tabs.query`:

```javascript theme={null}
// The tab showing the sign-in page
const [tab] = await chrome.tabs.query({ url: "https://github.com/*" });

// Or the active tab
const [active] = await chrome.tabs.query({ active: true, currentWindow: true });
```

## Turn on Agentic Mode

Agentic Mode stops normal 1Password browser behavior, such as inline suggestions and save prompts, from exposing the person's state in pages your agent controls. Turn it on every time your agent starts working in the browser, for the tab it works in or for the whole browser. A fill in a tab that Agentic Mode doesn't cover fails with `agenticModeNotEnabled`.

```javascript theme={null}
// One tab
await api.agenticMode.v1.enable({ tabId: tab.id });

// The whole browser, including tabs opened later
await api.agenticMode.v1.enable();
```

Check `success` in the response. Turn Agentic Mode off with `api.agenticMode.v1.disable()`, with the same argument, when the agent gives up control. See [Turn on Agentic Mode](/agentic-autofill/partners/extension-api#turn-on-agentic-mode) for the errors.

## Fill a granted login

Call `api.agenticAutofill.v1.fillCredential` with the tab and the reference of the granted login to fill. Your agent decides which granted login to use for which page. Each resolved grant carries a reference object: pass the string inside it, `grant.reference.reference`, as `resourcePath`. That's the same whether you checked the request with the extension or the SDK.

Before you fill, navigate the tab to the page with the username and password form. Sign-in pages that first ask the person to pick a method, such as a social sign-in choice, aren't supported yet.

```javascript Playwright theme={null}
const result = await worker.evaluate(
  async ({ accessToken, integrationKey, resourcePath, tabUrl }) => {
    const [tab] = await chrome.tabs.query({ url: tabUrl });
    if (!tab) return { success: false, error: { code: "invalidTabId" } };
    // Agentic Mode is already on for this tab or the whole browser.
    return api.agenticAutofill.v1.fillCredential({
      accessToken,
      integrationKey,
      resourcePath,
      tabId: tab.id,
    });
  },
  {
    accessToken,
    integrationKey,
    resourcePath: grant.reference.reference,
    tabUrl: "https://github.com/*",
  },
);

if (result.success) {
  // result.result.status === "fill_submitted"
} else {
  // result.error.code: invalidRequest, invalidTabId, agenticModeNotEnabled, fillFailed, or autosubmitFailed
}
```

A successful call returns `fill_submitted`. That means 1Password filled and submitted the form, and the filled values are no longer present on the page. It doesn't mean the website accepted the login or finished signing in, so check the page after the call returns.

On `fillFailed` and `autosubmitFailed`, 1Password clears what it filled before it returns.

A multi-page sign-in, such as a username page followed by a password page and a one-time code page, fills the same reference again on each page. Every fill is a fresh request to 1Password, so a revoked or expired grant fails at the next fill.

## Keep the model off the page during a fill

Between fill and submit, the values are in the page. The extension removes them before `fillCredential` returns, but until then anything that can read the page can read them.

* Don't let the model or agent read the DOM, take screenshots, or send other CDP commands to that tab until `fillCredential` returns.
* One approach is to keep the browser on a different machine from the model and pause agent actions until the call returns. A lock in your orchestrator also works. In either case, prevent the model and agent from reading the page while secrets are present.
* Don't run other extensions you don't trust in the same browser. An extension with access to the page can read it.

## Errors

| Code | Meaning | What to do |
| - | - | - |
| `invalidRequest` | A parameter is missing or has the wrong type. Passing the reference object instead of the string inside it causes this. | Pass `grant.reference.reference` as `resourcePath`, and a number as `tabId`. |
| `invalidTabId` | The tab doesn't exist or can't be used. | Look up the tab the agent is working in with `chrome.tabs.query`. |
| `agenticModeNotEnabled` | Agentic Mode doesn't cover the tab. | Turn on Agentic Mode for the tab or the whole browser, then fill again. |
| `noExistingCredentials` | Reserved. The granted login is no longer available. | Create a new access request and have the person approve it. |
| `fillFailed` | 1Password couldn't fill the login. An expired access token, a revoked grant, or a reference from another connection also shows up as this. | Refresh the access token before you fill. If it still fails, create a new access request. |
| `autosubmitFailed` | 1Password filled the form but couldn't submit it, and cleared the values. | Report the website to 1Password and use another sign-in method for that site. |

See [Errors and troubleshooting](/agentic-autofill/partners/troubleshooting) for more.


## Related topics

- [Get started with 1Password for SSH](/ssh/get-started.md)
- [Set up your team to use 1Password developer tools](/get-started/secure-developers.md)
- [Use 1Password to securely provide credentials to AI agents](/agentic-autofill.md)
