---
title: Plugins
description: Using plugins with varlock
---

Plugins allow extending the functionality of Varlock. Specifically they may introduce new [root decorators](/reference/root-decorators/), [item decorators](/reference/item-decorators/), [data types](/reference/data-types/), and [resolver functions](/reference/functions/).

```env-spec title="1Password plugin example" "@plugin" "@initOp" "opServiceAccountToken" /op\\(.*\\)/
# @plugin(@varlock/1password-plugin) # load + install plugin
# @initOp(token=$OP_TOKEN, allowAppAuth=true) # init via custom root decorator
# ---
# @type=opServiceAccountToken # custom data type
OP_TOKEN=
# @sensitive
XYZ_API_KEY=op(op://api-prod/xyz/api-key) # custom resolver function
```

This enables use cases like:
- loading values from cloud providers or locally running services
- adding domain-specific validation/coercion logic via custom data types
- generating values dynamically via custom resolver functions

Plugins are authored in TypeScript and can be loaded via local files, or from package registries like npm. Varlock will handle downloading and caching plugins automatically.


## Plugin installation ||installation||

Plugins are loaded using the `@plugin()` root decorator with an npm package name. How you specify the version depends on whether you are in a JavaScript project or using the standalone binary.

### JavaScript projects

In a JavaScript project (where a `package.json` file is present), you must install the plugin as a dependency in your project and load it **without** a version specifier. Varlock will use the version installed in your `node_modules` directory.

```env-spec title=".env.schema"
# @plugin(@varlock/a-plugin) # uses the locally installed version
```

You can optionally add a version specifier to validate that the installed version satisfies a [semver range](https://devhints.io/semver), but the local version is always what gets loaded. If the installed version doesn't match the specified range, an error will be thrown.

```env-spec title=".env.schema"
# @plugin(@varlock/a-plugin@^2.3.4) # validates installed version is >=2.3.4 <3.0.0
```

### Standalone binary

When using the standalone binary (no `package.json` present), you must specify a **fixed version number** (e.g., `1.2.3`). Semver ranges are not supported in this mode. Varlock will automatically download and cache the plugin from npm.

```env-spec title=".env.schema"
# @plugin(@varlock/a-plugin@1.2.3) # downloads and caches v1.2.3 from npm
```

:::caution[Third-party plugin confirmation]
When downloading a third-party (non-`@varlock/*`) plugin for the first time, Varlock will prompt you to confirm before downloading. Once confirmed and cached, subsequent runs will not re-prompt. You can also install the plugin via your `package.json` to skip the prompt entirely.
:::

{/*
INFO TO ADD LATER
- local single file
- local path (monorepo)
- https url
- git
- generic npm/jsr
*/}

## Plugin scope
Plugins are loaded globally, and the additional functionality they provide will be available in all `.env` files in your project. Only a single `@plugin()` decorator is needed to load the plugin, even if multiple files use its functionality. If a plugin is loaded in multiple files, no error will be thrown, as long as they all use the same version.

Note that plugins will not be loaded from an inactive file - for example an environment-specific file that does not match the current environment, or one that uses the [`@disable` root decorator](/reference/root-decorators/#disable).

No specific namespacing or prefixes are enforced, and any naming conflicts will trigger an error, but plugins will use specific names to avoid conflicts.

## Initialization
Plugins are initialized using custom root decorators that they introduce. In some cases, no specific initialization is needed, and in others, you may need to initialize multiple instances of a plugin with different options, referred to by some identifier. How (or if) a plugin needs to be initialized depends on the specific plugin and can depend on the the external service's data/auth model.

A plugin initialization root decorator is used to set IDs, toggle features, and wire up auth. Note that sensitive data should be passed in via references to config items within your schema.

```env-spec title="Plugin initialization example" "@initOp"
# @initOp(account=acmeco, token=$OP_TOKEN, allowAppAuth=forEnv(dev))
# ---
# @type=opServiceAccountToken @sensitive
OP_TOKEN=
```


### Multiple plugin instances
In secret storage tools, you should segment your data to follow the [_principle of least privilege_](https://en.wikipedia.org/wiki/Principle_of_least_privilege), so that different environments/services/devs only have access to the minimal secrets they need. At the very least, this usually means splitting your extra sensitive prod secrets from everything else, but it can be as fine-grained as needed.

We cannot always assume that you won't need access to multiple segments at the same time. In these cases, a plugin may be designed to be initialized multiple times with some kind of id parameter. Resolver functions and decorators can then accept an additional parameter to specify which instance to use.


```env-spec title=".env.schema" "@initOp" /id=[a-z]+/ /\\((dev),/
# @plugin(@varlock/1password-plugin)
# @initOp(id=dev, token=$OP_TOKEN_DEV, allowAppAuth=forEnv(dev))
# @initOp(id=prod, token=$OP_TOKEN_PROD, allowAppAuth=false);
# ---
# @type=opServiceAccountToken @sensitive
OP_TOKEN_DEV=
# @type=opServiceAccountToken @sensitive
OP_TOKEN_PROD=
XYZ_API_KEY=op(dev, op://api-creds-dev/xyz/api-key)
```
```env-spec title=".env.production" /\\((prod)/
XYZ_API_KEY=op(prod, op://api-creds-prod/xyz/api-key)
```
_While the 1Password plugin can be set up using a single instance (using a higher scoped service account for prod) you might want to use multiple instances if you want to make sure you don't accidentally access prod secrets while working locally._

## Usage

Once installed, all decorators, data types, and resolver functions provided by the plugin will be available for use within your `.env` files. These are available globally, and ordering is not important.

Some decorators or resolver functions may require the plugin to be initialized and will throw an error if not set up properly.

Please refer to the specific plugin's documentation for details on usage.

## Plugin caching

Plugins can use varlock's built-in [cache](/guides/caching/) to avoid repeated API calls or hold short-lived tokens. Caching is opt-in - it is only active when the user enables it (typically via a `cacheTtl` init option) and the global cache mode allows it.

Plugin authors get a scoped cache accessor on the plugin instance:

```ts
// primary API - atomic get-or-set
const value = await plugin.cache.getOrSet('some-key', '1h', () => fetchFromApi());

// lower-level helpers
await plugin.cache.get('some-key');
await plugin.cache.set('some-key', value, '1h');
plugin.cache.delete('some-key');
```

Keys are automatically prefixed with `plugin:{name}:`, so plugins cannot collide with each other's cache entries.
