Skip to content

Bitwarden Plugin

Our Bitwarden plugin enables secure loading of secrets from Bitwarden using declarative instructions within your .env files. It supports two distinct Bitwarden products:

  • Secrets Manager — programmatic secret storage, accessed via machine account access tokens over the REST API. Best for CI/CD and production. Uses @initBitwarden() + bitwarden().
  • Password Manager (and self-hosted Vaultwarden) — your personal/team password vault, accessed via the bw CLI. Best for local development. Uses @initBwp() + bwp().
  • Zero-config authentication - Just provide your machine account access token
  • UUID-based secret access - Fetch secrets by their unique identifiers
  • Self-hosted Bitwarden support - Configure custom API and identity URLs
  • Multiple instances - Connect to different organizations or self-hosted instances
  • Comprehensive error handling with helpful tips
  • Lightweight implementation using REST API (48 KB bundle, no native SDK dependencies)

In a JS/TS project, you may install the @varlock/bitwarden-plugin package as a normal dependency. Otherwise you can just load it directly from your .env.schema file, as long as you add a version specifier. See the plugins guide for more instructions on installing plugins.

.env.schema
# 1. Load the plugin
# @plugin(@varlock/bitwarden-plugin)
#
# 2. Initialize the plugin - see below for more details on options
# @initBitwarden(accessToken=$BITWARDEN_ACCESS_TOKEN)
# ---
# 3. Add a machine account access token config item
# @type=bitwardenAccessToken @sensitive @internal
BITWARDEN_ACCESS_TOKEN=
  1. Create a machine account in your Bitwarden organization

    Navigate to your Bitwarden organization’s Secrets ManagerMachine accounts → Click New machine account.

    Provide a name (e.g., “Production App”) and save it.

  2. Copy the access token (displayed only once!)

    After creating the machine account, you’ll see an Access token. Copy it immediately - it will only be displayed once.

  3. Grant access to secrets

    Grant your machine account access to the specific projects or secrets you need.

    Via Projects:

    • Create or select a project in Secrets Manager
    • Add secrets to the project
    • Grant your machine account access to the project

    Direct Secret Access:

    • Navigate to a specific secret
    • Click Access
    • Add your machine account with “Can read” permissions
  4. Wire up the token in your config

    .env.schema
    # @plugin(@varlock/bitwarden-plugin)
    # @initBitwarden(accessToken=$BITWARDEN_ACCESS_TOKEN)
    # ---
    # @type=bitwardenAccessToken @sensitive @internal
    BITWARDEN_ACCESS_TOKEN=
  5. Set your access token in environments

    Use your CI/CD system or platform’s env var management to securely inject the BITWARDEN_ACCESS_TOKEN value.

For self-hosted Bitwarden instances, you’ll need to provide both the API and identity URLs:

.env.schema
# @plugin(@varlock/bitwarden-plugin)
# @initBitwarden(
# accessToken=$BITWARDEN_ACCESS_TOKEN,
# apiUrl="https://bitwarden.yourcompany.com/api",
# identityUrl="https://bitwarden.yourcompany.com/identity"
# )

If you need to connect to multiple organizations or instances, register multiple named instances:

.env.schema
# @initBitwarden(id=prod, accessToken=$PROD_ACCESS_TOKEN)
# @initBitwarden(id=dev, accessToken=$DEV_ACCESS_TOKEN)
# ---
PROD_SECRET=bitwarden(prod, "11111111-1111-1111-1111-111111111111")
DEV_SECRET=bitwarden(dev, "22222222-2222-2222-2222-222222222222")

Once the plugin is installed and initialized, you can start adding config items that load values using the bitwarden() resolver function.

Fetch secrets by their UUID:

.env.schema
# Fetch secrets by UUID
DATABASE_URL=bitwarden("12345678-1234-1234-1234-123456789abc")
API_KEY=bitwarden("87654321-4321-4321-4321-cba987654321")

If you have multiple plugin instances, specify which instance to use:

