Skip to content

Arguments & Flags

A command’s input is described by a config object: a record whose keys are the field names your handler receives, and whose values are Params. There are two kinds of Param:

  • Flag — a named option, written --name value (or -n value with an alias). Order-independent.
  • Argument — a positional operand, matched left to right by position.

They share most constructors (string, integer, choice, file, …) and combinators (withDefault, optional, withSchema, map, …), so once you know one you mostly know the other. A few are specific to one kind — for example boolean is Flag-only, and variadic is Argument-only.

import { Console, Effect } from "effect"
import { Argument, Command, Flag } from "effect/unstable/cli"
const deploy = Command.make(
"deploy",
{
// Required positional argument. Without it, parsing fails with usage help.
service: Argument.string("service").pipe(
Argument.withDescription("Name of the service to deploy")
),
// A choice flag restricts the value to a fixed set. The result type is the
// union "staging" | "production", not just string.
env: Flag.choice("env", ["staging", "production"]).pipe(
Flag.withAlias("e"),
Flag.withDescription("Target environment")
),
// Optional flag with a default. Because of the default the field is always
// present, so the handler never has to deal with "missing".
replicas: Flag.integer("replicas").pipe(
Flag.withDefault(1),
Flag.withDescription("Number of replicas to run")
),
// A boolean flag is true when present (`--force`) and false otherwise.
// It also supports `--no-force` for explicit negation.
force: Flag.boolean("force").pipe(
Flag.withDescription("Skip the production confirmation prompt")
)
},
Effect.fn(function*({ service, env, replicas, force }) {
if (env === "production" && !force) {
// Handlers are Effects, so failing is just `Effect.fail`.
return yield* Effect.fail("Refusing to deploy to production without --force")
}
yield* Console.log(`Deploying ${service} to ${env} (${replicas} replicas)`)
})
)

Invoked as deploy api --env production --replicas 3 --force, the handler receives { service: "api", env: "production", replicas: 3, force: true }, fully typed.

A bare Flag or Argument is required — if it is missing, parsing fails and the user sees usage help. You make an input non-required in one of two ways:

import { Console, Effect, Option } from "effect"
import { Flag } from "effect/unstable/cli"
// 1. `withDefault` supplies a value when the flag is absent. The field type is
// just the value type — the handler never sees the absence.
const port = Flag.integer("port").pipe(Flag.withDefault(8080))
// 2. `optional` wraps the value in an Option, so the handler can distinguish
// "not provided" (Option.none) from any concrete value.
const tag = Flag.string("tag").pipe(Flag.optional)
const handler = Effect.fn(function*(config: {
readonly port: number
readonly tag: Option.Option<string>
}) {
yield* Console.log(`port=${config.port}`)
yield* Console.log(
Option.match(config.tag, {
onNone: () => "no tag supplied",
onSome: (t) => `tag=${t}`
})
)
})

The built-in constructors already parse and validate primitive types (integer rejects non-numbers, choice rejects values outside its set, file can require existence). When you need more, use withSchema to run a Schema over the parsed value, or map to transform it.

import { Schema } from "effect"
import { Flag } from "effect/unstable/cli"
// Validate with a Schema: parsing fails with a clear error if the port is out
// of range. Schema lives in core `effect`.
const port = Flag.integer("port").pipe(
Flag.withSchema(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })))
)
// Transform a parsed value into a richer shape.
const endpoint = Flag.integer("port").pipe(
Flag.map((p) => ({ port: p, url: `http://localhost:${p}` }))
)
// Read a secret as a Redacted value so it never prints in logs or errors.
const apiKey = Flag.redacted("api-key")

Use variadic to collect a positional argument that may appear multiple times into a ReadonlyArray. Pass min/max to bound how many are accepted.

import { Console, Effect } from "effect"
import { Argument, Command, Flag } from "effect/unstable/cli"
const cat = Command.make(
"cat",
{
// `cat a.txt b.txt c.txt` -> files: ["a.txt", "b.txt", "c.txt"]
// `min: 1` makes at least one file mandatory.
files: Argument.string("file").pipe(
Argument.withDescription("Files to print"),
Argument.variadic({ min: 1 })
),
// A key=value flag merges repeated occurrences into one record:
// `--define A=1 --define B=2` -> define: { A: "1", B: "2" }
define: Flag.keyValuePair("define").pipe(
Flag.withDescription("Template variables, repeatable")
)
},
Effect.fn(function*({ files, define }) {
for (const file of files) {
yield* Console.log(`reading ${file}`)
}
yield* Console.log(JSON.stringify(define))
})
)

These exist on both Flag and Argument (a flag is named, an argument is positional):

| Constructor | Result type | Notes | | --- | --- | --- | | string(name) | string | Any text value. | | integer(name) / float(name) | number | Rejects non-numeric input. | | boolean(name) | boolean | Flags only-style: present is true, supports --no-<flag>. | | choice(name, [...]) | union of literals | Restricts to a fixed set; result is the literal union. | | choiceWithValue(name, [[k, v], …]) | v | Maps choice strings to arbitrary typed values. | | date(name) | Date | Parses an ISO date. | | file(name) / directory(name) / path(name) | string | Optionally require existence with { mustExist: true }. | | fileText(name) | string | Reads the file’s contents. | | fileSchema(name, schema) | decoded value | Reads and decodes a file (e.g. JSON) through a Schema. | | redacted(name) | Redacted<string> | Keeps secrets out of logs and errors. | | keyValuePair(name) (Flag only) | Record<string, string> | Merges repeated --flag k=v into one map. |

These combinators improve the generated --help output and ergonomics. They work on any Param:

  • withDescription(text) — help text shown next to the flag/argument.
  • withAlias("x") (Flag only) — a short alias, e.g. -x.
  • withMetavar("NAME") — the placeholder shown in usage, e.g. <NAME>.
  • withHidden (Flag only) — omit from help and completions but still parse.
  • withFallbackConfig(config) — fall back to a value from Configuration (an env var, for instance) when the flag is absent.

Next, see how to combine commands into a tree on the Subcommands page.