Dart Command-Line Arguments
last modified October 18, 2026
In this article we explore command-line arguments in Dart. The
entry point receives the arguments as a simple list, and the args
package turns that list into named options, flags, and subcommands.
Command-Line Arguments Overview
A Dart command-line program declares its entry point as
void main(List<String> args). Everything typed after the script
name arrives in that list with no interpretation: options, their values, and
positional arguments are all present as raw strings in order. For anything beyond
the simplest tool, that raw list is tedious to handle by hand, so the
args package provides ArgParser to describe and parse
a proper command-line interface.
| API | Purpose | Example |
|---|---|---|
main(args) |
Raw argument list | void main(List<String> args) |
ArgParser |
Declare and parse a CLI | final parser = ArgParser(); |
addFlag |
Boolean switch | addFlag('verbose', abbr: 'v') |
addOption |
Option that takes a value | addOption('name') |
addCommand |
Named subcommand | addCommand('add') |
parser.usage |
Generated help text | print(parser.usage); |
The examples in this article use the args package. Add it to a
project with dart pub add args.
Reading Raw Arguments
The entry point can declare a List<String> parameter to receive
the arguments. To pass arguments to a script you use dart run,
placing them after the script name.
void main(List<String> args) {
print('count: ${args.length}');
print(args);
}
Nothing is parsed here: the output is exactly what was typed. The list includes
foo, the flag-looking --bar, and baz. The
two dash prefix is just text at this level, which is why real programs hand the
list to a parser.
$ dart run args1_raw.dart foo --bar baz count: 3 [foo, --bar, baz]
Parsing Options and Flags
An ArgParser is built by declaring each flag and option, then
parse interprets the arguments. Flags are switches that are either present
or not; options take a value. Both may have short abbreviations and default
values.
import 'package:args/args.dart';
void main(List<String> arguments) {
final parser = ArgParser()
..addFlag('verbose', abbr: 'v', negatable: false)
..addOption('name', abbr: 'n', defaultsTo: 'world')
..addOption('count', abbr: 'c', defaultsTo: '1');
final results = parser.parse(arguments);
final name = results['name'] as String;
final count = int.parse(results['count'] as String);
final verbose = results['verbose'] as bool;
for (var i = 0; i < count; i++) {
print('Hello, $name${verbose ? ' ($i)' : ''}');
}
}
The verbose flag uses negatable: false, so both
-v and --verbose are accepted, but
--no-verbose is not. The options
name and count both declare defaults, so the program
runs even when they are omitted. Values come back as strings and are cast to the
expected type before use.
$ dart run args2_parser.dart -v -n Alice -c 2 Hello, Alice (0) Hello, Alice (1) $ dart run args2_parser.dart Hello, world
Generating Usage and Help
The parser can generate its own help text from the declared options. Each option
accepts a help string, and parser.usage renders the
whole interface. A common convention is a --help flag that prints it.
import 'package:args/args.dart';
void main(List<String> arguments) {
final parser = ArgParser()
..addFlag('help', abbr: 'h', negatable: false)
..addOption('output', abbr: 'o', help: 'Write output to a file')
..addFlag('verbose', abbr: 'v', help: 'Enable verbose logging');
final results = parser.parse(arguments);
if (results['help'] as bool) {
print('usage: tool [options]');
print(parser.usage);
return;
}
print('running');
}
When -h is passed, the program prints a usage line and then the
generated listing. Notice that the flag verbose is shown as
--[no-]verbose because it was not declared non-negatable, so both
forms are valid. Help text is derived from the parser, so the documentation never
drifts from the actual options.
$ dart run args3_help.dart -h usage: tool [options] -h, --help -o, --output Write output to a file -v, --[no-]verbose Enable verbose logging
Positional and Rest Arguments
Positional arguments, such as file names, are collected in
results.rest, while options are read by name. Unknown options cause
a FormatException; they are not collected as positional arguments.
Use -- to end option parsing and treat subsequent arguments as rest
arguments, even if they begin with a dash.
import 'package:args/args.dart';
void main(List<String> arguments) {
final parser = ArgParser()..addOption('output', abbr: 'o');
final results = parser.parse(arguments);
final output = results['output'] as String?;
print('output: ${output ?? '(stdout)'}');
for (final file in results.rest) {
print('file: $file');
}
}
The output option is read by name and defaults to null
when absent, printed here as (stdout). The two file names are not
options, so they land in rest and are listed in order. This split
between named options and positional arguments is the standard shape of a CLI.
$ dart run args4_rest.dart -o out.txt a.txt b.txt output: out.txt file: a.txt file: b.txt
Subcommands
Tools with several modes, such as git commit and
git push, are built with subcommands. Registering a name with
addCommand lets the parser select it, and the chosen command carries
its own rest and options.
import 'package:args/args.dart';
void main(List<String> arguments) {
final parser = ArgParser()
..addCommand('add')
..addCommand('remove');
final results = parser.parse(arguments);
final command = results.command;
if (command == null) {
print('usage: tool <add|remove> [files...]');
return;
}
switch (command.name) {
case 'add':
print('adding: ${command.rest}');
case 'remove':
print('removing: ${command.rest}');
}
}
results.command is the parsed subcommand, or null when
none was given. Its name selects the branch, and its own
rest holds the arguments that follow the command. Because both
commands only need positional arguments, no options are declared for them.
$ dart run args5_commands.dart add a b adding: [a, b]
Handling Parse Errors
Unknown options and invalid option syntax cause parse to throw a
FormatException. Value validation and conversion can also throw this
exception. Wrapping parsing and validation in a try block lets the
program print a friendly message instead of crashing with a stack trace.
import 'package:args/args.dart';
void main(List<String> arguments) {
final parser = ArgParser()..addOption('port', abbr: 'p');
try {
final results = parser.parse(arguments);
final value = results['port'] as String?;
if (value == null) {
throw FormatException('Missing required option --port.');
}
final port = int.parse(value);
print('port=$port');
} on FormatException catch (e) {
print('invalid arguments: ${e.message}');
}
}
The parser itself is strict: an unknown option such as --unknown
raises a FormatException. The explicit null check rejects a missing
port with a FormatException before numeric conversion.
Converting a non-numeric value with int.parse raises one as well.
All three cases are caught by the same clause, so the program reports the problem
cleanly.
$ dart run args6_error.dart -p abc invalid arguments: Invalid radix-10 number $ dart run args6_error.dart --unknown invalid arguments: Could not find an option named "--unknown". $ dart run args6_error.dart invalid arguments: Missing required option --port.
Source
Dart: Command-line apps
pub.dev: args package
Dart API: ArgParser
Dart API: List
Dart hands command-line arguments to the entry point as a plain list, and the
args package turns that list into a real interface. Flags, options,
defaults, generated help, positional rest arguments, and subcommands
all come from a declarative ArgParser, while a
FormatException guard keeps invalid input from crashing the program.
Author
List all Dart tutorials.