CLI Commands
The holo command line tool creates projects, generates files, runs migrations, starts workers, and runs your own app commands. This page lists every built-in command with its arguments and flags.
Running holo
holo ships with the @holo-js/cli package, which every Holo project installs. Run it through your package manager from the project folder:
npx holo listpnpm dlx holo listyarn dlx holo listbunx holo listThe rest of the docs write every command with npx. Swap in your package manager's runner if you use a different one.
You can also run holo without a command, or with help, --help, or -h, to print the command list.
List all commands
npx holo listThe output has two sections: Internal commands (built into Holo) and App commands (the ones you write in server/commands, plus any commands from plugins). See Custom Commands.
Help for one command
Add --help or -h to any command to print its usage line and description:
npx holo make:model --helpPrint the version
npx holo --version-v works too.
How arguments and flags work
These rules apply to every command, including your own app commands.
| Form | Meaning |
|---|---|
--name value or --name=value | A flag with a value. |
--name | A boolean flag (true). |
-m | A short flag. -mfs sets -m, -f, and -s together. |
--queue a --queue b | Repeating a flag collects every value. |
--queue a,b | Most list flags also accept comma-separated values. |
-- | Everything after -- is passed as plain arguments. |
Number flags such as --step and --tries must be non-negative whole numbers.
Pick the environment
Every command accepts --env to pick the environment for that run. It wins over APP_ENV from your shell or .env, and decides which .env files load. See Env files and loading order.
npx holo migrate:fresh --env=testInteractive prompts
When you run a command in a terminal and leave out a required argument, holo asks for it. Some commands, like new, install, and make:model, also ask follow-up questions.
Turn prompts off with --no-interactive:
npx holo make:model Post --no-interactivePrompts are also skipped automatically when the command doesn't run in a terminal (for example in CI). In that case, a missing required argument makes the command fail.
Project commands
new
Create a new Holo project in a new folder and install its dependencies.
npx holo new <name> [options]| Argument / flag | Description |
|---|---|
<name> | Project folder name. You can pass --name <name> instead. The folder must be empty or not exist. |
--framework <nuxt|next|sveltekit> | Framework to scaffold. Default: nuxt. |
--database <sqlite|mysql|postgres> | Database driver. Default: sqlite. |
--package-manager <npm|pnpm|yarn|bun> | Package manager used to install dependencies. Default: npm. |
--package <name> | Optional package to include. Repeat the flag or pass a comma-separated list. Values: storage, events, queue, validation, forms, auth, authorization, notifications, mail, broadcast, realtime, security, cache, or none. Choosing forms also adds validation. |
--storage-default-disk <local|public> | Default disk when storage is included. Default: local. |
npx holo new blog --framework next --database postgres --package auth,mail,queueTIP
Most people start with create holo-js instead. See Installation.
dev
Write the generated schema types from your default connection's database, refresh Holo's other generated files, then start your framework's dev server.
npx holo dev [...frameworkArgs]The dev server doesn't need the database. If holo dev can't read it, it prints a warning that names the connection and starts with the generated schema it already has, or an empty one. Run npx holo migrate once the database is reachable to write it. holo dev and your app never create a missing SQLite file: pages that don't query the database still work, and a query fails with an error that names the file and tells you to run npx holo migrate.
While it runs, holo dev watches your Holo folders (config/, .env files, server/models, server/jobs, and so on) and refreshes the generated files when they change, restarting the dev server when needed.
Press Ctrl+C to stop it. A second Ctrl+C while it shuts down force-stops the server and its child processes and exits with status 130.
| Argument / flag | Description |
|---|---|
...frameworkArgs | Any extra arguments and flags are passed to the framework's dev server. |
If you don't pass --port (or -p), Holo uses the PORT environment variable when it's set.
npx holo dev --port 4000If the port is already in use, holo dev exits with a non-zero status and names the port. If another holo dev for this project is already running, the message gives its PID. holo dev never stops a process for you, so stop the other server yourself and run it again.
Your scaffolded package.json already has "dev": "holo dev", so npm run dev does the same thing.
build
Refresh Holo's generated files, then run your framework's production build. holo build reads the generated schema types from your default connection's database, so the database must be reachable; it doesn't load or run migrations. If the database can't be read, the build stops with an error that names the connection. A SQLite database file that doesn't exist counts as unreadable: holo build never creates it. Run npx holo migrate to create it. A SQLite :memory: database is new and empty in every process, so holo build can't read your tables from one and writes a generated schema with no tables.
npx holo build [...frameworkArgs]| Argument / flag | Description |
|---|---|
...frameworkArgs | Extra arguments passed to the framework build. |
npx holo buildstart
Start the production server from a finished build.
npx holo start [...frameworkArgs]| Argument / flag | Description |
|---|---|
...frameworkArgs | Extra arguments passed to the production server. |
Press Ctrl+C to stop it. A second Ctrl+C while it shuts down force-stops the server and its child processes and exits with status 130.
Like dev, start uses PORT when you don't pass --port. It loads your .env files and runs the server with NODE_ENV=production. The application environment still comes from APP_ENV, which is production when it isn't set. See Deployment.
PORT=8080 npx holo startprepare
Scan your project and regenerate the files in .holo-js/generated (model, migration, job, event, and command registries, plus generated types).
npx holo preparedev and build run this for you, and your scaffolded package.json runs it on install. Run it yourself after pulling changes or when your editor's types look out of date. prepare doesn't read the database or load your migrations. It registers each migration by its file name.
If .holo-js/generated/schema.generated.ts fails to load, for example a file written by an earlier release, every command replaces it with an empty generated schema, prints a one-line notice and continues. The next migrate, build or dev writes it again from the database.
install
Add a first-party Holo feature to an existing project. It adds the package, creates config files and folders, updates .env and .env.example, then installs dependencies. Packages are added in your project's Holo version style: exact if every @holo-js/* dependency is pinned exactly, ~ if every one uses ~, and ^ otherwise. A setting it adds to .env or .env.example goes next to the related settings already in the file, such as other REDIS_* keys. Settings with no related ones in the file are appended at the end.
npx holo install <target> [options]| Argument / flag | Description |
|---|---|
<target> | One of queue, events, auth, authorization, notifications, mail, broadcast, realtime, security, cache, media, schedule. |
--driver <name> | Only for queue (sync, redis, database; default sync) and cache (file, redis, database; default file). Other targets reject this flag. |
--social | auth only. Add social login support. |
--provider <name> | auth only. Social provider to add: google, github, discord, facebook, apple, linkedin. Repeat or comma-separate. Implies --social. |
--workos | auth only. Add WorkOS support. |
--clerk | auth only. Add Clerk support. |
What each target creates:
| Target | Creates |
|---|---|
queue | config/queue.ts, server/jobs |
events | server/events, server/listeners. Offers to set up queued listeners if queues aren't installed. |
auth | config/auth.ts, config/session.ts, config/security.ts, config/cors.ts, server/models/User.ts |
authorization | server/policies, server/abilities |
notifications | config/notifications.ts |
mail | config/mail.ts, server/mail |
broadcast | config/broadcast.ts, server/broadcast, server/channels, the /broadcasting/auth route, and client setup |
realtime | server/realtime and framework setup |
security | config/security.ts, config/cors.ts |
cache | config/cache.ts (and config/redis.ts for Redis, or the cache connection in an existing one) |
media | config/media.ts and the media table migration |
schedule | server/schedule.ts. Says when cache or queue is missing, since withoutOverlapping(), onOneServer() (with a store every server shares, such as Redis or the database), and scheduled jobs need them. |
npx holo install queue --driver redis
npx holo install auth --provider google,githubkey:generate
Write a random APP_KEY to your project's .env file. If .env doesn't exist, it is created. If APP_KEY already has a value, nothing changes.
npx holo key:generateconfig:cache
Compile every file in config/ into one cached file at .holo-js/generated/config-cache.json. Each env() call is evaluated with your current env values, and the results, secrets included, are written into the file. Holo still reads .env to choose the environment. When the app runs in production (APP_ENV=production, from your shell or from .env) and the cache was built for production, Holo reads the cache instead of loading config/*.ts. Config modules that contain functions, such as app with an exception reporter or security with rate-limit callbacks, are loaded from their files, but the env() values they read while loading are recorded in the cache too. Code that calls env() after startup, such as a reporter's body, reads the runtime environment. Other environments ignore the cache.
APP_ENV=production npx holo config:cacheWARNING
In production, changes to config/*.ts or .env have no effect after you cache config until you run config:cache again or config:clear. Rebuild the cache on every deploy, after your env variables are in place. The cache file contains your secrets, so keep it out of version control.
config:clear
Delete the config cache so Holo loads config/*.ts again.
npx holo config:clearagents:install
Install Holo-JS skill files for AI coding agents so they can look up Holo docs while they work. See Coding with AI Agents.
npx holo agents:install [options]Aliases: agent:install, ai:install.
| Flag | Description |
|---|---|
--agent <name> | Agents to install for: all, codex, claude, cursor, windsurf, opencode, gemini, kiro. Repeat or comma-separate. In a terminal you get a picker; otherwise every agent is installed. |
--global | Install into your home folder instead of the current project. |
--force | Replace skill folders that you changed by hand. Without it, the command refuses to overwrite them. |
npx holo agents:install --agent claude,cursorauth:notifications:publish
Copy the built-in auth notifications (such as email verification and password reset) into your app so you can edit them. Existing files are skipped.
npx holo auth:notifications:publishGenerators
Generators create a file from a template and then refresh the generated registries. Names can include folders, such as reports/send-digest. A generator fails if the file or name already exists.
make:model
Create a model, and optionally its migration, observer, seeder, and factory.
npx holo make:model <name> [options]| Argument / flag | Description |
|---|---|
<name> | Model name, such as Post or blog/Post. |
--table <table> | Table name to use instead of the default. |
-m, --migration | Also create a migration. |
-o, --observer | Also create an observer and attach it to the model. |
-s, --seeder | Also create a seeder. |
-f, --factory | Also create a factory. |
In a terminal, if you pass none of -m, -o, -s, -f, you're asked which files to create.
npx holo make:model Post -mfsCreates server/models/Post.ts, plus files in server/db/migrations, server/db/factories, and server/db/seeders.
make:migration
Create a timestamped migration in server/db/migrations.
npx holo make:migration <name> [--create <table> | --table <table>]| Argument / flag | Description |
|---|---|
<name> | Migration name, such as create_posts_table. |
--create <table> | Scaffold a migration that creates this table. |
--table <table> | Scaffold a migration that changes this existing table. |
Use one of --create or --table, not both. Without either, Holo guesses from names like create_posts_table or add_status_to_posts_table.
npx holo make:migration add_status_to_posts_table --table postsmake:seeder
Create a seeder in server/db/seeders.
npx holo make:seeder <name>npx holo make:seeder UserSeedermake:factory
Create a factory in server/db/factories.
npx holo make:factory <name>npx holo make:factory PostFactorymake:observer
Create a model observer in server/db/observers. It doesn't attach the observer to an existing model; use make:model --observer for that.
npx holo make:observer <name>npx holo make:observer PostObservermake:job
Create a queue job in server/jobs. File names are converted to kebab-case, and the job name uses dots for folders.
npx holo make:job <name>npx holo make:job reports/send-digestCreates server/jobs/reports/send-digest.ts with the job name reports.send-digest.
make:event
Create an event in server/events.
npx holo make:event <name>npx holo make:event user/registeredCreates server/events/user/registered.ts with the event name user.registered.
make:listener
Create an event listener in server/listeners.
npx holo make:listener <name> --event <event-name>| Argument / flag | Description |
|---|---|
<name> | Listener name. |
--event <event-name> | Required. The event to listen to. Repeat or comma-separate to listen to several events. |
npx holo make:listener user/send-welcome-email --event user.registeredmake:mail
Create a mail definition in server/mail. Mail files are created as Markdown mail.
npx holo make:mail <name> [--markdown]| Argument / flag | Description |
|---|---|
<name> | Mail name, such as auth/verify-email. |
--markdown | Create a Markdown mail. This is already the default. |
npx holo make:mail orders/order-shippedmake:broadcast
Create a broadcast event in server/broadcast.
npx holo make:broadcast <name>npx holo make:broadcast orders/shipment-updatedmake:channel
Create a channel authorization file in server/channels.
npx holo make:channel <pattern>| Argument | Description |
|---|---|
<pattern> | Channel name pattern. Use {param} for dynamic parts. |
npx holo make:channel orders.{orderId}Database commands
migrate
Run every migration that hasn't run yet, then write the generated schema types from the database. A skipped --isolated run leaves them unchanged. migrate:rollback and migrate:fresh write them too.
When the database of the default connection, or of another connection a migration uses, doesn't exist, migrate and migrate:fresh create it the first time they use it: the SQLite file and its directory, or the PostgreSQL or MySQL database. They are the only commands that create a database. holo seed and other commands fail on a missing one.
npx holo migrate [--step <n>] [--isolated]| Flag | Description |
|---|---|
--step <n> | Run at most this many pending migrations. |
--isolated | Take the migrations lock from the default cache store first. If another run holds it, this run skips and exits successfully. Use it when several replicas run migrate at deploy time; every server must share the store, such as redis or database. If @holo-js/cache isn't installed, the store can't be resolved, or the store can't give a lock, the run fails before migrating and the error names that cause. See Isolating migration runs. |
npx holo migratemigrate:fresh
Drop every table on the default connection and run all migrations from scratch.
npx holo migrate:fresh [--seed] [--only <names>] [--quietly] [--force]| Flag | Description |
|---|---|
--seed | Run seeders after migrating. |
--only <names> | With --seed, run only these seeders (comma-separated). |
--quietly | With --seed, run seeders without firing model events. |
--force | Skip the production confirmation. |
npx holo migrate:fresh --seedWhen APP_ENV is production, migrate:fresh asks you to confirm before it drops anything. Without a terminal, it stops with an error unless you pass --force.
DANGER
migrate:fresh deletes all your data. Never run it against a production database.
migrate:rollback
Undo migrations.
npx holo migrate:rollback [--step <n>] [--batch <n>] [--force]| Flag | Description |
|---|---|
--step <n> | Roll back at most this many migrations from the batch. |
--batch <n> | Roll back this batch number instead of the latest one. |
--force | Skip the production confirmation. |
With no flags, every migration in the latest batch is rolled back. When APP_ENV is production, the command asks you to confirm first, and without a terminal it stops with an error unless you pass --force.
npx holo migrate:rollback --step 1seed
Run your seeders.
npx holo seed [--only <names>] [--quietly] [--force]| Flag | Description |
|---|---|
--only <names> | Run only these seeders. Comma-separate or repeat the flag. |
--quietly | Run seeders without firing model events (observers don't run). |
--force | Skip the production confirmation. |
npx holo seed --only roles,adminseed writes the generated schema types from the database before it runs your seeders, so models resolve against the current tables. If it can't read the database, it warns and runs them with the generated schema it has. When APP_ENV is production, seed asks you to confirm first. Without a terminal, it stops with an error unless you pass --force.
prune
Delete old records from models that define a prunable query.
npx holo prune [ModelName ...]| Argument | Description |
|---|---|
ModelName ... | Prune only these models. Without names, every prunable model is pruned. Naming an unknown or non-prunable model is an error. |
npx holo prune Session AuditLogSchedule commands
See Task Scheduling for how to declare tasks in server/schedule.ts.
schedule:run
Run every task that is due this minute, then exit. Run it from a system cron entry of * * * * *.
npx holo schedule:runA failing task is reported and the rest still run. SIGINT and SIGTERM are passed on to the command that is running; no further tasks start, and schedule:run exits once that command has.
schedule:work
Start schedule:run at the start of every minute, in the foreground. Use it instead of a cron entry.
npx holo schedule:workCtrl+C, SIGINT, and SIGTERM stop it. The signal is passed on to any schedule:run still running, and schedule:work exits once they have. If schedule:run can't be started, it stops the same way and exits with the error.
schedule:list
List the tasks with their cron expressions and next due times in the app's timezone.
npx holo schedule:listQueue commands
See Running Workers and Failed Jobs for the full workflow.
queue:work
Process jobs from an async queue connection (redis or database). The sync driver doesn't need a worker.
npx holo queue:work [options]| Flag | Description |
|---|---|
-c, --connection <name> | Queue connection from config/queue.ts. Default: the default connection. |
-q, --queue <name> | Queue names to process. Repeat or comma-separate. |
--once | Process one job and stop. |
--stop-when-empty | Stop when the queue has no more jobs. |
--sleep <seconds> | Seconds to wait when the queue is empty. Default: the connection's sleep setting, or 1. |
--tries <n> | Maximum attempts per job, overriding the job's setting. |
--timeout <seconds> | Maximum run time per job, overriding the job's setting. |
--max-jobs <n> | Stop after processing this many jobs. |
--max-time <seconds> | Stop after running this long. |
The worker stops cleanly on SIGINT / SIGTERM and when queue:restart is run.
npx holo queue:work --connection redis --queue emails,media --tries 3queue:listen
Run a queue worker for development. It restarts the worker whenever your jobs, config, or related files change.
npx holo queue:listen [options]Accepts the same flags as queue:work except --once and --stop-when-empty.
npx holo queue:listen --connection redis --queue mediaCtrl+C, SIGINT, and SIGTERM stop the worker after its current job and then stop queue:listen. A second Ctrl+C while it shuts down force-stops the worker and exits with status 130. A restart waits for the old worker to exit. If the old worker reports a process error while it stops, queue:listen exits with that error and doesn't start a replacement. On Windows the worker's process tree is ended at once, so a job in progress is interrupted and retried later.
queue:restart
Tell every running worker to stop after its current job. Your process manager (systemd, PM2, Docker, and so on) then starts fresh workers with your new code. queue:listen restarts its worker automatically.
npx holo queue:restartqueue:clear
Delete pending jobs from a connection.
npx holo queue:clear [--connection <name>] [--queue <name>]| Flag | Description |
|---|---|
-c, --connection <name> | Connection to clear. Default: the default connection. |
-q, --queue <name> | Only clear these queues. Repeat or comma-separate. |
npx holo queue:clear --connection redis --queue emailsqueue:failed
List failed jobs.
npx holo queue:failedqueue:retry
Push failed jobs back onto their queue.
npx holo queue:retry <id|all>| Argument | Description |
|---|---|
<id|all> | A failed job ID from queue:failed, or all. |
npx holo queue:retry allqueue:forget
Delete one failed job record.
npx holo queue:forget <id>queue:flush
Delete every failed job record.
npx holo queue:flushqueue:table
Create the migration for the database queue driver's jobs table.
npx holo queue:tablequeue:failed-table
Create the migration for the failed jobs table.
npx holo queue:failed-tableCache commands
cache:clear
Remove every item from a cache store, whatever its key prefix. For a Redis store this runs FLUSHDB on its database; see Cache drivers before pointing a store at shared data.
npx holo cache:clear [--driver <name>]| Flag | Description |
|---|---|
-d, --driver <name> | Store name from config/cache.ts. Default: the default store. |
npx holo cache:clear --driver rediscache:forget
Remove one key from a cache store.
npx holo cache:forget <key> [--driver <name>]| Argument / flag | Description |
|---|---|
<key> | The cache key to remove. |
-d, --driver <name> | Store name. Default: the default store. |
npx holo cache:forget dashboard.statscache:table
Create the migration for the database cache driver's tables (cache entries and locks).
npx holo cache:tableSecurity commands
rate-limit:clear
Clear rate limit counters. Works with the file and redis drivers. The memory driver lives inside your server process, so it can't be cleared from the CLI.
npx holo rate-limit:clear [--limiter <name>] [--key <value>] [--all]| Flag | Description |
|---|---|
--limiter <name> | The limiter to clear. Required unless you pass --all. |
--key <value> | Only clear this key within the limiter, such as user:42. |
--all | Clear every limiter. |
npx holo rate-limit:clear --limiter login --key "ip:203.0.113.7"Media commands
media:table
Create the migration for the media table. holo install media already does this, so you only need it when you add @holo-js/media by hand.
npx holo media:tablemedia:regenerate
Rebuild image conversions for stored media. Like seed, it writes the generated schema types from the database first.
npx holo media:regenerate [ModelName ...] [--ids=<ids>] [--only=<names>] [--only-missing]| Argument / flag | Description |
|---|---|
ModelName ... | Only regenerate media attached to these models. |
--ids=<ids> | Only these media IDs (positive integers, comma-separated). |
--only=<names> | Only these conversions, such as thumb. Comma-separated. |
--only-missing | Only create conversions that don't exist yet. |
npx holo media:regenerate Post --only=thumbStorage commands
storage:decode-names
Rename files that Holo 0.3 stored under URL-encoded names on a local disk, such as May%20report.csv, to their real names, such as May report.csv. Run it once for each local disk after upgrading to 0.4.0.
npx holo storage:decode-names --disk=local| Flag | Description |
|---|---|
--disk=<name> | The local disk to rename files on. Required. |
It prints every rename and every file it skips, then the total. Running it again right away renames nothing. Run it before new uploads arrive: a file uploaded after the upgrade under a name that looks URL-encoded, such as May%20report.csv, would be renamed by a later run. It skips:
- a name that still looks URL-encoded after decoding, such as
100%2520off.txt, because Holo can't tell what the original name was; - a name whose decoded form already exists, checked again right before each rename;
- a name whose decoded form differs only by letter case or Unicode normalization from another decoded name or an existing name in the same folder, such as
cafÉ.txtandcafé.txt, because some filesystems treat them as the same file; - a file whose decoded name ends in
$, because local disks use that ending for metadata; - on Windows, a name Windows can't store, such as one with
:or?, one ending in a dot or space, or a device name such asCON; - a symbolic link, which is never followed, along with its metadata file.
A file and its metadata are checked together before either is renamed. If either name must be skipped, both are skipped. If the second rename fails, the command attempts to restore the first name and reports any failure.
It never follows a symlink inside the disk, so files outside the disk root are never renamed. The disk's root in config/storage.ts is used as the disk, including when that path is itself a symlink, as the rest of the disk does. Rename skipped files by hand. S3 disks are not supported; see S3 file names with special characters.
Broadcast commands
broadcast:work
Start the self-hosted broadcast WebSocket server. It reads config/broadcast.ts and your channel files, and prints the host and port it listens on. Stop it with Ctrl+C or SIGTERM.
npx holo broadcast:workSee Scaling in Production for running it alongside your app.
Plugin commands
Plugins are npm packages that add drivers, config, or commands to Holo. See Writing Plugins.
Every plugin:* command also works with a plugins: prefix (for example plugins:add).
plugin:add
Install a plugin package, add any packages it needs, activate it in config/app.ts, and refresh generated files. If any step fails, your package.json, config/app.ts, and .holo-js folder are restored.
npx holo plugin:add <package>npx holo plugin:add @acme/holo-plugin-sentryplugin:remove
Deactivate a plugin in config/app.ts.
npx holo plugin:remove <package> [--uninstall]| Argument / flag | Description |
|---|---|
<package> | Plugin package name. |
--uninstall | Also remove the package from package.json and reinstall dependencies. |
npx holo plugin:remove @acme/holo-plugin-sentry --uninstallplugin:list
List active plugins and whether each one loaded.
npx holo plugin:listplugin:info
Show a plugin's name, description, and what it adds (drivers, config files, env keys, commands, and so on).
npx holo plugin:info <package>plugin:doctor
Load every active plugin and report problems. Exits with an error if any plugin fails.
npx holo plugin:doctorNext steps
- Custom Commands — add your own
holocommands - Configuration Files
- Environment Variables
- Deploying Holo Apps