npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@optique/zod

v1.2.0

Published

Zod value parsers for Optique

Readme

@optique/zod

Zod value parsers for Optique. This package provides seamless integration between Zod schemas and @optique/core, enabling powerful validation and type-safe parsing of command-line arguments.

Installation

deno add jsr:@optique/zod jsr:@optique/run jsr:@optique/core zod
npm  add     @optique/zod     @optique/run     @optique/core zod
pnpm add     @optique/zod     @optique/run     @optique/core zod
yarn add     @optique/zod     @optique/run     @optique/core zod
bun  add     @optique/zod     @optique/run     @optique/core zod

This package supports Zod versions 3.25.0 and above, including Zod v4.

Quick example

The following example uses the zod() value parser to validate an email address.

import { run } from "@optique/run";
import { object } from "@optique/core/constructs";
import { option } from "@optique/core/primitives";
import { zod } from "@optique/zod";
import { z } from "zod";

const cli = run(
  object({
    email: option("--email",
      zod(z.string().email(), { placeholder: "" }),
    ),
  }),
);

console.log(`Welcome, ${cli.email}!`);

Run it:

$ node cli.js --email [email protected]
Welcome, [email protected]!

$ node cli.js --email invalid-email
Error: Invalid email

Common use cases

Email validation

import { option } from "@optique/core/primitives";
import { zod } from "@optique/zod";
import { z } from "zod";

const email = option("--email",
  zod(z.string().email(), { placeholder: "" }),
);

URL validation

import { option } from "@optique/core/primitives";
import { zod } from "@optique/zod";
import { z } from "zod";

const url = option("--url",
  zod(z.string().url(), { placeholder: "" }),
);

Port numbers with range validation

[!IMPORTANT] Always use z.coerce for non-string types, since CLI arguments are always strings.

import { option } from "@optique/core/primitives";
import { zod } from "@optique/zod";
import { z } from "zod";

const port = option("-p", "--port",
  zod(z.coerce.number().int().min(1024).max(65535), { placeholder: 1024 }),
);

Enum choices

import { option } from "@optique/core/primitives";
import { zod } from "@optique/zod";
import { z } from "zod";

const logLevel = option("--log-level",
  zod(z.enum(["debug", "info", "warn", "error"]), { placeholder: "debug" }),
);

Date transformations

import { argument } from "@optique/core/primitives";
import { zod } from "@optique/zod";
import { z } from "zod";

const startDate = argument(
  zod(z.string().transform((s) => new Date(s)), { placeholder: new Date(0) }),
);

Async schemas

Use zodAsync() when a schema depends on async refinements or transforms, and run the containing parser with runAsync() or await run():

import { runAsync } from "@optique/run";
import { object } from "@optique/core/constructs";
import { option } from "@optique/core/primitives";
import { zodAsync } from "@optique/zod";
import { z } from "zod";

async function checkApiKey(value: string): Promise<boolean> {
  return await Promise.resolve(value.startsWith("live_"));
}

const parser = object({
  apiKey: option("--api-key",
    zodAsync(z.string().refine(checkApiKey, "Unknown API key."), {
      placeholder: "",
    }),
  ),
});

const cli = await runAsync(parser);

The zod() helper remains synchronous and rejects schemas that require Zod's async parse path. zodAsync() preserves metavar inference, choices, suggestions, Boolean literal conversion, formatting, and custom errors. Fallback values from bindEnv() or bindConfig() are validated by the same schema before they are accepted.

Async validation can run during fallback resolution and other repeated parser paths, including shell completion requests. Keep remote checks bounded and cached when possible.

Custom error messages

You can customize error messages using the errors option:

import { option } from "@optique/core/primitives";
import { zod } from "@optique/zod";
import { message } from "@optique/core/message";
import { z } from "zod";

const email = option("--email", zod(z.string().email(), {
  placeholder: "",
  metavar: "EMAIL",
  errors: {
    zodError: (error, input) =>
      message`Please provide a valid email address, got ${input}.`
  }
}));

Important notes

Always use z.coerce for non-string types

CLI arguments are always strings. If you want to parse numbers, booleans, or other types, you must use z.coerce:

import { option } from "@optique/core/primitives";
import { zod } from "@optique/zod";
import { z } from "zod";

// ✅ Correct
const port = option("-p", zod(z.coerce.number(), { placeholder: 0 }));

// ❌ Won't work (CLI arguments are always strings)
// const port = option("-p", zod(z.number()));

zod() is synchronous

The zod() helper returns a sync value parser, so async Zod features like async refinements and transforms require zodAsync():

import { option } from "@optique/core/primitives";
import { zod, zodAsync } from "@optique/zod";
import { z } from "zod";

// ❌ Not supported by zod()
const syncEmail = option("--email",
  zod(z.string().refine(async (val) => await checkDB(val)),
    { placeholder: "" }),
);

// ✅ Use zodAsync() and runAsync()
const asyncEmail = option("--email",
  zodAsync(z.string().refine(async (val) => await checkDB(val)),
    { placeholder: "" }),
);

Zod version compatibility

This package supports both Zod v3 (3.25.0+) and Zod v4 (4.0.0+). The basic functionality works identically in both versions.

  • Zod v3: Uses standard error messages from error.issues[0].message
  • Zod v4: Automatically uses prettifyError() when available for better error formatting

Testing with different Zod versions

For contributors and users who want to test compatibility:

# Test with Zod v3
pnpm run test:zod3

# Test with Zod v4
pnpm run test:zod4

# Test with both versions sequentially
pnpm run test:all-versions

For more resources