apple/swift-argument-parser
> Declarative, type-safe command-line argument parsing for Swift, built on property wrappers.
GitHub repo · Documentation · License: Apache-2.0
Overview
swift-argument-parser (module name ArgumentParser) is Apple's official library for parsing command-line arguments in Swift. It was open-sourced in February 2020 alongside a swift.org announcement1, and is the parser SwiftPM itself, swift-format, and most first-party Swift command-line tools are built on. As of 2026 it is the default choice for CLIs in the Swift ecosystem — not because it has no competitors, but because it ships from Apple, tracks the toolchain, and needs no third-party dependency.
The defining design decision is that arguments are declared, not parsed imperatively. You write a struct conforming to ParsableCommand, decorate stored properties with @Flag, @Option, and @Argument, and the library discovers those declarations via reflection and builds the parser, the validation, the help text, and shell completions from them. This produces terse, readable command definitions and eliminates most boilerplate. The tradeoff is that the parser is assembled at runtime from Mirror reflection over the type, so a handful of misuse errors (duplicate flag names, conflicting definitions) surface on first parse rather than at compile time2.
The package is source-stable and follows semantic versioning: source-breaking public-API changes only land in a new major version2. The autogenerated help and error strings are explicitly not part of the stable API — their wording and formatting can change in any release, which matters for anyone snapshot-testing CLI output.
Getting Started
Add it to a SwiftPM package:
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.8.0"),
],
targets: [
.executableTarget(name: "repeat", dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]),
]
Declare a command as a struct:
import ArgumentParser
@main
struct Repeat: ParsableCommand {
@Flag(help: "Include a counter with each repetition.")
var includeCounter = false
@Option(name: .shortAndLong, help: "How many times to repeat 'phrase'.")
var count: Int? = nil
@Argument(help: "The phrase to repeat.")
var phrase: String
mutating func run() throws {
let repeatCount = count ?? 2
for i in 1...repeatCount {
print(includeCounter ? "\(i): \(phrase)" : phrase)
}
}
}
The library parses argv, instantiates the type, and calls run() — or exits with a generated usage message. --help output, short/long option names, and error messages are all derived from the property declarations. For async bodies, conform to AsyncParsableCommand instead and mark run() as async.
Architecture / How It Works
The declarative surface is a set of property wrappers — @Argument (positional), @Option (named, takes a value), @Flag (named boolean or enum), and @OptionGroup (compose another ParsableArguments type into this one). Each wrapper stores an internal ArgumentDefinition describing name, arity, help text, default, and how to decode the value.
At parse time the library reflects over the command instance using Mirror to enumerate its property wrappers, collects their ArgumentDefinitions into an ArgumentSet, and runs a token consumer (CommandParser) over the raw arguments. Value conversion goes through the ExpressibleByArgument protocol; any type that conforms — including your own — can be used directly as an @Option/@Argument value. After decoding, an optional validate() method runs for cross-field checks; throwing ValidationError produces a formatted message.
Subcommands are declared via a static configuration: CommandConfiguration that lists child command types. Dispatch is by the first positional token; nesting is arbitrary depth. Help and usage strings are generated from the same ArgumentDefinitions, and generateCompletionScript emits bash, zsh, and fish completion scripts from that same metadata — so completions stay in sync with the parser for free.
The reliance on reflection and property wrappers is the whole architecture: it is why the API is so compact, why declaration order controls help ordering, and why the library is coupled to Swift-version features. Property wrappers require Swift 5.1+, and later minor releases raise the floor (Swift 6.0 for 1.8.0)3.
Production Notes
Minor versions can force a toolchain upgrade. The package deliberately adopts new Swift language and toolchain features, and bumping the minimum Swift version is treated as a minor version change, not a major one2. A routine 1.x update can therefore require a newer Swift compiler. Pin ranges ("1.3.0"..<"1.8.0") if you must stay on an older toolchain.
Help/error text is not stable API. The exact wording and formatting of autogenerated help and error output may change between releases2. Do not write golden/snapshot tests against --help output across version bumps; assert on parsed values or exit codes instead.
run() is mutating on a fresh value. The command struct is decoded anew for each invocation and is a value type; mutations inside run() do not persist anywhere. Keep side effects explicit rather than expecting instance state to outlive the call.
Control flow is via thrown errors. Exit through throw ExitCode.failure, ValidationError, or CleanExit (used internally for --help). This means a bare catch-all in your own code can accidentally swallow the framework's exit signaling; let ArgumentParser's errors propagate to the top level so it can format and set the process exit code.
Testing. Separate parsing from execution: parseAsRoot(_:) returns the constructed command without running it, so unit tests can validate parsing behavior without spawning a process or capturing stdout. Help and error strings are English-only and not built for localization — tools needing translated CLI output must format their own messages.
When to Use / When Not
Use when:
- You're building a CLI in Swift and want help text, short/long options, subcommands, and shell
completions generated from the type definition.
- You want a first-party, zero-transitive-dependency parser that tracks the Swift toolchain.
- Your tool fits the declarative model: fixed flags/options/positionals with typed values.
Avoid when:
- You aren't writing Swift — this is Swift-only.
- You need help/usage formatting locked byte-for-byte across releases, or built-in i18n.
- You need fully dynamic argument sets computed at runtime; the reflection-over-declarations
model assumes arguments are known statically from the type.
Alternatives
- jakeheis/SwiftCLI — Swift CLI framework with a more imperative, router-style API; use it when you prefer explicit command registration over property-wrapper declarations.
- kylef/Commander — small, closure-based Swift command parser; use for tiny tools where ArgumentParser's structure is overkill.
- vapor/console-kit — CLI + console layer within the Vapor ecosystem; use when your tool already lives in a Vapor/SwiftNIO stack.
- clap-rs/clap — the Rust equivalent (derive macros over structs); reach for it if the tool is Rust, not Swift.
- spf13/cobra — the Go standard for command trees and completions; the reference point if you're comparing CLI ergonomics across languages.
History
| Version | Date | Notes |
|---|---|---|
| 0.0.1 | 2020-02 | Initial open-source release with the swift.org announcement1. Swift 5.1 minimum. |
| 0.2.0 | 2020 | Swift 5.2 minimum; API refinement during the pre-1.0 period. |
| 1.0.0 | 2021-11 | First source-stable release; semver commitment begins2. |
| 1.1.0 | 2022 | Swift 5.5 minimum; AsyncParsableCommand / async run()3. |
| 1.3.0 | 2024 | Swift 5.7 minimum3. |
| 1.8.0 | 2026 | Swift 6.0 minimum3. |
References
- ^ Nate Cook, "Announcing ArgumentParser," swift.org blog — 2020-02-27. https://swift.org/blog/argument-parser/
- ^ "Project Status," swift-argument-parser README (source-stability and semver policy). https://github.com/apple/swift-argument-parser#project-status
- ^ "Supported Versions" table, swift-argument-parser README (minimum Swift version per release). https://github.com/apple/swift-argument-parser#supported-versions
Tags
swift, cli, command-line, argument-parsing, apple, property-wrappers, developer-tools, swiftpm, type-safe, terminal