Migrate to v3
Layerfig v3 consolidates several learnings from migrating legacy projects to use Layerfig, making configuration more flexible and easier to maintain.
New Features
Section titled “New Features”Self-Referencing Slots
Section titled “Self-Referencing Slots”Previously, if you wanted to reuse the same slot value in multiple places, you had to repeat the logic:
{
"port": "${PORT:-3000}",
"host": "localhost:${PORT:-3000}" // repeating port logic
}
While this worked, it wasn’t convenient.
With self-referencing slots, you can now reference a value defined in the same configuration:
{
"port": "${PORT:-3000}",
"host": "localhost:${self.port}"
}
Zod Submodules
Section titled “Zod Submodules”Layerfig now exports two new submodules:
@layerfig/config/zod
→ Zod v4@layerfig/config/zod-mini
→ Zod v4 Mini
These are useful if you want to:
- Keep your schema in a separate file
- Use Zod across your application without importing it from the main module
Breaking Changes
Section titled “Breaking Changes”Remove configFolder
Section titled “Remove configFolder”Before, if you wanted to point out a different config folder to Layerfig search for files, you would use the configFolder
option:
import path from "node:path";
const config = new ConfigBuilder({
validate: (finalConfig) => schema.parse(finalConfig),
configFolder: "./path/to/config-folder",
})
.addSource(new FileSource("base.json"))
.build();
The problem was that internally, Layerfig would use process.cwd()
and resolve to the folder you’ve specified but in some cases this caused issues.
Now, if you want to change the default folder (or have a different one based on the environment), you can use the absoluteConfigFolderPath
option:
import path from "node:path";
const config = new ConfigBuilder({
validate: (finalConfig) => schema.parse(finalConfig),
absoluteConfigFolderPath: path.resolve(
process.cwd(),
"./path/to/config-folder"
),
})
.addSource(new FileSource("base.json"))
.build();
Please notice that as the name states, this option expects an absolute path to the folder where Layerfig should look for configuration files. If not it will throw an error.
Custom Parser API
Section titled “Custom Parser API”In v2, creating a custom configuration parser looked like this:
import { defineConfigParser } from "@layerfig/config";
export const iniParser = defineConfigParser({
acceptedFileExtensions: ["ini"],
parse: (fileContent) => {
// Logic to fetch, read, and parse the content
// Should return a Result
},
});
In v3, you now need to create a class that extends ConfigParser
:
// ./path/to/custom-parser.ts
import { ConfigParser } from "@layerfig/config";
class IniParser extends ConfigParser {
constructor() {
super({
acceptedFileExtensions: ["ini"],
});
}
load(fileContent: string) {
// Logic to fetch, read, and parse the content
// Should return a Result
}
}
export const iniParser = new IniParser();
This change improves internal checks and validations within Layerfig.
Client vs. Server Modules
Section titled “Client vs. Server Modules”To better separate client and server code, Layerfig now provides a dedicated client submodule:
import { ConfigBuilder } from "@layerfig/config/client";
All exports from the client
submodule are safe for browser environments.
The included z
instance is zod/mini
, helping reduce your client bundle size.
For server-side code, you can still import everything from the main module:
import { ConfigBuilder } from "@layerfig/config";
Slots syntax
Section titled “Slots syntax”In v2, slots could be defined in a few ways:
$PORT
=> it would getprocess.env.PORT
${APP_PORT:PORT}
=> it would try to resolveprocess.env.APP_PORT
and if not found,process.env.PORT
${APP_PORT:PORT:3000}
=> same but if both not found, fallback to3000
${self.port}
=> reference to the.port
value of the config
This strategy made unnecessarily complex and error prone to define what is a slot and split the values inside.
Now, every slot needs to be wrapped by ${}
(or defining your own slotPrefix, e.g., #{}
, @{}
, etc.).
-port: $PORT
+port: ${PORT}
Also, when channing values, instead using single colon (:
) to separate the values, you must use double colon (::
):
-port: ${APP_PORT:PORT:-3000}
+port: ${APP_PORT::PORT::-3000}
This refactor now allow you to specify more complex cases, such as multiple slots in the same value:
host:
${HOST::-localhost}:${self.port::-3000}
# 👆🏽 gets resolved to `localhost:3000` if neither `HOST` nor `self.port` are defined
Other Changes
Section titled “Other Changes”- User options are now validated with Zod (Mini)
- Zod upgraded from v3 to v4