Skip to content

Schema

One of the core features of varlock is its schema-driven approach to environment variables - which is best when shared with your team and committed to version control. We recommend creating a new .env.schema file to hold schema info set by config item decorators, non-sensitive default values, and root decorators to specify global settings that affect varlock itself.

This schema should include all of the environment variables that your application depends on, along with comments and documentation about them, and decorators which affect coercion, validation, and generated types / documentation.

The more complete your schema is, the more validation and coercion varlock can perform, and the more it can help you catch errors earlier in your development cycle.

Running varlock init will attempt to convert an existing .env.example file into a .env.schema file. It must be reviewed, but it should be a good starting point.

The header section of a .env file is a comment block at the beginning of the file that ends with a divider. Within this header, you can use root decorators to specify global settings and default behavior for all config items.

.env.schema
# This is the header, and may contain root decorators
# @envFlag=APP_ENV
# @defaultSensitive=false @defaultRequired=false
# @generateTypes(lang=ts, path=env.d.ts)
# ---
# This is a config item comment block and may contain decorators which affect only the item
# @required @type=enum(dev, test, staging, prod)
APP_ENV=dev

More details:

Config items are the environment variables that your application depends on. Like normal .env syntax, each item is a key-value pair of the form KEY=value. The key is the name of the environment variable, and a value may be specified or not.

While simply enumerating all of them in your .env.schema is useful (like a .env.example file), @env-spec allows us to attach additional comments and item decorators, making our schema much more powerful.

Values may be static, or set using functions, which can facilitate loading values from external sources without exposing any sensitive values.

Quote rules:

  • Static values can be wrapped in quotes or not — all quotes styles (`, ", ') are supported
  • Values wrapped in single quotes do not support expansion
  • Single line values may not contain newlines, but \n will be converted to an actual newline except in single quotes
  • Multiline values can be wrapped in ```, """. Also supported is " and ' but not recommended.
  • Unquoted values will be parsed as a number/boolean/undefined where possible (ITEM=foo -> "foo", while ITEM=true -> true), however data-types may further coerce values
.env.schema
NO_VALUE=
STATIC_VALUE_UNQUOTED=quotes are optional # but are recommended!
STATIC_VALUE_QUOTED="#hashtag" # and are necessary in some cases
FUNCTION_VALUE=exec(`op read "op://api-config/item/credential"`)
MULTILINE_VALUE="""
multiple
lines
"""

Comments are used to attach additional documentation and metadata to config items using item decorators. This additional metadata is used by varlock to perform validation, coercion, and generate types / documentation.

Multiple comment lines directly preceeding an item will be attached to that item. A blank line or a divider (# ---) break a comment block, and detach it from the following config item. Comment lines can either contain regular comments or item decorators. Note that if a line does not start with a decorator, it will be treated as a regular comment.

# description of item can be multiple lines
# this @decorator will be ignored because the line does not start with @
# @sensitive=false @required # decorator lines can end with a comment
# @type=string(startsWith=pk-) # multiple lines of decorators are allowed
SERVICE_X_PUBLISHABLE_KEY=pk-abc123

More details:

varlock supports expansion like other .env tools - such as dotenv-expand. However unlike other tools, it uses function calls to implement this.

  • prefix-${OTHER} -> concat("prefix", ref("OTHER"))
  • ${OTHER} -> ref("OTHER")
  • ${OTHER:-defaultval} -> fallback(ref("OTHER"), "defaultval")
  • $(whoami) -> exec("whoami")

While you could call those functions directly, in many cases it will be more clear to use expansion, or a mix of function calls and expansion. For example:

OP_VAULT_NAME=api-config-prod
MY_KEY=exec(`op read "op://${OP_VAULT_NAME}/service/api-key"`)

More details: