Skip to content

Schema Defintion Rules

@config-store relies on Zod schema to provide default values.

This means that the schema must be able to accept an empty initial value (e. g. null or undefined) and parse it into a default config.

Here’s how to achieve that, assuming you store settings in an object.

If your adapter receives undefined as an empty initial value, then you must attach .prefault({}) to your outmost z.object().

const MySettingsSchema = z.object().prefault({}); // Converts initial `undefined` value to `{}`
MySettingsSchema.parse(undefined); // => {}

If your adapter receives null as an empty initial value, then you must wrap the entire schema with .preprocess(), converting null into an empty object {}.

Also handles undefined!

const MySettingsSchema = z.preprocess(
(config: unknown) => config ?? {},
z.object() // You don't need `.prefault({})` on root level when using `.preprocess()`
);
MySettingsSchema.parse(null); // => {}

You must attach .default() to every primitive property.

const MySettingsSchema = z.object({
menuExpanded: z.boolean().default(true),
darkTheme: z.boolean().default(false),
});
MySettingsSchema.parse({}); // => {menuExpanded: true, darkTheme: false}

You must attach .prefault({}) to every nested object.

const MySettingsSchema =
z.object({
nestedSettings: z.object().prefault({}),
})
);
MySettingsSchema.parse({}); // => {nestedSettings: {foo: 'bar', baz: 'zomg'}}

Now you’re ready to build your schema:

const MySettingsSchema = z
.object({
menuExpanded: z.boolean().default(true),
darkTheme: z.boolean().default(false),
nestedSettings: z
.object({
foo: z.string().default('bar'),
baz: z.literal(['quux', 'zomg', 'lol']).default('zomg'),
})
.prefault({}),
})
.prefault({});
MySettingsSchema.parse(undefined); // =>
/**
* {
* menuExpanded: true,
* darkTheme: false,
* nestedSettings: {
* foo: 'bar',
* baz: 'zomg'
* }
* }
*/