For AI agents: a documentation index is available at /llms.txt
Skip to main content

Loading secrets in BAP

loadSecret() fills a credential straight from 1Password into a login form. The value is resolved in the browser at the moment it's typed, so it never appears in your code, your query, or the response. This guide covers the setup it needs and the session lockdown it deliberately triggers.

Prerequisites

Connect with an integration

The session has to know which 1Password service account to resolve against, and that's a connection-time decision: append integrationId to the endpoint. A loadSecret() call on a session without one fails.

import Browserless from "@browserless.io/bap-ts";

const TOKEN = "YOUR_API_TOKEN_HERE";
const INTEGRATION_ID = "YOUR_SERVICE_ACCOUNT_ID";

const browser = Browserless.connect({
// The integration is bound to the session, not to the call.
browserWSEndpoint: `wss://production-sfo.browserless.io/chromium/bql?integrationId=${INTEGRATION_ID}`,
token: TOKEN,
});

Fill a credential

Pass the 1Password op:// reference and the input to fill. With no selector, the currently focused element receives the value, which suits flows where you've just tabbed or clicked into the field.

try {
const page = await browser.newPage();
await page.goto("https://example.com/login");

// The username is not a secret, so type it normally.
await page.type("#username", "name@example.com");

// The password is resolved in the browser and never reaches this script.
const result = await page.loadSecret("op://Vault/ExampleLogin/password", {
selector: "#password",
});

// Bail out rather than submitting an empty password field.
if (!result.ok) {
throw new Error(`${result.error}: ${result.message}`);
}

const navigated = page.waitForNavigation({ waitUntil: "networkIdle" });
await page.click("#submit");
await navigated;
} finally {
await browser.close();
}

The response carries ok, plus an error code and human-readable message when it's false. Codes include DomainNotAllowed, SelectorNotFound, NoFocusedElement, TargetNotFillable, and CredentialNotResolved, which between them tell you whether the problem is your integration's allowed domains, your selector, or the reference itself.

The session locks down after the first secret

This is the part worth reading before you build on it. Once any secret has been filled, the session refuses everything that could read the credential back out, for the rest of its life:

  • Captures: screenshots, PDFs, screencasts, Live URLs, and session recording
  • Content readback: evaluate(), html(), text(), querySelector(), querySelectorAll(), and cookies()

That's deliberate. A filled password is present in the DOM, so a screenshot or an html() call would hand back the very value the feature exists to protect. The consequence for your script is that anything you need to read or capture has to happen before the loadSecret() call, and afterward you're limited to interaction and navigation.

Reconnecting doesn't lift this. reconnect() returns you to the same browser session, and the lockdown lasts for that session's life, so the restrictions follow you across the reconnect. When a run needs both a loadSecret() login and captures afterward, use a separate session for the capture work.

// Wrong order: this html() call rejects, because a secret was already filled.
await page.loadSecret("op://Vault/ExampleLogin/password", { selector: "#password" });
await page.click("#submit");
const { html } = await page.html();

// Right order: capture what you need first, then authenticate.
const { html: before } = await page.html();
await page.loadSecret("op://Vault/ExampleLogin/password", { selector: "#password" });
await page.click("#submit");

When to use this instead of cookies

loadSecret() and cookie injection both avoid putting a password in your code, and they suit different situations.

Reach for loadSecret() when the site requires a real login each run, when a human rotates the credential in 1Password and you want automation to pick that up without a deploy, or when your threat model says the password must never exist in your process memory.

Reach for cookies when you can log in once and reuse the session, since you skip the login entirely and keep full capture and readback for the whole run.

FAQ & Troubleshooting

My screenshot fails after logging in with loadSecret()

That's the intended lockdown, not a bug. Filling a secret disables captures and content readback for the rest of the session, because a screenshot of a filled password field would defeat the purpose. Capture before the call, or do the capture work in a separate session. Reconnecting won't help, since it returns to the same session and inherits the same restrictions.

The call returns DomainNotAllowed

The page's origin isn't in the service account's allowed-domain list. Add it in the dashboard under the 1Password service account, and check you're on the origin you expect after any redirects.

The call returns NoFocusedElement

You omitted selector and nothing had focus. Either pass the input's selector, or click into the field first so the focused element is the one you want filled.

Can I read the secret's value in my script?

No, by design. The value is resolved in the browser and appears in neither the query nor the response, and the readback methods that could expose it are disabled once it's filled. If you need the value itself, fetch it from 1Password directly in your own code rather than through BAP.

Next steps

Was this page helpful?