Define behavior in specific environments

Configuration files let you define default behavior for your application, and override it for specific environments. This allows you to make changes without affecting deployments in other environments.

Encore supports configuration files written in CUE, which is a superset of JSON. It adds the following:

  • C-style comments
  • Quotes may be omitted from field names without special characters
  • Commas at the end of fields are optional
  • A comma after last element in list is allowed
  • The outer curly braces on the file are optional
  • Expressions such as interpolation, comprehensions and conditionals are supported.
Take care

For sensitive data use Encore's secrets management functionality instead of configuration.

Using Config

Inside your service, you can call config.Load[*SomeConfigType]() to load the config. This must be done at the package level, and not inside a function. See more in the package documentation.

Here's an example implementation:

package mysvc import ( "" ) type SomeConfigType struct { ReadOnly config.Bool // Put the system into read-only mode Example config.String } var cfg *SomeConfigType = config.Load[*SomeConfigType]()

The type you pass as a type parameter to this function will be used to generate a encore.gen.cue file in your services directory. This file will contain both the CUE definition for your configuration type, and some metadata that Encore will provide to your service at runtime. This allows you to change the final value of your configuration based on the environment the application is running in.

Any files ending with .cue in your service directory or sub-directories will be loaded by Encore and given to CUE to unify and compute a final configuration.

Example CUE files
// Code generated by encore. DO NOT EDIT. package mysvc #Meta: { APIBaseURL: string Environment: { Name: string Type: "production" | "development" | "ephemeral" | "test" Cloud: "aws" | "gcp" | "encore" | "local" } } #Config: { ReadOnly: bool // Put the system into read-only mode Example: string } #Config
Please note

Loading configuration is only supported in services and the loaded data can not be referenced from packages outside that service.

CUE tags in Go Structs

You can use the cue tag in your Go to specify additional constraints on your configuration. For example:

type FooBar { A int `cue:">100"` B int `cue:"A-50"` // If A is set, B can be inferred by CUE C int `cue:"A+B"` // Which then allows CUE to infer this too } var _ = config.Load[*FooBar]()

Will result in the following CUE type definition being generated:

#Config: { A: int & >100 B: int & A-50 // If A is set, B can be inferred by CUE C: int & A+B // Which then allows CUE to infer this too }

Config Wrappers

Encore provides type wrappers for config in the form of config.Value[T] and config.Values[T] which expand into functions of type T and []T respectively. These functions allow you to override the default value of your configuration in your CUE files inside tests, where only code run from that test will see the override.

In the future we plan to support real-time updating of configuration values on running applications, thus using these wrappers in your configuration today will future proof your code and allow you to automatically take advantage of this feature when it is available.

Any type supported in API requests and responses can be used as the type for a config wrapper. However for convenience, Encore ships with the following inbuilt aliases for the config wrappers:

  • config.String, config.Bool, config.Int, config.Uint, config.Int8, config.Int16, config.Int32, config.In64, config.Uint8, config.Uint16, config.Uint32, config.Uint64, config.Float32, config.Float64, config.Bytes, config.Time, config.UUID
Example Application using Wrappers
type mysvc import ( "" ) type Server struct { // The config wrappers do not have to be in the top level struct Enabled config.Bool Port config.Int } type SvcConfig struct { GameServerPorts config.Values[Server] } var cfg = config.Load[*SvcConfig]() func startServers() { for _, server := range cfg.GameServerPorts() { if server.Enabled() { go startServer(server.Port()) } } } func startServer(port int) { // ... }

Provided Meta Values

When your application is running, Encore will provide information about that environment to your CUE files, which you can use to filter on. These fields can be found in the encore.gen.cue file which Encore will generate when you add a call to load config. Encore provides the following meta values:

  • APIBaseURL: The base URL of the Encore API, which can be used to make API calls to the application.
  • Environment: A struct containing information about the environment the application is running in.
       Name: The name of the environment
       Type: One of production, development, ephemeral or test.
       Cloud: The cloud the app is running on, which is one of aws, gcp, encore or local.

The following are useful conditionals you can use in your CUE files:

// An application running due to `encore run` if #Meta.Environment.Type == "development" && #Meta.Environment.Cloud == "local" {} // An application running in a development environment in the Cloud if #Meta.Environment.Type == "development" && #Meta.Environment.Cloud != "local" {} // An application running in a production environment if #Meta.Environment.Type == "production" {} // An application running in an environment that Encore has created // for an open Pull Request on Github if #Meta.Environment.Type == "ephemeral" {}

Testing with Config

Through the provided meta values, your applications configuration can have different values in tests, compared to when the application is running. This can be useful to prevent external side effects from your tests, such as emailing customers across all test.

Sometimes however, you may want to test specific behaviours based on different configurations (such as disabling user signups), in this scenario using the Meta data does not give you fine enough control. To allow you to set a configuration value at a per test level, Encore provides the helper function et.SetCfg. You can use this function to set a new value only in the current test and any sub tests, while all other tests will continue to use the value defined in the CUE files.

// By default we want to sent emails SendEmails: bool | *true // But in all tests we want to disable emails if #Meta.Environment.Type == "test" { SendEmails: false }

Useful CUE Patterns

If you're new the CUE, we'd recommend checking out the CUE documentation and cuetorials, however to get you started, here are some useful patterns you can use in your CUE files.

CUE supports the concept of a default value, which it will use if no other concrete value is provided. This can be useful for when you normally want one value, but occasionally might want to provide an override in a certain scenario. A default value is specified by prefixing it with a *.

// ReadOnlyMode is a boolean and if we don't provide a value, it // will default to false. ReadOnlyMode: bool | *false if #Meta.Environment.Name == "old-prod" { // On this environment, we want to set ReadOnlyMode to true ReadOnlyMode: true }

Any field prefixed with an _ will not be exported to the concrete configuration once evaluated by CUE and can be used to hold intermediate values. Because CUE allows you to define the same field as many times as you want, as long as the values unify, we can build complex validation logic.

import ( "list" // import CUE's list package ) // Set some port numbers defaulting just to 8080 // but in development including 8443 portNumbers: [] | *[8080] if #Meta.Environment.Type == "development" { portNumbers: [8080, 8443] } // Port numbers must be an array and all values // are integers 1024 or above. portNumbers: [ & >= 1024] // The ports are considered valid if they contain the port number 8080. _portsAreValid: list.Contains(portNumbers, 8080) // Ensure that the ports are valid by constraining the value to be true. // CUE will report an error if the value is false (that is if the portNumbers list // does not contain the value 8080). _portsAreValid: true

If statements in CUE do not have else branches, which can make it difficult to write complex conditionals, we however can use an array to emulate a switch statement, where the first value that matches the condition is returned. The following example will set SendEmailsFrom to a single string.

SendEmailsFrom: [ // These act as individual case statements if #Meta.Environment.Type == "production" { "[email protected]" }, if #Meta.Environment.Name == "staging" { "[email protected]" }, // This last value without a condition acts as the default case "[email protected]", ][0] // Return the first value which matches the condition

CUE allows us to extract map keys and use them as values to simply the config we need to write and minimize duplication.

// Define the type we want to use #Server: { server: string port: int & > 1024 enabled: bool | *true } // Specify that servers is a map of strings to #Server // where they key we assign the the variable Name servers: [Name=string]: #Server & { // Then we union the key with the value of server server: Name } servers: { "Foo": { port: 8080 }, "Bar": { port: 8081 enabled: false }, }

This will result in the concrete configuration of:

{ "servers": { "Foo": { "server": "Foo", "port": 8080, "enabled": true }, "Bar": { "server": "Bar", "port": 8081, "enabled": false } } }