.env.schema
PROD_ITEM=bitwarden(prod, "11111111-1111-1111-1111-111111111111")
DEV_ITEM=bitwarden(dev, "22222222-2222-2222-2222-222222222222")

Machine accounts provide programmatic access to Bitwarden Secrets Manager.

  1. Log in to your Bitwarden organization web vault

  2. Navigate to Secrets Manager → Machine accounts

  3. Click “New machine account”

  4. Provide a name (e.g., “Production App”)

  5. Copy the Access token (shown only once!)

  6. Grant access to specific projects or secrets

Permission Levels:

  • Can read - Retrieve secrets only (recommended for most use cases)
  • Can read, write - Retrieve, create, and edit secrets

Via Projects (Recommended):

  1. Create or select a project in Secrets Manager
  2. Add secrets to the project
  3. Grant your machine account access to the project

This approach makes it easier to manage access to multiple secrets at once.

Direct Secret Access:

  1. Navigate to a specific secret
  2. Click Access
  3. Add your machine account with appropriate permissions

Separate from Secrets Manager, you can also load values from your personal or team Bitwarden Password Manager vault — or a self-hosted Vaultwarden server. This path uses the Bitwarden bw CLI rather than the REST API, so it’s best suited to local development.

varlock acquires and caches a CLI session token for you: on first load it runs bw unlock (prompting for your master password), then caches the token in varlock’s encrypted cache and reuses it on subsequent loads. You no longer need to manually unlock and paste a session token into a .env file.

By default the token is cached indefinitely (sessionTtl="forever"). This is safe because the cached token is encrypted at rest with varlock’s local key — biometric-gated (Touch ID / Windows Hello) on platforms with a secure enclave — so reading it requires you. Note the bw CLI session does not auto-lock on system sleep/lock/restart (those are app-only vault-timeout settings), so by default a fresh master-password unlock is only needed when the session is explicitly invalidated (bw lock/bw logout, an external bw unlock, or an account policy). Set a shorter sessionTtl if you want varlock to force periodic master-password re-auth.

  1. Install the bw CLI from a trusted source.

    Terminal window
    # macOS
    brew install bitwarden-cli
    # Linux
    snap install bw
    # Windows
    choco install bitwarden-cli

    Or download the official binary from the Bitwarden CLI docs.

  2. Log in once with your account:

    Terminal window
    bw login
    # For Vaultwarden / self-hosted, point the CLI at your server first:
    # bw config server https://vault.yourcompany.com

    varlock handles unlocking from here on — you don’t need to run bw unlock yourself.

.env.schema
# 1. Load the plugin
# @plugin(@varlock/bitwarden-plugin)
#
# 2. Initialize the Password Manager instance (no token needed)
# @initBwp()
# ---
# 3. Load values from vault items by name or UUID
DATABASE_PASSWORD=bwp("Production DB")

On the first varlock load, you’ll be prompted for your master password once; the resulting session token is cached and reused (sessionTtl, default forever). The bw CLI session does not lock on system sleep, lock, or restart — those vault-timeout settings apply to the Bitwarden apps and extension, not the CLI — so in practice you re-enter the master password only when the session is explicitly invalidated (see below) or when you set a shorter sessionTtl.

By default bwp() returns the item’s password. Use the field argument to fetch a different field. Standard fields are password, username, notes, totp, and uri; any other name is matched against the item’s custom fields (case-insensitive).

.env.schema
DB_USER=bwp("Production DB", field=username)
DB_PASSWORD=bwp("Production DB", field=password)
# the stored TOTP secret/seed, not a generated 6-digit code (see note below)
API_TOTP_SEED=bwp("Some Service", field=totp)
# custom field on the item
STRIPE_KEY=bwp("Stripe", field="secret-key")
.env.schema
# @initBwp(sessionTtl="1h", cacheTtl="5m")
  • sessionTtl (default forever) — how long the unlocked CLI session token is cached before varlock forces a fresh bw unlock. forever keeps the token until the CLI session is invalidated externally (bw lock/bw logout, an external bw unlock, or account policy), at which point the next load re-unlocks automatically; set e.g. "1h" to force periodic master-password re-auth.
  • cacheTtl (optional) — additionally cache the resolved item values, avoiding a bw invocation per value within the window. See the Caching guide.

