# @varlock/1password-plugin

[![npm version](https://img.shields.io/npm/v/@varlock/1password-plugin.svg)](https://npmx.dev/package/@varlock/1password-plugin) [![GitHub stars](https://img.shields.io/github/stars/dmno-dev/varlock.svg?style=social&label=Star)](https://github.com/dmno-dev/varlock) [![license](https://img.shields.io/npm/l/@varlock/1password-plugin.svg)](https://github.com/dmno-dev/varlock/blob/main/LICENSE)

This package is a [Varlock](https://varlock.dev) [plugin](https://varlock.dev/guides/plugins/) that enables loading data from [1Password](https://1password.com/) into your configuration.

## Features

- **Service account authentication** for CI/CD and production environments
- **Desktop app authentication** for local development (with biometric unlock support)
- **Connect server authentication** for self-hosted 1Password infrastructure
- **Secret references** using 1Password's standard `op://` format
- **Bulk-load environments** with `opLoadEnvironment()` via `@setValuesBulk`
- **Multiple vault support** for different environments and access levels
- **Multiple instances** for connecting to different accounts or vaults
- Compatible with any 1Password account type (personal, family, teams, business)
- Comprehensive error handling with helpful tips

## Installation

If you are in a JavaScript based project and have a package.json file, you can either install the plugin explicitly
```bash
npm install @varlock/1password-plugin
```
And then register the plugin without any version number
```env-spec title=".env.schema"
# @plugin(@varlock/1password-plugin)
```

Otherwise just set the explicit version number when you register it
```env-spec title=".env.schema"
# @plugin(@varlock/1password-plugin@1.2.3)
```

See our [Plugin Guide](https://varlock.dev/guides/plugins/#installation) for more details.

## Setup + Auth

After registering the plugin, you must initialize it with the `@initOp` root decorator.

### Service account setup (for deployed environments)

For deployed environments (CI/CD, production, etc), you'll need a service account token:

```env-spec
# @plugin(@varlock/1password-plugin)
# @initOp(token=$OP_TOKEN)
# ---

# @type=opServiceAccountToken @sensitive @internal
OP_TOKEN=
```

**How to create a service account:**

1. Navigate to your 1Password web interface
2. Go to **Service Accounts** → Create a new service account
3. Grant access to necessary vault(s)
4. Copy the service account token (displayed only once!)
5. Set the token in your deployed environments using your platform's env var management

:::note
Vault access rules cannot be edited after creation. If your vault setup changes, you'll need to create a new service account.
:::

### CLI-based service account auth (memory-constrained environments)

By default, service account tokens use the 1Password JavaScript SDK (which includes a WASM module). In memory-constrained environments (e.g., 512 MB containers), the SDK's memory footprint may be prohibitive.

Set `useCliWithServiceAccount=true` to use the `op` CLI binary instead of the SDK while still authenticating via `OP_SERVICE_ACCOUNT_TOKEN`:

```env-spec
# @plugin(@varlock/1password-plugin)
# @initOp(token=$OP_TOKEN, useCliWithServiceAccount=true)
# ---

# @type=opServiceAccountToken @sensitive @internal
OP_TOKEN=
```

**Requirements:**

1. Install the `op` CLI: [Installation guide](https://www.1password.dev/cli/get-started/)
2. The `OP_SERVICE_ACCOUNT_TOKEN` (i.e. `$OP_TOKEN` above) must resolve to a valid service account token at load time.

The `op` binary is significantly lighter than the WASM SDK. `op` authentication is handled headlessly via the token — no desktop app or interactive sign-in is needed.

### Desktop app auth (for local dev)

During local development, you can use the 1Password desktop app instead of a service account:

```env-spec
# @plugin(@varlock/1password-plugin)
# @initOp(token=$OP_TOKEN, allowAppAuth=true, account=acmeco)
# ---

# @type=opServiceAccountToken @sensitive @internal
OP_TOKEN=
```

**Setup requirements:**

1. Install the `op` CLI: [Installation guide](https://www.1password.dev/cli/get-started/)
2. Enable desktop app + CLI integration in 1Password settings
3. Specify your account shorthand (optional but recommended)
   - Run `op account list` to see available accounts
   - The shorthand is the subdomain of your `x.1password.com` sign-in address

When enabled, if the service account token is empty, the plugin will use the desktop app for authentication. With biometric unlock enabled, this provides a secure and convenient development experience.

:::note
Keep in mind that this method connects as _YOU_ who likely has more access than a tightly scoped service account. Consider only enabling this for non-production secrets.
:::

### Connect server setup (self-hosted)

If you are running a self-hosted [1Password Connect server](https://www.1password.dev/connect/), you can authenticate using a Connect server URL and token:

```env-spec
# @plugin(@varlock/1password-plugin)
# @initOp(connectHost="http://connect-server:8080", connectToken=$OP_CONNECT_TOKEN)
# ---

# @type=opConnectToken @sensitive @internal
OP_CONNECT_TOKEN=
```

**Setup requirements:**

1. Deploy a [1Password Connect server](https://www.1password.dev/connect/get-started/)
2. Create a Connect token with access to the required vault(s)
3. Set the `OP_CONNECT_TOKEN` environment variable

This method uses the Connect server REST API directly — no `op` CLI or 1Password SDK is required.

:::note
The `opLoadEnvironment()` function is not supported with Connect server auth. Use `op()` to read individual items instead.
:::

### Multiple instances

If you need to connect to multiple accounts or vault configurations, register multiple named instances:

```env-spec
# @initOp(id=notProd, token=$OP_TOKEN, allowAppAuth=forEnv(dev), account=acmeco)
# @initOp(id=prod, token=$OP_TOKEN_PROD, allowAppAuth=false)
```

## Reading secrets

This plugin introduces the `op()` function to fetch secret values using 1Password secret references.

```env-spec title=".env.schema"
# @plugin(@varlock/1password-plugin)
# @initOp(token=$OP_TOKEN, allowAppAuth=forEnv(dev), account=acmeco)
# ---

# @type=opServiceAccountToken @sensitive @internal
OP_TOKEN=

# Fetch secrets using 1Password secret references
DB_PASS=op(op://my-vault/database-password/password)
API_KEY=op(op://api-vault/stripe/api-key)
```

### Optional secrets with fallback

Use `allowMissing=true` on `op()` to return `undefined` when an item doesn't exist in 1Password, then combine with `fallback()` to provide a default value:

```env-spec
# If the item exists in 1Password, its value is used.
# If not found, falls back to the default "local-dev-key".
SOME_API_KEY=fallback(op(op://my-vault/some-item/DOES_NOT_EXIST, allowMissing=true), "local-dev-key")
```

You can also set `allowMissing` on `@initOp()` to apply it to all `op()` calls for that instance. It can be a dynamic value, e.g. `forEnv(dev)` to only allow missing items in dev:

```env-spec
# @initOp(token=$OP_TOKEN, allowMissing=forEnv(dev))
# ---

SOME_API_KEY=fallback(op(op://my-vault/some-item/field), "local-dev-key")
```

**How to find a secret reference:**

In 1Password, click on the down arrow icon on any field and select `Copy Secret Reference`.

### Multiple instances

If using multiple plugin instances:

```env-spec
# @initOp(id=dev, token=$OP_TOKEN_DEV, allowAppAuth=true)
# @initOp(id=prod, token=$OP_TOKEN_PROD, allowAppAuth=false)
# ---

DEV_ITEM=op(dev, op://vault-name/item-name/field-name)
PROD_ITEM=op(prod, op://vault-name/item-name/field-name)
```

### Loading 1Password Environments

Use `opLoadEnvironment()` with `@setValuesBulk` to load all variables from a [1Password environment](https://www.1password.dev/environments/) at once:

```env-spec
# @plugin(@varlock/1password-plugin)
# @initOp(token=$OP_TOKEN, allowAppAuth=forEnv(dev), account=acmeco)
# @setValuesBulk(opLoadEnvironment(your-environment-id))
# ---

# @type=opServiceAccountToken @sensitive @internal
OP_TOKEN=

API_KEY=
DB_PASSWORD=
```

With a named instance:

```env-spec
# @initOp(id=prod, token=$OP_TOKEN_PROD, allowAppAuth=false)
# @setValuesBulk(opLoadEnvironment(prod, your-environment-id))
```

> **Note:** When using desktop app auth (`allowAppAuth`), the `op environment` command requires a beta version of the 1Password CLI (v2.33.0+). Download it from the [CLI release history](https://app-updates.agilebits.com/product_history/CLI2) (click "show betas"). Service account auth via the SDK does not have this requirement.

---

## Reference

### Root decorators

#### `@initOp()`

Initialize a 1Password plugin instance - setting up options and authentication. Can be called multiple times to set up different instances.

**Parameters:**

- `token?: string` - Service account token. Should be a reference to a config item of type `opServiceAccountToken`.
- `allowAppAuth?: boolean` - Enable authenticating using the local desktop app (defaults to `false`)
- `account?: string` - Limits the `op` CLI to connect to specific 1Password account (shorthand, sign-in address, account ID, or user ID)
- `connectHost?: string` - URL of a self-hosted 1Password Connect server (e.g., `http://connect-server:8080`)
- `connectToken?: string` - API token for the Connect server. Should be a reference to a config item of type `opConnectToken`. Required when `connectHost` is set.
- `id?: string` - Instance identifier for multiple instances (defaults to `_default`)
- `allowMissing?: boolean` - When `true`, all `op()` calls for this instance will return `undefined` instead of throwing when the referenced item/field is not found. Can be a dynamic value (e.g., `allowMissing=forEnv(dev)` to only allow missing in dev). Other errors (auth failures, bad format, etc.) still throw.
- `cacheTtl?: string | number` - Cache resolved values from `op()` / `opLoadEnvironment()` for the provided TTL (`"5m"`, `"1h"`, `"1d"`, or `"forever"` to cache until manually cleared). Set to `false` (or an empty string) to disable caching. Storage follows global `@cache` mode (`disk` or `memory`) and is disabled when caching is globally disabled or `--skip-cache` is used.

### Functions

#### `op()`

Fetch an individual field using a 1Password secret reference.

**Signatures:**

- `op(secretReference)` - Fetch from default instance
- `op(instanceId, secretReference)` - Fetch from a specific instance
- `op(secretReference, allowMissing=true)` - Return `undefined` if the item is not found
- `op(instanceId, secretReference, allowMissing=true)` - Same, with a specific instance

**Named parameters:**

- `allowMissing?: boolean` - When `true`, returns `undefined` instead of throwing if the referenced item/field is not found. Can be a dynamic value (e.g., `allowMissing=forEnv(dev)`). Useful when combined with `fallback()` to provide a default value. Other errors (auth failures, invalid format, etc.) still throw.

**Secret Reference Format:**

- Format: `op://vault-name/item-name/field-name`
- Example: `op://production/database/password`

#### `opLoadEnvironment()`

Load all variables from a 1Password environment. Intended for use with `@setValuesBulk`.

**Signatures:**

- `opLoadEnvironment(environmentId)` - Load from default instance
- `opLoadEnvironment(instanceId, environmentId)` - Load from a specific instance

### Data Types

- `opServiceAccountToken` - 1Password service account token (sensitive, validated format)
- `opConnectToken` - API token for a self-hosted 1Password Connect server (sensitive)

---

## 1Password Setup

### Vault Organization

If your secrets live in a vault with other sensitive data, create a new vault and move your secrets to it. **1Password's access system is based on vaults, not individual items**.

You can create multiple vaults to segment access to different environments, services, etc.

**Best practices:**
- Have a vault for highly sensitive production secrets
- Have another vault for everything else
- Grant team members access based on their needs
- Follow the [principle of least privilege](https://support.1password.com/business-security-practices/#access-management-and-the-principle-of-least-privilege)

### Create a Service Account

1. Log in to your 1Password web interface (can only be done in web)
2. Navigate to **Service Accounts** → **Create service account**
3. Grant access to necessary vault(s) during creation
4. Copy the service account token (shown only once!)
5. Save the token securely in another vault

:::note
Vault access rules cannot be edited after creation. If your setup changes, create a new service account.
:::

**Access toggle:**
Each vault has a toggle to disable service account access in general. It's on by default. [Learn more](https://www.1password.dev/service-accounts/manage-service-accounts/)

### Rate Limits

Note that [rate limits](https://www.1password.dev/service-accounts/rate-limits/) vary by account type (personal, family, teams, business).

## Troubleshooting

### Secret not found
- Verify the secret reference format is correct: `op://vault/item/field`
- Check that the item exists in the specified vault
- Ensure the field name matches exactly

### Permission denied
- Verify your service account has access to the vault
- Check that the vault allows service account access (toggle in vault settings)
- For desktop app: ensure your user account has access to the vault

### Authentication failed (service account)
- Verify the service account token is correct
- Check if the token has been revoked
- Ensure the service account hasn't been disabled

### Desktop app authentication failed
- Ensure the `op` CLI is installed and in your `$PATH`
- Verify desktop app integration is enabled in 1Password settings
- Check that you specified the correct account (run `op account list`)
- Try running `op whoami` to debug CLI connection

### Connect server errors
- Verify the Connect server URL is correct and the server is running
- Check that the Connect token has access to the required vault(s)
- Ensure the Connect server version supports the vaults and items you're accessing
- Try accessing `<connectHost>/v1/vaults` directly to verify connectivity

### Rate limiting
- Check your account type's rate limits
- Consider implementing caching or reducing request frequency
- For high-volume use cases, consider upgrading to a business account

## Resources

- [1Password](https://1password.com/)
- [Service Accounts](https://www.1password.dev/service-accounts/)
- [1Password Connect](https://www.1password.dev/connect/)
- [1Password CLI](https://www.1password.dev/cli/)
- [Secret References](https://www.1password.dev/cli/secret-references/)
- [Full documentation](https://varlock.dev/plugins/1password/)
