Zod integration
This API is available since Optique 0.7.0.
The @optique/zod package provides seamless integration with Zod, enabling you to use Zod schemas for validating command-line arguments. This allows you to leverage Zod's powerful validation capabilities and reuse existing schemas across your CLI and application code.
deno add jsr:@optique/zod zodnpm add @optique/zod zodpnpm add @optique/zod zodyarn add @optique/zod zodbun add @optique/zod zodBasic usage
The zod() function creates a value parser from any Zod schema:
import { zod } from "@optique/zod";
import { z } from "zod";
// Email validation
const email = zod(z.string().email(), { placeholder: "" });
// Port number with range validation
const port = zod(z.coerce.number().int().min(1024).max(65535), { placeholder: 1024 });
// Enum choices
const logLevel = zod(z.enum(["debug", "info", "warn", "error"]), { placeholder: "debug" });IMPORTANT
The options object is required. In particular, placeholder must be a valid stand-in value of the schema's output type. Optique uses it during deferred prompt resolution, so it does not need to be meaningful user data, but it must be safe for downstream transforms.
String coercion
CLI arguments are always strings, so use z.coerce for non-string types:
// ✅ Correct: Use z.coerce for numbers
const age = zod(z.coerce.number().int().min(0), { placeholder: 0 });
// ❌ Won't work: z.number() expects actual numbers, not strings
const num = zod(z.number(), { placeholder: 0 }); NOTE
Both z.boolean() and z.coerce.boolean() are handled specially: instead of rejecting CLI strings or applying JavaScript truthiness semantics, Optique accepts CLI-friendly literals (true/false, 1/0, yes/no, on/off, case-insensitive).
Transformations
Zod's transformation capabilities work seamlessly with Optique:
// Parse and transform to Date
const startDate = zod(z.string().transform((s) => new Date(s)), { placeholder: new Date(0) });
// Transform to uppercase
const name = zod(z.string().transform((s) => s.toUpperCase()), { placeholder: "" });Custom error messages
Customize error messages for better user experience:
const email = zod(z.string().email(), {
placeholder: "",
metavar: "EMAIL",
errors: {
zodError: (error, input) =>
message`Please provide a valid email address, got ${input}.`
}
});Integration with Optique
Zod parsers work seamlessly with all Optique features:
import { object } from "@optique/core/constructs";
import { option, argument } from "@optique/core/primitives";
import { zod } from "@optique/zod";
import { z } from "zod";
const config = object({
email: option("--email", zod(z.string().email(), { placeholder: "" })),
port: option("-p", "--port", zod(z.coerce.number().int().min(1024).max(65535), { placeholder: 1024 })),
logLevel: option("--log-level", zod(z.enum(["debug", "info", "warn", "error"]), { placeholder: "debug" })),
startDate: argument(zod(z.string().transform((s) => new Date(s)), { placeholder: new Date(0) })),
});Version compatibility
The @optique/zod package supports both Zod v3 (3.25.0+) and Zod v4 (4.0.0+):
- Zod v3: Uses standard error messages from
error.issues[0].message - Zod v4: Automatically uses
prettifyError()when available for better error formatting
Limitations
Async refinements not supported: Since Optique's parsing is synchronous, async Zod features like
refine(async ...)cannot be used. Async boolean schemas are detected when a valid boolean literal is parsed ("true","false", etc.) and throw aTypeError; unrecognized inputs like"maybe"return a normal validation error instead. Perform async validation after parsing if needed.Boolean parsing in unions: The CLI-friendly boolean parsing (accepting
true/false,1/0,yes/no,on/off) applies only when the entire schema is recognized as a boolean type. For unions that are not recognized as wholly boolean, arm precedence is preserved and parsing follows Zod's native union/coercion behavior.
The Zod integration provides a powerful way to reuse validation logic across your entire application while maintaining full type safety and excellent error messages.