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 valuewith 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.
Required vs. optional
Section titled “Required vs. optional”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}` }) )})Validating and transforming values
Section titled “Validating and transforming values”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")Repeated and variadic inputs
Section titled “Repeated and variadic inputs”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)) }))Common constructors
Section titled “Common constructors”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. |
Adding metadata
Section titled “Adding metadata”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.