The litestar CLI is a click Group, and
plugins can attach commands to it. Subclass CLIPlugin,
implement on_cli_init, register the plugin on your app. No separate console script, no argparse mess, and your command ships with the app it operates on. Just a litestar $yourCoolCommand and you're ready to go!
Everything below is against Litestar 2.24.0, the current release. Here's the whole thing. Click through the tabs, or jump straight to :
from litestar import Litestar, getfrom cli_plugin import GreetingCLIPlugin@get("/")async def index() -> dict[str, str]: return {"hello": "world"}app = Litestar(route_handlers=[index], plugins=[GreetingCLIPlugin()])from __future__ import annotationsfrom typing import TYPE_CHECKINGfrom litestar.plugins import CLIPluginif TYPE_CHECKING: from click import Groupclass GreetingCLIPlugin(CLIPlugin): """Adds a `greet` command to the app's `litestar` CLI.""" def on_cli_init(self, cli: Group) -> None: import click @cli.command() @click.option("--name", default="world", help="Who to greet.") def greet(name: str) -> None: """Say hello from your app's own CLI.""" click.echo(f"Hello, {name}!")[project]name = "litestar-cli-plugin-demo"version = "0.1.0"description = "Minimal example of a custom Litestar CLI plugin"requires-python = ">=3.11"dependencies = ["litestar[standard]>=2.24,<3"]The plugin
CLIPlugin asks for one method. on_cli_init hands you the root Group
behind the litestar command, and anything you attach to it shows up in
litestar --help next to the built-ins. In
that's a single greet command with one option.
Two deliberate choices in there. click is imported inside on_cli_init, not
at module top level, because the CLI machinery only exists when someone is
actually running the CLI. Importing your app for ASGI serving never pays for
it. The Group type import lives under TYPE_CHECKING for the same reason:
it's annotation-only.
CLIPlugin, not CLIPluginProtocol
Most examples you'll find (including the
current CLI docs) subclass CLIPluginProtocol.
Use CLIPlugin instead. We started moving plugin protocols to plain base
classes in 2.15, where InitPlugin
and SerializationPlugin replaced their *Protocol names, which now carry
deprecation notices. On the main branch, CLIPluginProtocol is gone from
litestar.plugins entirely. CLIPlugin is the name that survives into 3.0.
On 2.x you lose nothing by switching. CLIPlugin subclasses the protocol, and
it also gives you server_lifespan(), a context manager that wraps
litestar run if you need setup and teardown around the dev server.
Wiring it up
Registration is the plugins=[GreetingCLIPlugin()] line in ,
identical to any other Litestar plugin. The route handler in exists to prove this is a servable app. The CLI plugin goes right along side it (not replacing it.)
CLI plugins are one corner of a much bigger extension surface. By the way if you want to learn a little deeper about the various plugin options Litestar has you can check out my talk on the topic presented at EuroPython 2026. This includes the lifecycle hooks with on_app_init, teaching Litestar to serialize your own types, custom DI providers, and contributing to the OpenAPI schema.
Run it
Copy the command from the workspace footer above. It downloads the project and
invokes the new command through uv:
litestar greet --name pythonista
# Hello, pythonista!Because greet is registered on the real CLI group, litestar greet --help
comes for free, and litestar run still serves the app.