Interactive unlock needs a TTY, so a non-interactive load (a dev server started by a tool that has no TTY, an editor task runner, etc.) can’t show the master-password prompt. How you fix it depends on the environment:

Local dev, using auto-unlock — if you’re letting varlock unlock the vault itself (no sessionToken / masterPassword configured on @initBwp()), run varlock load once in a regular terminal (npx varlock load, pnpm exec varlock load, etc.). That performs the interactive unlock and caches the session token in varlock’s encrypted cache, so subsequent non-interactive commands reuse it without prompting (until the session is invalidated — see sessionTtl above). This only applies to the auto-unlock flow — if you’ve configured sessionToken= or masterPassword=, no interactive varlock load is needed.

CI / truly headless — there’s no terminal to unlock from, so provide credentials explicitly instead:

.env.schema
# Option A: pass a pre-obtained session token (from `bw unlock --raw`)
# @initBwp(sessionToken=$BWP_SESSION)
# Option B: let varlock unlock non-interactively with a master password
# @initBwp(masterPassword=$BW_MASTER_PASSWORD)
# ---
# @type=bwSessionToken @sensitive @internal
BWP_SESSION=

The bw CLI keeps one logged-in account per data directory, so to read from more than one account or server (e.g. personal + work, or bitwarden.com + a self-hosted Vaultwarden) you give each instance its own data dir via appDataDir. Set up each dir once with the bw CLI (bw config server … if needed, then bw login), then point an instance at it. Since a data-dir path is specific to your machine, reference it from a per-developer value rather than hardcoding it:

.env.schema
# @initBwp(id=personal, appDataDir=$BWP_PERSONAL_DIR)
# @initBwp(id=work, appDataDir=$BWP_WORK_DIR)
# ---
# bw data dirs — set per-machine in .env.local (a leading ~ is expanded)
BWP_PERSONAL_DIR=
BWP_WORK_DIR=
PERSONAL_TOKEN=bwp(personal, "My Token")
WORK_DB=bwp(work, "Work DB", field=password)

varlock unlocks and caches a session token per account (keyed by appDataDir), so the two accounts never share or invalidate each other’s sessions.


Initialize a Bitwarden Secrets Manager plugin instance for accessing secrets.

