Flag
Defines named options for command-line applications.
A Flag<A> describes how to read one named value from parsed command-line
input, validate it, and produce an A. Flags are useful for inputs such as
ports, verbosity switches, configuration files, output directories, choices,
secrets, and repeated values. The helpers here build flags with aliases,
defaults, optional values, prompts, configuration fallbacks, validation, and
value transformations.
Aliasing
Adds an alias to a flag, allowing it to be referenced by multiple names.
Signature
declare const withAlias: { <A>(alias: string): (self: Flag<A>) => Flag<A>; <A>(self: Flag<A>, alias: string): Flag<A>;}Example
(Adding flag aliases)
import { Flag } from "effect/unstable/cli"
// Flag can be used as both --verbose and -vconst verboseFlag = Flag.boolean("verbose").pipe( Flag.withAlias("v"))
// Multiple aliases can be chainedconst helpFlag = Flag.boolean("help").pipe( Flag.withAlias("h"), Flag.withAlias("?"))const kinds = [verboseFlag.kind, helpFlag.kind] // => ["flag", "flag"]Alternatives
Provides an alternative flag if the first one fails to parse.
Signature
declare const orElse: { <B>(that: LazyArg<Flag<B>>): <A>(self: Flag<A>) => Flag<B | A>; <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>): Flag<A | B>;}Example
(Falling back to another flag)
import { Flag } from "effect/unstable/cli"
// Try parsing as integer, fallback to stringconst valueFlag = Flag.orElse( Flag.integer("value"), () => Flag.string("value"))
// Multiple input sources with fallbackconst configFlag = Flag.orElse( Flag.file("config"), () => Flag.string("config-url"))const kinds = [valueFlag.kind, configFlag.kind] // => ["flag", "flag"]orElseResult
Tries to parse with the first flag, then the second, returning a Result that indicates which succeeded.
Signature
declare const orElseResult: { <B>(that: LazyArg<Flag<B>>): <A>(self: Flag<A>) => Flag<Result<A, B>>; <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>): Flag<Result<A, B>>;}Example
(Returning fallback results)
import { Effect, FileSystem, Layer, Path, Result, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
const sourceFlag = Flag.orElseResult( Flag.string("source"), () => Flag.string("source-url"))
const program = Effect.gen(function*() { const [, source] = yield* sourceFlag.parse({ arguments: [], flags: { "source-url": ["https://example.com"] } }) return source})
await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Result.fail("https://example.com")Combinators
withFallbackConfig
Adds a fallback config that is loaded when a required flag is missing.
Signature
declare const withFallbackConfig: { <B>(config: Config<B>): <A>(self: Flag<A>) => Flag<B | A>; <A, B>(self: Flag<A>, config: Config<B>): Flag<A | B>;}Example
(Falling back to config)
import { Config } from "effect"import { Flag } from "effect/unstable/cli"
const verbose = Flag.boolean("verbose").pipe( Flag.withFallbackConfig(Config.boolean("VERBOSE")))verbose.kind // => "flag"withFallbackPrompt
Adds a fallback prompt that is shown when a required flag is missing.
Signature
declare const withFallbackPrompt: { <B>(prompt: FallbackPrompt<B>): <A>(self: Flag<A>) => Flag<B | A>; <A, B>(self: Flag<A>, prompt: FallbackPrompt<B>): Flag<A | B>;}Example
(Falling back to prompts)
import { Flag, Prompt } from "effect/unstable/cli"
const name = Flag.string("name").pipe( Flag.withFallbackPrompt(Prompt.text({ message: "Name" })))name.kind // => "flag"Constructors
Creates a boolean flag that can be enabled or disabled.
Signature
declare function boolean(name: string): Flag<boolean>Example
(Creating boolean flags)
import { Flag } from "effect/unstable/cli"
const verboseFlag = Flag.boolean("verbose")// Usage: --verbose (true) or --no-verbose (false)// Omission fails unless the flag is made optional or given a fallback.verboseFlag.kind // => "flag"Creates a flag that accepts one of the provided string choices and returns the selected string.
When to use
Use when you need to define a named CLI flag with fixed string choices and no custom value mapping.
Gotchas
An empty choices array compiles, but no input value can parse successfully.
See
- choiceWithValue for mapping accepted strings to different typed values
Signature
declare function choice<Choices extends readonly Array<string>>(name: string, choices: Choices): Flag<Choices[number]>choiceWithValue
Constructs option parameters that represent a choice between several inputs. Each tuple maps a string flag value to an associated typed value.
Signature
declare function choiceWithValue<Choice extends readonly Array<readonly [string, any]>>(name: string, choices: Choice): Flag<Choice[number][1]>Example
(Creating flag choices with values)
import { Flag } from "effect/unstable/cli"
// simple enum like choice mapping directly to string unionconst color = Flag.choice("color", ["red", "green", "blue"])
// choice with custom value mappingconst logLevel = Flag.choiceWithValue("log-level", [ ["debug", "Debug" as const], ["info", "Info" as const], ["error", "Error" as const]])const kinds = [color.kind, logLevel.kind] // => ["flag", "flag"]Creates a date flag that accepts date input in ISO format.
Signature
declare function date(name: string): Flag<Date>Example
(Creating date flags)
import { Flag } from "effect/unstable/cli"
const startDateFlag = Flag.date("start-date")// Usage: --start-date 2023-12-25startDateFlag.kind // => "flag"Creates a directory path flag that accepts directory paths with optional existence validation.
Signature
declare function directory(name: string, options?: { readonly mustExist?: boolean;}): Flag<string>Example
(Creating directory flags)
import { Flag } from "effect/unstable/cli"
// Basic directory flagconst outputFlag = Flag.directory("output")// Usage: --output ./build
// Directory that must existconst sourceFlag = Flag.directory("source", { mustExist: true })// Usage: --source ./src (directory must exist)const kinds = [outputFlag.kind, sourceFlag.kind] // => ["flag", "flag"]Creates a file path flag that accepts file paths with optional existence validation.
Signature
declare function file(name: string, options?: { readonly mustExist?: boolean;}): Flag<string>Example
(Creating file flags)
import { Flag } from "effect/unstable/cli"
// Basic file flagconst inputFlag = Flag.file("input")// Usage: --input ./data.json
// File that must existconst configFlag = Flag.file("config", { mustExist: true })// Usage: --config ./config.yaml (file must exist)const kinds = [inputFlag.kind, configFlag.kind] // => ["flag", "flag"]Creates a flag that reads and parses the content of the specified file.
Details
The parser that is utilized will depend on the specified format, or the
extension of the file passed on the command-line if no format is specified.
Signature
declare function fileParse(name: string, options?: FileParseOptions): Flag<unknown>Example
(Parsing file contents)
import { Flag } from "effect/unstable/cli"
// Will use the extension of the file passed on the command line to determine// the parser to useconst config = Flag.fileParse("config")
// Will use the JSON parserconst jsonConfig = Flag.fileParse("json-config", { format: "json" })const kinds = [config.kind, jsonConfig.kind] // => ["flag", "flag"]fileSchema
Creates a flag that reads and validates file content using the specified schema.
Signature
declare function fileSchema<A>(name: string, schema: ConstraintDecoder<A, Environment>, options?: { readonly errorFormatter?: Formatter<string>; readonly format?: "json" | "ini" | "toml" | "yaml";}): Flag<A>Example
(Validating file contents)
import { Schema } from "effect"import { Flag } from "effect/unstable/cli"
const ConfigSchema = Schema.Struct({ port: Schema.Number, host: Schema.String})
const config = Flag.fileSchema("config", ConfigSchema, { format: "json" })config.kind // => "flag"Creates a flag that reads and returns file content as a string.
Signature
declare function fileText(name: string): Flag<string>Example
(Reading file text)
import { Flag } from "effect/unstable/cli"
const config = Flag.fileText("config-file")// --config-file ./app.json will read the file contentconfig.kind // => "flag"Creates a float flag that accepts decimal number input.
Signature
declare function float(name: string): Flag<number>Example
(Creating float flags)
import { Flag } from "effect/unstable/cli"
const rateFlag = Flag.float("rate")// Usage: --rate 3.14rateFlag.kind // => "flag"Creates an integer flag that accepts whole number input.
Signature
declare function integer(name: string): Flag<number>Example
(Creating integer flags)
import { Flag } from "effect/unstable/cli"
const portFlag = Flag.integer("port")// Usage: --port 8080portFlag.kind // => "flag"keyValuePair
Creates a flag that parses key=value pairs.
When to use
Use when you need a CLI flag that accepts one or more key=value
configuration entries.
Details
Requires at least one key=value pair. Multiple pairs are merged into a single record.
Signature
declare function keyValuePair(name: string): Flag<Record<string, string>>Example
(Parsing key-value pairs)
import { Flag } from "effect/unstable/cli"
const env = Flag.keyValuePair("env")// --env FOO=bar --env BAZ=qux will parse to { FOO: "bar", BAZ: "qux" }env.kind // => "flag"Creates an empty sentinel flag that always fails to parse. This is useful for creating placeholder flags or for combinators.
Signature
declare const none: Flag<never>Example
(Creating sentinel flags)
import { Flag } from "effect/unstable/cli"
const makeValueFlag = (includeValue: boolean) => includeValue ? Flag.string("value") : Flag.none
makeValueFlag(true) === Flag.none // => falsemakeValueFlag(false) === Flag.none // => trueCreates a path flag that accepts file system path input with validation options.
Signature
declare function path(name: string, options?: { readonly mustExist?: boolean; readonly pathType?: "either" | "file" | "directory"; readonly typeName?: string;}): Flag<string>Example
(Creating path flags)
import { Flag } from "effect/unstable/cli"
// Basic path flagconst pathFlag = Flag.path("config-path")
// File-only path that must existconst fileFlag = Flag.path("input-file", { pathType: "file", mustExist: true})
// Directory path with custom type nameconst dirFlag = Flag.path("output-dir", { pathType: "directory", typeName: "OUTPUT_DIRECTORY"})const kinds = [pathFlag.kind, fileFlag.kind, dirFlag.kind] // => ["flag", "flag", "flag"]Creates a string flag whose parsed value is wrapped in Redacted.Redacted so
stringification and logging redact the value.
Gotchas
Values supplied on the command line may still be visible to the operating system or shell history.
Signature
declare function redacted(name: string): Flag<Redacted<string>>Example
(Creating redacted flags)
import { Effect, FileSystem, Layer, Path, Redacted, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
const passwordFlag = Flag.redacted("password")
const program = Effect.gen(function*() { const [, password] = yield* passwordFlag.parse({ arguments: [], flags: { "password": ["abc123"] } }) return Redacted.value(password).length})
await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => 6Creates a string flag that accepts text input.
Signature
declare function string(name: string): Flag<string>Example
(Creating string flags)
import { Flag } from "effect/unstable/cli"
const nameFlag = Flag.string("name")// Usage: --name "John Doe"nameFlag.kind // => "flag"Filtering
Filters a flag value based on a predicate, failing with a custom error if the predicate returns false.
Signature
declare const filter: { <A>(predicate: (a: A) => boolean, onFalse: (a: A) => string): (self: Flag<A>) => Flag<A>; <A>(self: Flag<A>, predicate: (a: A) => boolean, onFalse: (a: A) => string): Flag<A>;}Example
(Filtering parsed values)
import { Flag } from "effect/unstable/cli"
// Ensure port is in valid rangeconst portFlag = Flag.integer("port").pipe( Flag.filter( (port) => port >= 1 && port <= 65535, (port) => `Port ${port} is out of range (1-65535)` ))
// Ensure non-empty stringconst nameFlag = Flag.string("name").pipe( Flag.filter( (name) => name.trim().length > 0, () => "Name cannot be empty" ))const kinds = [portFlag.kind, nameFlag.kind] // => ["flag", "flag"]Transforms and filters a flag value, failing with a custom error if the transformation returns None.
Signature
declare const filterMap: { <A, B>(f: (a: A) => Option<B>, onNone: (a: A) => string): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, f: (a: A) => Option<B>, onNone: (a: A) => string): Flag<B>;}Example
(Filtering and transforming values)
import { Option } from "effect"import { Flag } from "effect/unstable/cli"
// Parse positive integers onlyconst positiveInt = Flag.integer("count").pipe( Flag.filterMap( (n) => n > 0 ? Option.some(n) : Option.none(), (n) => `Expected positive integer, got ${n}` ))
// Parse valid email addressesconst emailFlag = Flag.string("email").pipe( Flag.filterMap( (email) => email.includes("@") ? Option.some(email) : Option.none(), (email) => `Invalid email address: ${email}` ))const kinds = [positiveInt.kind, emailFlag.kind] // => ["flag", "flag"]Mapping
Transforms the parsed value of a flag using a mapping function.
Signature
declare const map: { <A, B>(f: (a: A) => B): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, f: (a: A) => B): Flag<B>;}Example
(Mapping parsed values)
import { Flag } from "effect/unstable/cli"
// Convert string to uppercaseconst nameFlag = Flag.string("name").pipe( Flag.map((name) => name.toUpperCase()))
// Convert port to URLconst urlFlag = Flag.integer("port").pipe( Flag.map((port) => `http://localhost:${port}`))const kinds = [nameFlag.kind, urlFlag.kind] // => ["flag", "flag"]Transforms the parsed value using an Effect that can perform IO operations.
Signature
declare const mapEffect: { <A, B>(f: (a: A) => Effect<B, CliError, Environment>): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, f: (a: A) => Effect<B, CliError, Environment>): Flag<B>;}Example
(Mapping parsed values effectfully)
import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
const upperName = Flag.string("name").pipe( Flag.mapEffect((name) => Effect.succeed(name.toUpperCase())))
const [, value] = await Effect.runPromise( upperName.parse({ arguments: [], flags: { name: ["alice"] } }).pipe(Effect.provide(CliTestLayer)))value // => "ALICE"mapTryCatch
Transforms the parsed value using a function that might throw, with error handling.
Signature
declare const mapTryCatch: { <A, B>(f: (a: A) => B, onError: (error: unknown) => string): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, f: (a: A) => B, onError: (error: unknown) => string): Flag<B>;}Example
(Mapping thrown errors)
import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
// Parse JSON string with error handlingconst jsonFlag = Flag.string("config").pipe( Flag.mapTryCatch( (json) => JSON.parse(json), (error) => `Invalid JSON: ${error}` ))
// Parse URL with error handlingconst urlFlag = Flag.string("url").pipe( Flag.mapTryCatch( (url) => new URL(url), (error) => `Invalid URL: ${error}` ))
const [, value] = await Effect.runPromise( jsonFlag.parse({ arguments: [], flags: { config: ['{"enabled":true}'] } }).pipe(Effect.provide(CliTestLayer)))value // => { enabled: true }Metadata
withDescription
Adds a description to a flag for help documentation.
Signature
declare const withDescription: { <A>(description: string): (self: Flag<A>) => Flag<A>; <A>(self: Flag<A>, description: string): Flag<A>;}Example
(Adding help descriptions)
import { Flag } from "effect/unstable/cli"
const portFlag = Flag.integer("port").pipe( Flag.withDescription("The port number to listen on"))
const configFlag = Flag.file("config").pipe( Flag.withDescription("Path to the configuration file"))const kinds = [portFlag.kind, configFlag.kind] // => ["flag", "flag"]withHidden
Hides a flag from generated help output and shell completions while keeping it fully parseable on the command line.
When to use
Use when experimental or internal flags should be accepted but not advertised, such as
--experimental-foo, debug toggles, or escape hatches that are not yet committed to the
public CLI surface.
Signature
declare function withHidden<A>(self: Flag<A>): Flag<A>Example
(Hiding a flag from help)
import { Flag } from "effect/unstable/cli"
// Flag still parses --experimental-foo, but it does not appear in --help.const experimental = Flag.boolean("experimental-foo").pipe( Flag.withHidden)experimental.kind // => "flag"withMetavar
Sets a custom metavar (placeholder name) for the flag in help documentation.
Details
The metavar is displayed in usage text to indicate what value the user should
provide. For example, --output FILE shows FILE as the metavar.
Signature
declare const withMetavar: { <A>(metavar: string): (self: Flag<A>) => Flag<A>; <A>(self: Flag<A>, metavar: string): Flag<A>;}Example
(Setting metavars)
import { Flag } from "effect/unstable/cli"
const databaseFlag = Flag.string("database-url").pipe( Flag.withMetavar("URL"), Flag.withDescription("Database connection URL"))// In help: --database-url URL
const timeoutFlag = Flag.integer("timeout").pipe( Flag.withMetavar("SECONDS"))// In help: --timeout SECONDSconst kinds = [databaseFlag.kind, timeoutFlag.kind] // => ["flag", "flag"]Models
Optionality
Makes a flag optional, returning an Option type that can be None if not provided.
Signature
declare function optional<A>(param: Flag<A>): Flag<Option<A>>Example
(Making flags optional)
import { Effect, FileSystem, Layer, Option, Path, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
const optionalPort = Flag.optional(Flag.integer("port"))
const program = Effect.gen(function*() { const [, port] = yield* optionalPort.parse({ arguments: [], flags: { "port": ["4000"] } }) return port})
await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Option.some(4000)withDefault
Provides a default value for a flag when it's not specified.
Signature
declare const withDefault: { <B>(defaultValue: B | Effect<B, CliError, Environment>): <A>(self: Flag<A>) => Flag<B | A>; <A, B>(self: Flag<A>, defaultValue: B | Effect<B, CliError, Environment>): Flag<A | B>;}Example
(Providing default values)
import { Flag } from "effect/unstable/cli"
const portFlag = Flag.integer("port").pipe( Flag.withDefault(8080))// If --port is not provided, defaults to 8080
const hostFlag = Flag.string("host").pipe( Flag.withDefault("localhost"))// If --host is not provided, defaults to "localhost"const kinds = [portFlag.kind, hostFlag.kind] // => ["flag", "flag"]Repetition
Ensures a flag is specified at least a minimum number of times.
Signature
declare const atLeast: { <A>(min: number): (self: Flag<A>) => Flag<readonly Array<A>>; <A>(self: Flag<A>, min: number): Flag<readonly Array<A>>;}Example
(Requiring repeated values)
import { Flag } from "effect/unstable/cli"
const sourceFlag = Flag.atLeast(Flag.file("source"), 2)// Requires at least 2 source files// Usage: --source file1.ts --source file2.ts
const tagFlag = Flag.string("tag").pipe( Flag.atLeast(1))// Requires at least 1 tagconst kinds = [sourceFlag.kind, tagFlag.kind] // => ["flag", "flag"]Ensures a flag is specified at most a maximum number of times.
Signature
declare const atMost: { <A>(max: number): (self: Flag<A>) => Flag<readonly Array<A>>; <A>(self: Flag<A>, max: number): Flag<readonly Array<A>>;}Example
(Limiting repeated values)
import { Flag } from "effect/unstable/cli"
const warningFlag = Flag.atMost(Flag.string("warning"), 3)// Allows up to 3 warning flags// Usage: --warning w1 --warning w2 --warning w3
const debugFlag = Flag.string("debug").pipe( Flag.atMost(1))// Allows at most 1 debug flagconst kinds = [warningFlag.kind, debugFlag.kind] // => ["flag", "flag"]Ensures a flag is specified between a minimum and maximum number of times.
Signature
declare const between: { <A>(min: number, max: number): (self: Flag<A>) => Flag<readonly Array<A>>; <A>(self: Flag<A>, min: number, max: number): Flag<readonly Array<A>>;}Example
(Bounding repeated values)
import { Flag } from "effect/unstable/cli"
const hostFlag = Flag.between(Flag.string("host"), 1, 3)// Requires 1-3 host flags// Usage: --host host1 --host host2
const excludeFlag = Flag.string("exclude").pipe( Flag.between(0, 5))// Allows 0-5 exclude patternsconst kinds = [hostFlag.kind, excludeFlag.kind] // => ["flag", "flag"]Schemas
withSchema
Validates and transforms a flag value using a Schema codec.
Signature
declare const withSchema: { <A, B>(schema: ConstraintCodec<B, A, Environment, unknown>): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, schema: ConstraintCodec<B, A, Environment, unknown>): Flag<B>;}Example
(Validating with schemas)
import { Schema } from "effect"import { Flag } from "effect/unstable/cli"
const isEmail = Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, { message: "Must be a valid email address"})
// Parse and validate email with custom schemaconst EmailSchema = Schema.String.pipe( Schema.check(isEmail))
const emailFlag = Flag.string("email").pipe( Flag.withSchema(EmailSchema))
// Parse JSON configuration with schema validationconst ConfigSchema = Schema.Struct({ port: Schema.Number, host: Schema.String, ssl: Schema.optional(Schema.Boolean)}).pipe(Schema.fromJsonString)
const configFlag = Flag.string("config").pipe( Flag.withSchema(ConfigSchema))const kinds = [emailFlag.kind, configFlag.kind] // => ["flag", "flag"]