Types
Types allow you to validate, convert, and parse values for both flags and parameters. Clerc provides several built-in type functions and supports custom types.
Built-in Basic Types
Clerc supports the standard JavaScript type constructors for common use cases:
- String: For string values (default value:
undefined) - Number: For numeric values (can be used directly)
- Boolean: For boolean switches (default value:
false) - Object: For key-value pairs (default value:
{})
String Type
The String type is used for flags and parameters that accept string values. This is the most basic type.
Default value behavior: If not specified, its value is undefined (unless a default property is set).
const cli = Cli()
.command("greet", "Greet someone", {
flags: {
name: {
type: String,
description: "User name",
default: "World",
},
message: {
type: String,
short: "m",
description: "Greeting message",
},
},
})
.on("greet", (ctx) => {
console.log(`${ctx.flags.message}, ${ctx.flags.name}!`);
// $ node cli.mjs greet --name John --message Hello
// Hello, John!
// $ node cli.mjs greet --message Hello
// ctx.flags.message => "Hello"
// ctx.flags.name => "World" (uses default value)
})
.parse();Boolean Type
The Boolean type is used for creating boolean switch flags. By default, simply mentioning the flag name sets it to true.
Default value behavior: If the flag is not specified, its value is false.
const cli = Cli()
.command("build", "Build the project", {
flags: {
production: {
type: Boolean,
description: "Build for production",
},
watch: {
type: Boolean,
short: "w",
description: "Enable watch mode",
},
},
})
.on("build", (ctx) => {
// $ node cli.mjs build --production --watch
ctx.flags.production; // => true
ctx.flags.watch; // => true
// $ node cli.mjs build
ctx.flags.production; // => false
ctx.flags.watch; // => false
})
.parse();Boolean's Negatable Property
The Boolean type supports a negatable property that allows you to decide whether to enable negated flags. By default, negatable is true, which means --no-flag will set the flag flag to false.
const cli = Cli()
.command("start", "Start the application", {
flags: {
color: {
type: Boolean,
negatable: true, // default
description: "Enable color output",
default: true,
},
cache: {
type: Boolean,
negatable: false, // disable negation form
description: "Enable caching",
default: true,
},
},
})
.on("start", (ctx) => {
// $ node cli.mjs start
ctx.flags.color; // => true
ctx.flags.cache; // => true
// $ node cli.mjs start --no-color --no-cache
ctx.flags.color; // => false
ctx.flags.cache; // => true
// You must use --cache=false to disable caching
// $ node cli.mjs start --cache=false
ctx.flags.cache; // => false
})
.parse();Array Type
The Array type is used for flags and parameters that accept multiple values. Define it by wrapping the type function in an array:
Default value behavior: If not specified, its value is [] (empty array).
const cli = Cli()
.command("copy", "Copy files", {
flags: {
// Use [String] to accept multiple string values
include: {
type: [String],
short: "i",
description: "File patterns to include",
},
// Use [Number] to accept multiple numeric values
ports: {
type: [Number],
short: "p",
description: "Ports to listen on",
},
},
})
.on("copy", (ctx) => {
// $ node cli.mjs copy -i "*.js" -i "*.ts" -p 3000 -p 3001
ctx.flags.include; // => ["*.js", "*.ts"]
ctx.flags.ports; // => [3000, 3001]
// $ node cli.mjs copy
ctx.flags.include; // => []
ctx.flags.ports; // => []
})
.parse();Counter Type
The counter type is used to count how many times a flag is specified. This can be implemented by using the [Boolean] type:
Default value behavior: If not specified, its value is 0.
const cli = Cli()
.command("log", "Display logs", {
flags: {
// [Boolean] type counts how many times the flag is used
verbose: {
type: [Boolean],
short: "v",
description: "Verbosity level (-v, -vv, -vvv)",
},
},
})
.on("log", (ctx) => {
// $ node cli.mjs log -v
ctx.flags.verbose; // => 1
// $ node cli.mjs log -vvv
ctx.flags.verbose; // => 3
// $ node cli.mjs log -v -v -v
ctx.flags.verbose; // => 3
// $ node cli.mjs log
ctx.flags.verbose; // => 0
})
.parse();Object Type
The Object type is used for flags that accept key-value pairs. Use dots or other delimiters to specify object properties:
Default value behavior: If not specified, its value is {} (empty object).
const cli = Cli()
.command("config", "Configure the application", {
flags: {
define: {
type: Object,
short: "d",
description: "Define environment variables",
},
},
})
.on("config", (ctx) => {
// $ node cli.mjs config --define.apiUrl http://api.example.com --define.debug
ctx.flags.define; // => { apiUrl: "http://api.example.com", debug: true }
// $ node cli.mjs config
ctx.flags.define; // => {}
})
.parse();INFO
If you want to pass key-value pairs like K=V, you can use the colon delimiter in the command line:
$ node cli.mjs config --define:env=production --define:version=1.0.0Actually, --define=env=production also works fine, it's just not as intuitive.
Advanced Object Type with objectType()
For more control over object flag parsing, type conversion, and default value merging, you can use the objectType() function from @clerc/parser:
import { coerceObjectValue, objectType, setDotValues } from "@clerc/parser";
// or import { objectType, setDotValues, coerceObjectValue } from "clerc";
const cli = Cli()
.command("dev", "Start development server", {
flags: {
env: {
type: objectType<{ PORT?: number; DEBUG?: boolean; HOST?: string }>({
setValue: (object, path, value) => {
// Custom type conversion based on field name
if (path === "PORT") {
setDotValues(object, path, Number(value));
} else if (path === "DEBUG") {
setDotValues(object, path, value === "true");
} else {
// For other fields, use default coercion
setDotValues(object, path, coerceObjectValue(value));
}
},
}),
default: { PORT: 3000, HOST: "0.0.0.0" }, // Default values
},
},
})
.on("dev", (ctx) => {
// $ node cli.mjs dev --env.PORT 8080 --env.DEBUG true
ctx.flags.env.PORT; // => 8080 (number)
ctx.flags.env.DEBUG; // => true (boolean)
ctx.flags.env.HOST; // => "0.0.0.0" (merged from default)
})
.parse();Key features:
- Type-safe generic support: Specify the expected object structure with
objectType<T>() - Custom value transformation: The
setValuefunction receives:object: The current object being builtpath: The dot-separated path (e.g.,"PORT"or"foo.bar")value: The raw CLI string value
- Automatic default merging: When you provide a
defaultvalue in the flag config, it automatically merges with user-provided values (shallow merge by default) - Helper functions: Use
setDotValues,appendDotValues, andcoerceObjectValuefor common operations
Default behavior (without custom setValue):
import { objectType } from "@clerc/parser";
// or import { objectType } from "clerc";
const cli = Cli()
.command("config", "Configure settings", {
flags: {
settings: {
type: objectType(), // Uses default behavior
default: { theme: "dark", language: "en" },
},
},
})
.on("config", (ctx) => {
// $ node cli.mjs config --settings.name app --settings.version 1.0.0
ctx.flags.settings; // => { name: "app", version: "1.0.0", theme: "dark", language: "en" }
// $ node cli.mjs config --settings.tags a --settings.tags b
ctx.flags.settings; // => { tags: ["a", "b"], theme: "dark", language: "en" }
// Duplicate keys become arrays, default values are merged
})
.parse();The default behavior automatically:
- Converts
"true"or empty values to booleantrue - Converts
"false"to booleanfalse - Handles duplicate keys by creating arrays
- Merges external
defaultvalues with user-provided values (shallow merge)
Custom merge logic:
By default, objectType performs a shallow merge when combining default values with user-provided values. You can customize this behavior with the mergeObject option:
import { objectType } from "@clerc/parser";
const cli = Cli()
.command("start", "Start the server", {
flags: {
config: {
type: objectType({
mergeObject: (target, defaults) => {
// Custom merge logic: deep merge nested objects
for (const [key, val] of Object.entries(defaults)) {
if (
typeof val === "object" &&
val !== null &&
typeof target[key] === "object"
) {
// Deep merge nested objects
Object.assign(target[key], val, target[key]);
} else if (!(key in target)) {
// Add missing keys from defaults
target[key] = val;
}
}
},
}),
default: { db: { host: "localhost", port: 5432 }, cache: { ttl: 300 } },
},
},
})
.on("start", (ctx) => {
// $ node cli.mjs start --config.db.host example.com
ctx.flags.config;
// => { db: { host: "example.com", port: 5432 }, cache: { ttl: 300 } }
// Deep merge preserves db.port from default
})
.parse();Utility functions:
setDotValues(object, path, value): Sets a value at a nested path (overwrites existing values)appendDotValues(object, path, value): Sets a value at a nested path (converts duplicates to arrays)coerceObjectValue(value): Default boolean coercion ("true"→true,"false"→false)
TIP
The objectType() function provides a more powerful and type-safe alternative to the basic Object type, especially when you need:
- Custom type conversions per field
- Better TypeScript type inference
- Integration with schema validation libraries
Built-in Advanced Types
Clerc provides some built-in advanced type functions to facilitate common needs:
Enum: Restrict flag and parameter values to a predefined set.Range: Restrict numeric values to a specific range and convert to numbers.Regex: Validate values against a regular expression pattern.
These type functions can be used for both flags and parameters, allowing you to share the same type definitions across your CLI:
import { Types } from "clerc";
Cli()
.command("serve", "Start the server", {
flags: {
mode: {
type: Types.Enum("development", "production", "test"),
default: "development" as const,
description: "Set the application mode",
},
},
parameters: [
{
key: "[port]",
type: Types.Range(1024, 65_535),
description: "Port number",
},
],
})
.on("serve", (ctx) => {
ctx.flags.mode;
ctx.parameters.port;
})
.parse();Enum Type
Restrict flag or parameter values to a predefined set of options:
import { Types } from "clerc";
const cli = Cli()
.scriptName("build-cli")
.description("Build tool")
.version("1.0.0")
.command("config", "Configure build settings", {
flags: {
format: {
type: Types.Enum("json", "yaml", "toml"),
description: "Output format",
},
},
parameters: [
{
key: "<setting>",
type: Types.Enum("output", "target", "format"),
description: "Setting name",
},
{
key: "<value>",
description: "Setting value",
},
],
})
.on("config", (ctx) => {
console.log(`Setting ${ctx.parameters.setting} = ${ctx.parameters.value}`);
})
.parse();Usage:
$ build-cli config --format json output dist
$ build-cli config --format yaml target es2020
$ build-cli config --format invalid value
# Error: Invalid value: invalid. Must be one of: json, yaml, tomlRange Type
Restrict numeric values to a specific range and convert to numbers:
import { Types } from "clerc";
const cli = Cli()
.scriptName("server-cli")
.description("Server management tool")
.version("1.0.0")
.command("start", "Start the server", {
flags: {
port: {
type: Types.Range(1024, 65_535),
description: "Port number",
},
},
parameters: [
{
key: "[timeout]",
type: Types.Range(1, 3600),
description: "Timeout in seconds",
},
],
})
.on("start", (ctx) => {
const port = ctx.flags.port ?? 3000;
console.log(`Starting server on port ${port}`);
})
.parse();Usage:
$ server-cli start --port 3000
$ server-cli start --port 8080
$ server-cli start --port 100
# Error: Invalid value: 100. Must be a number between 1024 and 65535Regex Type
Validate values against a regular expression pattern:
import { Types } from "clerc";
const cli = Cli()
.scriptName("git-clone")
.description("Clone repository")
.version("1.0.0")
.command("clone", "Clone a repository", {
parameters: [
{
key: "<repo>",
type: Types.Regex(/^[\w\-.]+\/[\w\-.]+$/, "owner/repo format"),
description: "Repository in owner/repo format",
},
],
})
.on("clone", (ctx) => {
console.log(`Cloning ${ctx.parameters.repo}`);
})
.parse();Usage:
$ git-clone clone clercjs/clerc
$ git-clone clone myorg/myrepo
$ git-clone clone invalid
# Error: Invalid value: invalid. Must match: owner/repo formatCustom Types
You can create custom type functions by providing a function that accepts a string argument and returns the parsed value.
Type Display Property
Custom type functions can include an optional display property that provides a user-friendly name for the type in help output. This is especially useful for complex types where the function name doesn't clearly describe what the type accepts.
// Custom type function that parses a comma-separated string into an array of strings
const CommaSeparatedList = (value: string): string[] =>
value.split(",").map((item) => item.trim());
// Add a display property for better help documentation
CommaSeparatedList.display = "item1,item2,...";
const cli = Cli()
.scriptName("custom-cli")
.description("A CLI using a custom type")
.version("1.0.0")
.command("list", "Display list", {
flags: {
items: {
type: CommaSeparatedList,
default: [] as string[],
description: "Comma-separated list of strings",
},
},
})
.on("list", (ctx) => {
console.log("Items:", ctx.flags.items);
})
.parse();The display property is used by the help system to show a more descriptive type name instead of the function name. For example, instead of showing "CommaSeparatedList" in the help output, it would show "item1,item2,...".
Basic Custom Type Example
// Custom type function that parses a comma-separated string into an array of strings
const CommaSeparatedList = (value: string): string[] =>
value.split(",").map((item) => item.trim());
const cli = Cli()
.scriptName("custom-cli")
.description("A CLI using a custom type")
.version("1.0.0")
.command("list", "Display list", {
flags: {
items: {
type: CommaSeparatedList,
default: [] as string[],
description: "Comma-separated list of strings",
},
},
})
.on("list", (ctx) => {
console.log("Items:", ctx.flags.items);
})
.parse();Custom type functions can also be used with array syntax to accept multiple values:
const cli = Cli()
.command("process", "Process files", {
flags: {
// Use [CommaSeparatedList] to accept multiple comma-separated lists
patterns: {
type: [CommaSeparatedList],
short: "p",
description: "File patterns (comma-separated)",
},
},
})
.on("process", (ctx) => {
// $ node cli.mjs process -p "*.js,*.ts" -p "src/**"
ctx.flags.patterns; // => [["*.js", "*.ts"], ["src/**"]]
})
.parse();Using Custom Types with Parameters
Custom type functions with display properties can also be used for parameters, providing better help documentation:
// Custom type function for parsing version numbers
function Version(value: string): string {
if (!/^\d+\.\d+\.\d+$/.test(value)) {
throw new Error(`Invalid version format: ${value}. Expected format: x.y.z`);
}
return value;
}
// Add display property for help documentation
Version.display = "x.y.z";
const cli = Cli()
.scriptName("release-cli")
.description("Release management tool")
.version("1.0.0")
.command("publish", "Publish a new version", {
parameters: [
{
key: "<version>",
type: Version,
description: "Version number to publish",
},
{
key: "[channel]",
type: Types.Enum("stable", "beta", "alpha"),
description: "Release channel",
},
],
})
.on("publish", (ctx) => {
console.log(
`Publishing version ${ctx.parameters.version} to ${ctx.parameters.channel || "stable"} channel`,
);
})
.parse();In the help output, instead of showing "Version" as the type, it will show "x.y.z", making it clearer what format is expected.