Key/value args:

  • accessToken (required): Machine account access token. Should be a reference to a config item of type bitwardenAccessToken.
  • apiUrl (optional): API URL for self-hosted Bitwarden (defaults to https://api.bitwarden.com)
  • identityUrl (optional): Identity service URL for self-hosted Bitwarden (defaults to https://identity.bitwarden.com)
  • cacheTtl (optional): cache resolved values for the specified duration (e.g. "5m", "1h", "1d", or forever to cache until manually cleared). For cache mode behavior and CLI cache controls, see the Caching guide.
  • id (optional): Instance identifier for multiple instances
# @initBitwarden(accessToken=$BITWARDEN_ACCESS_TOKEN)
# ---
# @type=bitwardenAccessToken @sensitive @internal
BITWARDEN_ACCESS_TOKEN=

Initialize a Password Manager / Vaultwarden instance accessed via the bw CLI. With no arguments, varlock unlocks the vault interactively on first use and caches the session token.

Key/value args (all optional):

  • sessionToken: a pre-obtained CLI session token (e.g. $BWP_SESSION). When provided and non-empty, it’s used as-is instead of auto-unlocking.
  • masterPassword: master password used to unlock the vault non-interactively (e.g. in CI). Reference a @sensitive config item.
  • sessionTtl: how long the auto-unlocked session token is cached before a fresh bw unlock (default forever — kept until the CLI session is invalidated externally; e.g. "1h" to force periodic master-password re-auth).
  • cacheTtl: cache resolved item values for the specified duration. See the Caching guide.
  • appDataDir: bw CLI data directory (BITWARDENCLI_APPDATA_DIR) for this instance — point separate instances at separate dirs to read from different accounts/servers. A leading ~ is expanded. The dir must be set up independently (bw config server + bw login).
  • id: instance identifier for multiple instances.
# @initBwp(sessionTtl="1h")
# ---
DB_PASSWORD=bwp("Production DB")

This type is @internal by default — varlock uses it to fetch your other secrets but does not inject it into your application. Override with @internal=false if your app uses the credential directly — for example to write secrets back or fetch additional secrets at runtime.

Represents a Bitwarden Secrets Manager machine account access token. Validation ensures the token is in the correct format (0.<client_id>.<client_secret>:<encryption_key>). Note that the type itself is marked as @sensitive, so adding an explicit @sensitive decorator is optional.

# @type=bitwardenAccessToken
BITWARDEN_ACCESS_TOKEN=

Represents a secret UUID in Bitwarden Secrets Manager. Validation ensures the ID is a valid UUID format.

# @type=bitwardenSecretId
MY_SECRET_ID=12345678-1234-1234-1234-123456789abc

Represents an organization UUID in Bitwarden. Validation ensures the ID is a valid UUID format.

# @type=bitwardenOrganizationId
BITWARDEN_ORG_ID=87654321-4321-4321-4321-cba987654321

This type is @internal by default — varlock uses it to fetch your other secrets but does not inject it into your application. Override with @internal=false if your app uses the credential directly — for example to write secrets back or fetch additional secrets at runtime.

Represents a Bitwarden CLI session token (the output of bw unlock). Only needed when supplying a token manually for non-interactive use. Marked @sensitive.

# @type=bwSessionToken
BWP_SESSION=

Fetch a secret from Bitwarden Secrets Manager by UUID.

Array args:

  • instanceId (optional): instance identifier to use when multiple plugin instances are initialized
  • secretId (required): secret UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Fetch by secret UUID
DATABASE_URL=bitwarden("12345678-1234-1234-1234-123456789abc")
# With instance ID
PROD_SECRET=bitwarden(prod, "11111111-1111-1111-1111-111111111111")

Fetch a field from a Password Manager / Vaultwarden vault item via the bw CLI.

Args:

  • instanceId (optional, positional): instance identifier when multiple @initBwp() instances are initialized
  • item (required, positional): item name or UUID to look up
  • field (optional, key/value): which field to return — password (default), username, notes, totp (the stored TOTP secret/seed, not a generated code), uri, or a custom field name
# Default field (password)
DB_PASSWORD=bwp("Production DB")
# Specific field
DB_USER=bwp("Production DB", field=username)
# With instance ID
WORK_DB=bwp(work, "Work DB", field=password)

  • Verify the secret UUID is correct (must be valid UUID format)
  • Check that the secret exists in your Bitwarden Secrets Manager
  • Ensure your machine account has access to the secret or its project
  • Verify your machine account has “Can read” or “Can read, write” permissions
  • Check that the machine account has access to the specific secret
  • Review the access settings in Bitwarden Secrets Manager console
  • Verify the access token is correct
  • Check if the access token has been revoked or expired
  • Ensure the machine account is not disabled
  • For self-hosted: verify apiUrl and identityUrl are correct
  • Secret IDs must be valid UUIDs: 12345678-1234-1234-1234-123456789abc
  • Check for typos or incorrect format
  • UUIDs should contain 32 hexadecimal characters and 4 hyphens
  • bw not found — install the CLI from a trusted source and ensure it’s on your PATH.
  • Not logged in — run bw login once (for Vaultwarden, also bw config server <url> first).
  • Cannot unlock without an interactive terminal — you’re auto-unlocking (no sessionToken / masterPassword configured) in a non-TTY context. For local dev, run varlock load once in a regular terminal to unlock and cache the session token; for CI / headless, provide sessionToken=$BWP_SESSION or masterPassword=... to @initBwp(). See Non-interactive loads.
  • Item / field not found — verify the item name or UUID; the error lists the item’s available custom fields.