Custom Commands
The binary produced by pw build can run your own subcommands as well as the
web server. This is useful for imports, backfills, scheduled batch jobs, and
operational maintenance: the command lives in the same application, so it can
reuse the generated queries and domain code called by web handlers instead of
building a second CLI project.
For the development tool itself, see pw command. Configuration files, environment variables, generated scaffolds, and their precedence are covered in Application Configuration.
Your own subcommands
Section titled “Your own subcommands”A subcommand needs only a struct and one registration call. pw generate reads
that call site and writes the parser, leaving no parallel flag wiring to
maintain.
package main
type importCommand struct { Path string `arg:"required" help:"CSV file to import"` Label string `arg:"optional"` Tags []string `arg:"*"` DryRun bool `default:"false" help:"parse without writing"`}| Tag | Meaning |
|---|---|
arg:"required" |
a positional argument that must be supplied |
arg:"optional" |
a positional argument that may be omitted |
arg:"*" |
variadic positional arguments, zero or more |
(no arg tag) |
an option, with the same tags as configuration fields |
Register it before configuration is parsed, then dispatch:
func main() { handlers.RegisterConfig() pw.RegisterSubCommand[importCommand]("import", "import a CSV file")
if err := pw.ParseConfig(); err != nil { log.Fatal(err) } if command, ok := pw.Command[importCommand](); ok { if err := runImport(context.Background(), command); err != nil { log.Fatal(err) } return }
if err := pw.Run(context.Background(), handlers.Handlers()); err != nil { log.Fatal(err) }}./myapp import ./data/users.csv --dry-runpw.Command[T] reports false unless that subcommand was the one selected, so
the if doubles as the dispatch. Only the chosen subcommand receives input.
Calling pw.ParseConfig yourself is safe: pw.Run calls it too, and it parses
exactly once.
Registration order matters for the same reason it does for configuration —
generated definitions register during package init, so RegisterSubCommand
must run after every init and before ParseConfig. Registering after parsing
panics.
One name is taken: healthcheck belongs to the framework’s
container health probe,
and registering it panics at startup. A Dockerfile that already says
HEALTHCHECK CMD ["/myapp", "healthcheck"] must keep meaning the probe, so the
collision fails fast instead of shadowing one or the other.
The subcommand shares the server’s parsed configuration. pw.Config[T]
therefore returns the same values the server would use, including the DSN,
without a second settings path to keep in sync.
The database pool is a separate matter: it is opened by pw.Run and
pw.Middlewares, not by ParseConfig. A subcommand that needs a connection
opens one from the configured DSN itself.
