# What is a Parsing Extension?

Since v2, App Config has a built-in abstraction for parsing files in special ways. We call these parsing extensions because they change the way that values themselves are parsed. There are default extensions, but even these can be disabled if need be.

Generally, the answer to the question is that extensions are "value transformers". App Config visits every property of parsed config files and asks each extension if anything should be done to it.

# The $env Directive

Selects values based on the current APP_CONFIG_ENV, NODE_ENV or ENV (in that order). Falls back to the default key if there is no current environment, or if a value for it is not present.

apiUrl:
  $env:
    default: 'http://localhost:3000'
    qa: 'https://qa.app.example.com'
    staging: 'https://staging.app.example.com'
    production: 'https://app.example.com'

This structure is flattened. The example above would resolve (with no environment) to:

apiUrl: 'http://localhost:3000'

If $env values are objects, they can be merged.

api:
  $env:
    dev:
      url: 'http://localhost:3000'
    qa:
      url: 'https://qa.app.example.com'

  timeout: 5000

This will safely resolve to an object { url: string, timeout: number } by merging sibling keys.

Note that $env is aware of environment name aliases as well. See meta file for how to change the environmentSourceNames or environmentAliases.

# The $extends Directive

Merges (deeply) values from another file. The current file has precedent when merging.

# Use it with a file path
$extends: './other-file.yml'

# Or use it with options
nested:
  propertyA:
    $extends:
      path: ./other-file.yml
      optional: true
      select: 'foo.bar'

# An array also works
$extends:
  - ./file1.yml
  - ./file2.yml

This option can be given a filepath, an object with options, or an array of objects.

select allows extending a property from the other file, and optional allows missing files.

To read another file with a different environment, simply use the env option:

$extends:
  path: ./other-file.yml
  env: staging

This will load the file as if you were in staging, even though the current environment might be something else.

# The $override Directive

The override directive is exactly the same as $extends, except that the other file takes precedent when merging. This is useful for the pattern:

# if present, the values in this file will override the ones here
$override:
  path: /etc/my-app/config.yml
  optional: true

# ... default values

# The $envVar Directive

Pulls in an environment variable.

username:
  $envVar: USER

hostname:
  $envVar:
    name: HOSTNAME
    allowNull: true

Use parseInt, parseFloat or parseBool option alongside the name to parse values from the env string.

# The $substitute Directive

Allows environment variables to be used in strings. Very similar to $envVar, but allows embedding variables in strings.

apiUrl:
  $env:
    dev: { $substitute: 'http://${MY_IP:-localhost}:3000' }
    qa: 'https://qa.app.example.com'

username: { $substitute: '$USER' }

Works essentially the same way as bash variable substitution (opens new window) (including fallback syntax).

The other form of substitution is via name:

username:
  $substitute:
    # Look for $USER, use 'no-user' as fallback value
    name: 'USER'
    fallback: 'no-user'

Use allowNull: true if your fallback is allowed to be nullable.

# Decryption

Values that are encrypted will automatically be decrypted. These values come in the form of enc:{revision}:{base64}. Because this string format is unique, App Config can guarantee that it's not a plain string value.

The user will be required to unlock their keychain if the secret-agent isn't running.

password: 'enc:1:wy4ECQMI/o7E3nw9SP7g3VsIMg64HGIcRb9HyaXpQnyjozFItwx4HvsP1D2plP6Y0kYBAr2ytzs2v5bN+n2oVthkEmbrq8oqIqCF3Cx+pcjJ+5h+SyxQuJ7neNp4SRtnD4EK32rPJpyDMeHG4+pGwIjFuSH1USqQ=SZWR'

# The $if and $try Directives

Performs basic control flow.

$try:
  $value:
    $extends: ./other-file.yml
  $fallback: {}

Use $unsafe: true if you want to catch any error. Some "safe" errors are caught by default.

$if:
  $check: true
  $then: foo
  $else: bar

The $if directive is straightforward, see the example.

# The $eq Directive

Checks if two values are equal (using deep equality).

$if:
  $check:
    $eq:
      - 42
      - 42
  $then: foo
  $else: bar

# The $hidden Directive

You can use $hidden anywhere - the values underneath it are not processed directly. This is useful for shared values.

$hidden:
  foo: 42

bar:
  baz:
    $extendsSelf: '$hidden.foo'

qux:
  $extendsSelf: '$hidden.foo'

This results in { bar: { baz: 42 }, qux: 42 }.

# The $timestamp Directive

Allows injecting the current date and time into configuration. While this option is useful, it does of course come at the cost of non-determinism.

buildDate:
  $timestamp: true

By default, this will resolve as buildDate: new Date().toISOString(). Options can also be passed to toLocaleDateString().

buildDate:
  $timestamp:
    locale: en-US
    weekday: long
    year: numeric
    month: long
    day: numeric

Note that the time here is entirely determined by when App Config runs. That might be deploy time, it might be build time, or it might be run time.

# The $git Directive

To retrieve the current git revision or branch name, use the $git directive. Do note that this means your config is not deterministic.

builtRevision:
  $git: commit # use this for the full length commit hash
  $git: commitShort # use this for the short commit hash
  $git: branch # use this for the current branch name

Note that this directive will not fail if git status is not clean.

# Custom Extensions

It's not all too difficult to make your own!

import { loadConfig, defaultExtensions, ParsingExtension } from '@app-config/main';

const uppercaseExtension: ParsingExtension = (value, [_, key]) => {
  if (key === '$uppercase') {
    return (parse) => parse(value.toUpperCase());
  }

  return false;
};

const config = await loadConfig({
  parsingExtensions: defaultExtensions().concat(uppercaseExtension),
});

In essence, every parsing extension is called for every value that's seen in configuration. That includes every primitive value, all object keys, and array iteration. This process is top-down, which you might not expect. That means that visitors are called on root-level properties first, and descends recursively. Extensions descend via opt-in (the parse function).

App Config uses the same public interface for its built-in extensions, so if you're looking for examples there are a few in the codebase.

We'd like to document this more. Custom resolvers and value transformations have a lot of potential.

# Loading Custom Extensions

App Config will look at the meta file for "extra" parsing extensions to use.

For example:

parsingExtensions:
  - '@app-config/vault'

You can pass options to the extensions as well, by making array items objects.

parsingExtensions:
  - name: '@app-config/vault'
    options:
      address: 'http://localhost:8200'

These parsing extensions are used for loading config files and secret files. They do not take effect when parsing $APP_CONFIG.