Linter · Rules
bun/zod-prefer-lazy-schema
Disallow building a validation schema while the module loads
A Zod schema at the top level of a module is built as soon as the module is imported, even if you never validate anything with it. With hundreds of schemas, that makes every start slower.
The rule reports a chain of calls that starts with z, where z is imported or global, if it runs while the module loads. That includes static class fields and functions that are called where they're written. A schema inside another schema isn't reported again.
Configure
Off by default. To turn it on:
[lint.rules]
"bun/zod-prefer-lazy-schema" = "error" # or "warn"
Every option, set to its default. The ones that are commented out are unset by default.
[lint.rules]
"bun/zod-prefer-lazy-schema" = ["error", {
roots = ["z"],
methods = [], # list of strings
# factorySuffix = <string>,
# lazyWrapper = <string>,
}]
Examples
Reported:
import { z } from "zod";
export const User = z.object({
name: z.string().min(1),
email: z.string().email(),
});
Not reported:
import { z } from "zod";
let user: z.ZodType | undefined;
export function userSchema() {
return (user ??= z.object({
name: z.string().min(1),
email: z.string().email(),
}));
}
z.lazy() doesn't build anything until it's used, so it's allowed:
import { z } from "zod";
export const Comment = z.lazy(() => z.object({ text: z.string() }));
Options
| Option | Default | What it does |
|---|---|---|
roots | ["z"] | The names that a schema starts with, such as the z in z.object(). |
methods | [] | Methods that build a schema, no matter what they're called on, such as extend. |
factorySuffix | none | Also report calls to functions whose name ends in this string. |
lazyWrapper | none | The name of your helper that builds a schema on first use. The message then shows how to call it. |
import * as v from "valibot";
export const Email = v.pipe(v.string(), v.email());
import { User } from "./user";
export const Admin = User.extend({ level: 1 });
import { createUserSchema } from "./user";
export const User = createUserSchema();
lazyWrapper doesn't change what's reported. A schema in a function that you pass to a helper is allowed with or without it:
import { z } from "zod";
import { once } from "./once";
export const user = once(() => z.object({ name: z.string() }));
When not to use it
In a small program, or a server that starts once and runs for days, the time to build the schemas doesn't matter.