{"id":13903188,"url":"https://github.com/ndruger/zod-opts","last_synced_at":"2026-01-12T02:31:37.405Z","repository":{"id":144081544,"uuid":"615467492","full_name":"ndruger/zod-opts","owner":"ndruger","description":"Command-line argument parsing and validation with Zod.","archived":false,"fork":false,"pushed_at":"2024-06-28T19:59:53.000Z","size":248,"stargazers_count":11,"open_issues_count":2,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2024-10-31T12:20:51.104Z","etag":null,"topics":["command-line","option-parser","option-valuation","zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ndruger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-17T19:06:47.000Z","updated_at":"2024-10-30T20:43:38.000Z","dependencies_parsed_at":"2024-02-10T11:28:48.675Z","dependency_job_id":null,"html_url":"https://github.com/ndruger/zod-opts","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndruger%2Fzod-opts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndruger%2Fzod-opts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndruger%2Fzod-opts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndruger%2Fzod-opts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ndruger","download_url":"https://codeload.github.com/ndruger/zod-opts/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226320948,"owners_count":17606379,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["command-line","option-parser","option-valuation","zod"],"created_at":"2024-08-06T22:01:48.545Z","updated_at":"2026-01-12T02:31:37.399Z","avatar_url":"https://github.com/ndruger.png","language":"TypeScript","funding_links":[],"categories":["command-line"],"sub_categories":[],"readme":"# ZodOpts\n\n[![NPM Version](http://img.shields.io/npm/v/zod-opts.svg?style=flat)](https://www.npmjs.org/package/zod-opts)\n[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)\n[![CI](https://github.com/ndruger/zod-opts/actions/workflows/ci.yml/badge.svg)](https://github.com/ndruger/zod-opts/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/ndruger/zod-opts/branch/master/graph/badge.svg?token=TX7GCMBT8U)](https://codecov.io/gh/ndruger/zod-opts)\n[![Minified size](https://img.shields.io/bundlephobia/min/zod-opts)](https://bundlephobia.com/package/zod-opts)\n\nA library that simplifies the process of parsing and validating command-line arguments using the [Zod](https://github.com/colinhacks/zod) validation library\n\n\u003c!-- TOC --\u003e\n\n- [ZodOpts](#zodopts)\n  - [Installation](#installation)\n  - [Zod Compatibility](#zod-compatibility)\n  - [Quick Start](#quick-start)\n  - [Options](#options)\n    - [Various option types](#various-option-types)\n      - [boolean types](#boolean-types)\n        - [negatable boolean](#negatable-boolean)\n      - [enum types](#enum-types)\n      - [array types](#array-types)\n        - [array option](#array-option)\n        - [array positional arguments](#array-positional-arguments)\n    - [Custom validation](#custom-validation)\n    - [Variadic arguments](#variadic-arguments)\n  - [Commands](#commands)\n  - [Help](#help)\n  - [Version](#version)\n  - [Advanced Usage](#advanced-usage)\n    - [Reuse Zod object type](#reuse-zod-object-type)\n  - [Future work ideas](#future-work-ideas)\n\n\u003c!-- /TOC --\u003e\n\n## Installation\n\n```bash\nnpm install zod-opts # npm\n\nyarn add zod-opts # yarn\n```\n\n## Zod Compatibility\n\nzod-opts supports both Zod v3 (3.25.0+) and Zod v4.\n\n### Behavioral Differences\n\nThere is a behavioral difference between Zod v3 and v4 when using `.default().optional()`:\n\n```ts\nconst schema = z.string().default(\"foo\").optional();\n\n// Zod v3: schema.parse(undefined) → undefined\n// Zod v4: schema.parse(undefined) → \"foo\"\n```\n\n| Pattern                                | Zod v3      | Zod v4      |\n| -------------------------------------- | ----------- | ----------- |\n| `z.string().optional()`                | `undefined` | `undefined` |\n| `z.string().default(\"foo\")`            | `\"foo\"`     | `\"foo\"`     |\n| `z.string().optional().default(\"foo\")` | `\"foo\"`     | `\"foo\"`     |\n| `z.string().default(\"foo\").optional()` | `undefined` | `\"foo\"`     |\n\nIn Zod v4, `.default()` always applies regardless of `.optional()`. If you're migrating from v3 to v4, review any usage of `.default().optional()` as the behavior has changed.\n\n**Recommendation**: Use `.default(\"foo\")` alone if you want a default value. The `.optional()` suffix is redundant in Zod v4.\n\n### Changes introduced for Zod v4 support\n\n- Zod schema inspection now uses the compatibility layer to recognize v4-specific shapes (e.g., enums defined via `entries`, arrays with `element`), so more v4 schemas are accepted without code changes.\n- Optional/default handling is stricter: defaults wrapped in `optional`/`effects`/`pipe`/`nullable`/`readonly` are resolved before building option metadata, matching Zod v4 behavior.\n- Unknown commands/options/positionals now surface clear `ParseError` messages early; validation no longer silently assumes definitions exist. This improves feedback for mis-typed flags when running under either Zod v3 or v4.\n\n## Quick Start\n\nFile: [simple.ts](./example/simple.ts)\n\n```ts\nimport { z } from \"zod\";\nimport { parser } from \"zod-opts\";\n\nconst parsed = parser()\n  .options({\n    option1: {\n      type: z.boolean().default(false),\n      alias: \"a\",\n    },\n    option2: {\n      type: z.string(),\n    },\n  })\n  .parse(); // same with .parse(process.argv.slice(2))\n\n// parsed is inferred as { option1: boolean, option2: string }\nconsole.log(parsed);\n```\n\n```bash\n# Valid options\n$ node simple.js --option1 --option2=str  # or `node simple.js -a --option2 str`\n{ option1: true, option2: 'str' }\n\n# Help\n$ node simple.js --help\nUsage: simple.js [options]\n\nOptions:\n  -h, --help              Show help\n  -a, --option1           (default: false)\n      --option2 \u003cstring\u003e                    [required]\n\n# Invalid options show help and make exit(1)\n$ node simple.js\nRequired option is missing: option2\n\nUsage: simple.js [options]\n\nOptions:\n  -h, --help              Show help\n  -a, --option1           (default: false)\n      --option2 \u003cstring\u003e                    [required]\n```\n\nFile: [complex.ts](./example/complex.ts)\n\n```ts\nimport { z } from \"zod\";\n// import { parser } from \"zod-opts\";\nimport { parser } from \"../src/index\";\n\nconst parsed = parser()\n  .name(\"scriptA\") // script name on Usage\n  .version(\"1.0.0\") // version on Usage\n  .options({\n    option1: {\n      // if default() is specified, it will be optional option.\n      type: z.string().describe(\"description of option\").default(\"default\"),\n      argumentName: \"NameA\", // used in Usage.\n    },\n    option2: {\n      type: z\n        .string()\n        .regex(/[a-z]+/) // you can use zod's various methods.\n        .optional(), // if optional() is specified, it will be optional option.\n    },\n    option3: {\n      type: z.number().min(5), // accepts only number and greater than 5.\n    },\n    option4: {\n      type: z.enum([\"a\", \"b\", \"c\"]).default(\"b\"), // accepts only \"a\", \"b\", \"c\" and default is \"b\".\n    },\n  })\n  .args([\n    {\n      // And required arguments.\n      name: \"arg1\",\n      type: z.string(),\n    },\n  ])\n  .parse();\n\n// parsed is inferred as below.\n// const parsed: {\n//   option1: string;\n//   option2?: string | undefined;\n//   option3: number;\n//   option4: \"a\" | \"b\" | \"c\";\n//   arg1: string;\n// }\nconsole.log(parsed);\n```\n\n```bash\n# Valid options\n$  node complex.js --option3=10 arg_str\n{\n  option1: 'default',\n  option2: undefined,\n  option3: 10,\n  option4: 'b',\n  arg1: 'arg_str'\n}\n\n# Help\n$  node complex.js --help\nUsage: scriptA [options] \u003carg1\u003e\n\nArguments:\n  arg1    [required]\n\nOptions:\n  -h, --help              Show help\n  -V, --version           Show version\n      --option1 \u003cNameA\u003e   description of option (default: \"default\")\n      --option2 \u003cstring\u003e\n      --option3 \u003cnumber\u003e                                              [required]\n      --option4 \u003cstring\u003e  (choices: \"a\", \"b\", \"c\") (default: \"b\")\n\n# Version\n$  node complex.js --version\n1.0.0\n```\n\n## Options\n\n### Various option types\n\n#### boolean types\n\n- .options() supports boolean type\n- .args() DOES NOT support boolean type\n\nFile: [boolean.ts](./example/boolean.ts)\n\n```ts\nconst parsed = parser()\n  .options({\n    option1: {\n      type: z.boolean(), // required option. type is boolean\n    },\n    option2: {\n      type: z.boolean().default(false), // optional option. type is boolean\n    },\n    option3: {\n      type: z.boolean().optional(), // optional option. type is boolean|undefined\n    },\n    option4: {\n      type: z.boolean().default(false).optional(), // optional option. type is boolean|undefined\n    },\n  })\n  .parse();\n\n// parsed is inferred as below:\n// const parsed: {\n//     option1: boolean;\n//     option2: boolean;\n//     option3?: boolean;\n//     option4?: boolean;\n// }\n```\n\n##### negatable boolean\n\nYou can use '--no-' prefix to set false(ex. `--no-option1`).\n\n```ts\nconst parsed = parser()\n  .options({\n    option1: {\n      type: z.boolean().default(true),\n    },\n  })\n  .parse();\nconsole.log(parsed);\n```\n\n```bash\n$ node script.js --no-option1\n{ option1: false }\n```\n\n#### enum types\n\n- .options() supports enum type\n- .args() supports enum type\n\nFile: [enum.ts](./example/enum.ts)\n\n```ts\nconst parsed = parser()\n  .options({\n    option1: {\n      type: z.enum([\"a\", \"b\"]), // required option. type is \"a\"|\"b\"\n    },\n    option2: {\n      type: z.enum([\"a\", \"b\"]).default(\"b\"), // optional option. type is \"a\"|\"b\"\n    },\n    option3: {\n      type: z.enum([\"a\", \"b\"]).optional(), // optional option. type is \"a\"|\"b\"|undefined\n    },\n    option4: {\n      type: z.enum([\"a\", \"b\"]).default(\"b\").optional(), // optional option. type is \"a\"|\"b\"|undefined\n    },\n  })\n  .args([\n    {\n      name: \"position1\",\n      type: z.enum([\"a\", \"b\"]), // required arg. type is \"a\"|\"b\"\n    },\n  ])\n  .parse();\n\n// parsed is inferred as below:\n// const parsed: {\n//   option1: \"a\" | \"b\";\n//   option2: \"a\" | \"b\";\n//   option3?: \"a\" | \"b\";\n//   option4?: \"a\" | \"b\";\n//   position1: \"a\" | \"b\";\n// };\nconsole.log(parsed);\n```\n\n#### array types\n\n- .options() supports array type\n- .args() supports array type\n\n##### array option\n\nCAUTION: `program --opt opt_arg1 opt_arg2 pos_arg` will be treated as `opt=['opt_arg1' 'opt_arg2' 'pos_arg']`.\nIn this case, the user should use `program --opt opt_arg1 opt_arg2 -- pos_arg`.\n\nFile: [array_option.ts](./example/array_option.ts)\n\n```ts\nconst parsed = parser()\n  .options({\n    opt: {\n      type: z.array(z.string()), // required option. type is string[]\n      //   type: z.array(z.string()).default([]), // optional arg. type is string[] and default is []\n    },\n  })\n  .parse();\n\n// parsed is inferred as below:\n// const parsed: {\n//   opt: string[];\n// };\nconsole.log(parsed);\n```\n\n```bash\n# Valid options\n$ node array_option.js --opt str1 str2\n{ opt: [ 'str1', 'str2' ] }\n\n# Invalid options (empty array is not permitted. use `.default([])` instead).\n$ node array_option.js --opt\nOption 'opt' needs value: opt\n\nUsage: array_option.js [options]\n\nOptions:\n  -h, --help              Show help\n      --opt \u003cstring ...\u003e             [required]\n```\n\n##### array positional arguments\n\nFile: [array_argument.ts](./example/array_argument.ts)\n\n```ts\nconst parsed = parser()\n  .args([\n    {\n      name: \"pos\",\n      type: z.array(z.string()), // required arg. type is string[]\n      //   type: z.array(z.string()).default([]), // optional arg. type is string[] and default is []\n    },\n  ])\n  .parse();\n\n// parsed is inferred as below:\n// const parsed: {\n//   pos: string[];\n// };\nconsole.log(parsed);\n```\n\n```bash\n# Valid options\n$ node array_argument.js str1 str2\n{ pos: [ 'str1', 'str2' ] }\n\n# Invalid options (empty array is not permitted. use `.default([])` instead).\n$ node array_argument.js\nRequired argument is missing: pos\n\nUsage: array_argument.js [options] \u003cpos ...\u003e\n\nArguments:\n  pos    [required]\n\nOptions:\n  -h, --help  Show help\n```\n\n### Custom validation\n\nYou can use Zod's `.refine()` method to validate each option(e.g. `z.string().refine((v) =\u003e v === \"foo\" || v === \"bar\", {message: \"option1 must be foo or bar\"}`).\n\nIf you want to check combinations of options, you can use `.validation()` method. `.validation()` registers the custom validation function. And the function is called after default validation.\n\nFile: [custom_validation.ts](./example/custom_validation.ts)\n\n```ts\nconst parsed = parser()\n  .options({\n    option1: {\n      type: z.number(),\n    },\n    option2: {\n      type: z.number(),\n    },\n  })\n  .validation((parsed) =\u003e {\n    if (parsed.option1 === parsed.option2) {\n      throw Error(\"option1 and option2 must be different\"); // or return \"option1 and option2 must be different\"\n    }\n    return true;\n  })\n  .parse();\n\nconsole.log(parsed);\n```\n\n```bash\n# Valid options\n$ node custom_validation.js --option1=10 --option2=11\n{ option1: 10, option2: 11 }\n\n# Invalid options\n$ node custom_validation.js --option1=10 --option2=10\noption1 and option2 must be different\n\nUsage: custom_validation.js [options]\n\nOptions:\n  -h, --help              Show help\n      --option1 \u003cnumber\u003e             [required]\n      --option2 \u003cnumber\u003e             [required]\n```\n\n### Variadic arguments\n\nPlease refer [array types](#array-types).\n\n## Commands\n\nFile [command.ts](./example/command.ts)\n\n```ts\nimport { z } from \"zod\";\nimport { parser } from \"zod-opts\";\n\nconst command1 = command(\"command1\")\n  .options({\n    option1: {\n      type: z.boolean().default(false),\n    },\n  })\n  .action((parsed) =\u003e {\n    // parsed is inferred as { option1: boolean }\n    console.log(\"command2\", parsed);\n  });\n\nconst command2 = command(\"command2\")\n  .options({\n    option1: {\n      type: z.string(),\n    },\n  })\n  .action((parsed) =\u003e {\n    // parsed is inferred as { option1: string }\n    console.log(\"command2\", parsed);\n  });\n\nparser().subcommand(command1).subcommand(command2).parse();\n```\n\n```bash\n# Valid options\n$ node command.js command1 --option1\ncommand1 { option1: true }\n\n# Invalid options\n$  node command.js command2 a\nToo many positional arguments\n\nUsage: command.js command2 [options]\n\nOptions:\n  -h, --help              Show help\n      --option1 \u003cstring\u003e             [required]\n\n# Global help\n$ node command.js --help\nUsage: command.js [options] \u003ccommand\u003e\n\nCommands:\n  command1\n  command2\n\nOptions:\n  -h, --help  Show help\n\n# Command help\n$ node command.js command1 --help\nUsage: command.js command1 [options]\n\nOptions:\n  -h, --help     Show help\n      --option1  (default: false)\n```\n\n## Help\n\nYou can `.showHelp()` to show help message. And `.getHelp()` returns the help message.\n\n## Version\n\nIf the parser has called with `.version()` method, The user can show the version with `--version` or `-V` option.\n\n```bash\n$ node complex.js --version\n1.0.0\n```\n\n## Advanced Usage\n\n### Reuse Zod object type\n\nIf you want to reuse Zod object type, you can define the type and use it in `.options()` and `.args()`.\n\nFile: [map_zod_object.ts](./example/map_zod_object.ts)\n\n```ts\nimport { z } from \"zod\";\nimport { parser } from \"zod-opts\";\n\nconst OptionsSchema = z.object({\n  opt1: z.string(),\n  opt2: z.number().optional(),\n  pos1: z.enum([\"a\", \"b\"]),\n});\n\ntype Options = z.infer\u003ctypeof OptionsSchema\u003e;\n\nfunction parseOptions(): Options {\n  return parser()\n    .name(\"scriptA\")\n    .version(\"1.0.0\")\n    .description(\"desc\")\n    .options({\n      opt1: { type: OptionsSchema.shape.opt1 },\n      opt2: { type: OptionsSchema.shape.opt2 },\n    })\n    .args([\n      {\n        name: \"pos1\",\n        type: OptionsSchema.shape.pos1,\n      },\n    ])\n    .parse();\n}\n\nconst options = parseOptions();\nconsole.log(options);\n```\n\n## Future work ideas\n\n- [ ] Support nested commands.\n- [ ] Support `z.array()` type in `options()`.\n- [ ] Support custom callback to handle errors, help and exit().\n- [ ] `asyncParse()`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndruger%2Fzod-opts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fndruger%2Fzod-opts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndruger%2Fzod-opts/lists"}