Skip to content

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).

ts
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.

ts
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.

ts
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).

ts
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.

ts
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).

ts
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:

bash
$ node cli.mjs config --define:env=production --define:version=1.0.0

Actually, --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:

ts
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:

  1. Type-safe generic support: Specify the expected object structure with objectType<T>()
  2. Custom value transformation: The setValue function receives:
    • object: The current object being built
    • path: The dot-separated path (e.g., "PORT" or "foo.bar")
    • value: The raw CLI string value
  3. Automatic default merging: When you provide a default value in the flag config, it automatically merges with user-provided values (shallow merge by default)
  4. Helper functions: Use setDotValues, appendDotValues, and coerceObjectValue for common operations

Default behavior (without custom setValue):

ts
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 boolean true
  • Converts "false" to boolean false
  • Handles duplicate keys by creating arrays
  • Merges external default values 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:

ts
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:

ts
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:

ts
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:

bash
$ 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, toml

Range Type

Restrict numeric values to a specific range and convert to numbers:

ts
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:

bash
$ 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 65535

Regex Type

Validate values against a regular expression pattern:

ts
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:

bash
$ git-clone clone clercjs/clerc
$ git-clone clone myorg/myrepo
$ git-clone clone invalid
# Error: Invalid value: invalid. Must match: owner/repo format

Custom 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.

ts
// 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

ts
// 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:

ts
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:

ts
// 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.

Released under the MIT license