---
title: Bitwarden Plugin
description: Using Bitwarden Secrets Manager and Password Manager (and Vaultwarden) with Varlock
---

import { Steps, Icon } from '@astrojs/starlight/components';
import Badge from '@/components/Badge.astro';

<div class="page-badges">
  <Badge npmPackage="@varlock/bitwarden-plugin" />
</div>

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](https://bitwarden.com/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](https://bitwarden.com/products/personal/)** (and self-hosted **[Vaultwarden](https://github.com/dani-garcia/vaultwarden)**): your personal/team password vault, accessed via the `bw` CLI. Best for local development. Uses `@initBwp()` + `bwp()`.

## Features

- **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
- **Helpful error handling** with resolution tips
- **Lightweight implementation** using REST API (48 KB bundle, no native SDK dependencies)

## Installation and setup

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](/guides/plugins/#installation) for more instructions on installing plugins.

```env-spec title=".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=
```

### Machine account setup

<Steps>
1. **Create a machine account** in your Bitwarden organization

    Navigate to your Bitwarden organization's **Secrets Manager** → **Machine 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.

    :::caution[Save the token securely]
    Store the access token securely. You won't be able to see it again after this step!
    :::

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-spec title=".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.
</Steps>

:::tip[Permission levels]
Machine accounts can have _Can read_ (retrieve secrets only) or _Can read, write_ (retrieve, create, and edit secrets) permissions. For most use cases, _Can read_ is sufficient.
:::

### Self-hosted Bitwarden

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

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

### Multiple instances

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

```env-spec title=".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")
```

## Loading secrets

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

### Basic usage

Fetch secrets by their UUID:

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

:::tip[Finding secret UUIDs]
To find a secret's UUID:
1. Open your Bitwarden Secrets Manager
2. Navigate to the secret
3. Copy the UUID from the URL or secret details (format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`)
:::

### Multiple instances

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

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

---

## Bitwarden Setup

### Create a machine account

Machine accounts provide programmatic access to Bitwarden Secrets Manager.

<Steps>
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
</Steps>

**Permission Levels:**

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

:::caution[Access token security]
Store the access token securely - it will only be displayed once during creation!
:::

### Grant access to secrets

**Via Projects (Recommended):**

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

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

**Direct Secret Access:**

<Steps>
1. Navigate to a specific secret
2. Click **Access**
3. Add your machine account with appropriate permissions
</Steps>

---

## Password Manager / Vaultwarden

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](https://bitwarden.com/help/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 with 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.

### Prerequisites

<Steps>
1. **Install the `bw` CLI** from a trusted source.

    :::caution[Install from trusted sources only]
    Install the official CLI via a trusted channel and keep it up to date. Supply-chain incidents have affected the broader Bitwarden CLI ecosystem in the past. varlock never installs `bw` for you; it only invokes whatever `bw` is already on your `PATH`.
    :::

    ```bash
    # macOS
    brew install bitwarden-cli
    # Linux
    snap install bw
    # Windows
    choco install bitwarden-cli
    ```

    Or download the official binary from the [Bitwarden CLI docs](https://bitwarden.com/help/cli/).

2. **Log in once** with your account:

    ```bash
    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, so you don't need to run `bw unlock` yourself.
</Steps>

### Basic usage

```env-spec title=".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`.

:::note[Using `bw` outside varlock can invalidate the cached session]
Each `bw unlock` (and `bw login`) issues a new session key and **invalidates any previously issued ones**; `bw lock` / `bw logout` invalidate them too. So if you run those commands in your own scripts or shell (or keep a `BW_SESSION` exported elsewhere), varlock's cached token gets invalidated (and vice-versa: varlock unlocking invalidates your shell's `BW_SESSION`). Plain reads (`bw get`, `bw list`, `bw sync`) are fine and don't invalidate anything.

This is not fatal: the next `varlock` load detects the stale session and re-unlocks once, at the cost of one extra master-password prompt. If you frequently `bw unlock` outside varlock, consider letting varlock own the unlock (don't re-unlock manually) or pass a shared `sessionToken=$BWP_SESSION` so both sides use the same token.
:::

### Selecting a field

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-spec title=".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")
```

:::note[`totp` returns the secret, not a code]
`field=totp` returns the **stored TOTP secret/seed** (a base32 key or `otpauth://` URI), not a generated 6-digit code. It is read verbatim from `bw get item`, the same way every other field is. There is no field that returns a live code: a one-time code rotates every ~30 seconds, but config is resolved once at load time, so a captured code would be stale before it could be used.
:::

### Caching and TTLs

```env-spec title=".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](/guides/caching/).

### Non-interactive loads

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-spec title=".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=
```

### Multiple accounts / servers

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-spec title=".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.

:::note[`appDataDir` is machine-specific]
A data-dir path points at a directory on *your* machine, so avoid hardcoding it in a shared `.env.schema`. There's no way to override an already-declared instance's settings from another file, so make the **value** local rather than the instance: reference `appDataDir=$BWP_WORK_DIR` and set that var in your (gitignored) `.env.local`. If the whole multi-account workflow is personal, you can instead declare the entire `@initBwp(...)` in `.env.local`; root decorators are honored there too.
:::

:::caution
Without distinct `appDataDir`s, multiple instances all point at the same global `bw` account, so `bwp(personal, …)` and `bwp(work, …)` would read the *same* vault. The `appDataDir` is what makes them separate. (This differs from Secrets Manager, where each instance has its own `accessToken`.)
:::

---

## Reference

### Root decorators
<div class="reference-docs">
<div>
#### `@initBitwarden()`

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](/guides/caching/).
- `id` (optional): Instance identifier for multiple instances

