Plugins
Plugins allow extending the functionality of Varlock. Specifically they may introduce new root decorators, item decorators, data types, and resolver functions.
# @plugin(@varlock/1password-plugin) # load + install plugin# @initOp(token=$OP_TOKEN, allowAppAuth=true) # init via custom root decorator# ---# @type=opServiceAccountToken # custom data typeOP_TOKEN=# @sensitiveXYZ_API_KEY=op(op://api-prod/xyz/api-key) # custom resolver functionThis unlocks 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
Section titled “Plugin 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
Section titled “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.
# @plugin(@varlock/a-plugin) # uses the locally installed versionYou can optionally add a version specifier to validate that the installed version satisfies a semver range — but the local version is always what gets loaded. If the installed version doesn’t match the specified range, an error will be thrown.
# @plugin(@varlock/a-plugin@^2.3.4) # validates installed version is >=2.3.4 <3.0.0Standalone binary
Section titled “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.
# @plugin(@varlock/a-plugin@1.2.3) # downloads and caches v1.2.3 from npmPlugin scope
Section titled “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.
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
Section titled “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.
# @initOp(account=acmeco, token=$OP_TOKEN, allowAppAuth=forEnv(dev))# ---# @type=opServiceAccountToken @sensitiveOP_TOKEN=Multiple plugin instances
Section titled “Multiple plugin instances”In secret storage tools, you should segment your data to follow the 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.
# @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 @sensitiveOP_TOKEN_DEV=# @type=opServiceAccountToken @sensitiveOP_TOKEN_PROD=XYZ_API_KEY=op(dev, op://api-creds-dev/xyz/api-key)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.
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.