```env-spec "@initBitwarden"
# @initBitwarden(accessToken=$BITWARDEN_ACCESS_TOKEN)
# ---
# @type=bitwardenAccessToken @sensitive @internal
BITWARDEN_ACCESS_TOKEN=
```
</div>

<div>
#### `@initBwp()`

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](/guides/caching/).
- `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.

```env-spec "@initBwp"
# @initBwp(sessionTtl="1h")
# ---
DB_PASSWORD=bwp("Production DB")
```
</div>
</div>

### Data types
<div class="reference-docs">
<div>
#### `bitwardenAccessToken`

This type is [`@internal`](/reference/item-decorators/#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.

```env-spec "bitwardenAccessToken"
# @type=bitwardenAccessToken
BITWARDEN_ACCESS_TOKEN=
```
</div>

<div>
#### `bitwardenSecretId`

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

```env-spec "bitwardenSecretId"
# @type=bitwardenSecretId
MY_SECRET_ID=12345678-1234-1234-1234-123456789abc
```
</div>

<div>
#### `bitwardenOrganizationId`

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

```env-spec "bitwardenOrganizationId"
# @type=bitwardenOrganizationId
BITWARDEN_ORG_ID=87654321-4321-4321-4321-cba987654321
```
</div>

<div>
#### `bwSessionToken`

This type is [`@internal`](/reference/item-decorators/#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`.

```env-spec "bwSessionToken"
# @type=bwSessionToken
BWP_SESSION=
```
</div>
</div>

### Resolver functions
<div class="reference-docs">
<div>
#### `bitwarden()`

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`

```env-spec /bitwarden\\(.*\\)/
# Fetch by secret UUID
DATABASE_URL=bitwarden("12345678-1234-1234-1234-123456789abc")

# With instance ID
PROD_SECRET=bitwarden(prod, "11111111-1111-1111-1111-111111111111")
```
</div>

<div>
#### `bwp()`

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. One of `password` (default), `username`, `notes`, `totp` (the stored TOTP secret/seed, not a generated code), `uri`, or a custom field name

```env-spec /bwp\\(.*\\)/
# 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)
```
</div>
</div>

---

## Troubleshooting

### Secret not found
- 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

### Permission denied
- 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

### Authentication failed
- 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

### Invalid UUID format
- 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

### Password Manager (`bwp()`)
- **`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](#non-interactive-loads).
- **Item / field not found**: verify the item name or UUID; the error lists the item's available custom fields.

## Resources

- [Bitwarden Secrets Manager](https://bitwarden.com/products/secrets-manager/)
- [Machine Accounts Documentation](https://bitwarden.com/help/machine-accounts/)
- [Self-Hosting Bitwarden](https://bitwarden.com/help/manage-your-secrets-org/#self-hosting)
- [Bitwarden CLI](https://bitwarden.com/help/cli/)
- [Vaultwarden](https://github.com/dani-garcia/vaultwarden)
