trk / processwire-console
A professional CLI tool for ProcessWire CMS.
Fund package maintenance!
Requires
- php: >=8.3
- dragonmantank/cron-expression: ^3.3
- laravel/prompts: ^0.3.16
- symfony/console: ^7.0
Suggests
- fakerphp/faker: Ideal for generating dummy data in database seeders.
README
ProcessWire Console
vendor/bin/wire
A professional CLI for ProcessWire CMS/CMF.
57 production-ready commands · JSON output for AI agents · Composer-driven extensibility
Table of Contents
- Why ProcessWire Console?
- Installation
- Shell Alias (Recommended)
- Quick Start
- Command Reference
- JSON Output (Machine-Readable)
- Interactive Mode
- Extending the Console
- Security
- Architecture
- Requirements
- License
Why ProcessWire Console?
ProcessWire is a powerful CMF but lacks a first-party CLI. This package fills that gap with 57 production-ready commands purpose-built for ProcessWire's architecture:
| Domain | Commands | Description |
|---|---|---|
| Content (Pages) | 8 commands | Create, update, publish, move, trash, restore |
| Schema (Fields) | 7 commands | List, info, create, update, rename, attach, detach |
| Schema (Templates) | 6 commands | List, info, create, update, rename, reorder fields |
| Modules | 5 commands | List, install, uninstall, refresh, upgrade |
| Users & RBAC | 11 commands | Users, roles, permissions management |
| Cache & Logs | 7 commands | Clear caches, list/read/tail/clear log files |
| Database | 4 commands | Backup, restore, list backups, purge old backups |
| Scaffolding | 5 commands | Generate modules, migrations, queues, seeders, schedules |
| Migrations | 8 commands | Run, rollback, reset, refresh, fresh, status |
| Queues | 5 commands | Work, failed, retry, clear, initialize tables |
| Seeding | 1 command | Populate the database with test data |
| Scheduling | 1 command | Run due scheduled tasks |
| Maintenance | 2 commands | Put the application up or down |
| Runtime | 2 commands | Interactive REPL, command listing |
| Testing | 2 commands | Run tests, scaffold test files |
| Total | 73 commands |
Every command supports --json for machine-readable output, --dry-run for safe previews, and --force for non-interactive scripting.
Installation
composer require trk/processwire-console
A wire symlink is created at your project root automatically. If it isn't, run:
php vendor/bin/wire list
Verify Installation
php vendor/bin/wire list
If the database is not configured, the CLI will still start and display command help. Full functionality requires a working ProcessWire installation with database access.
Shell Alias (Recommended)
Typing php vendor/bin/wire every time is tedious. Set up a shell alias to use just wire:
macOS / Linux (Bash/Zsh)
Add this line to your shell config file (~/.zshrc, ~/.bashrc, or ~/.bash_profile):
# Add ProcessWire Console alias echo 'alias wire="php vendor/bin/wire"' >> ~/.zshrc && source ~/.zshrc
For Bash users, replace
~/.zshrcwith~/.bashrc.
Windows (PowerShell)
Run in PowerShell to add a persistent alias:
# Create profile if it doesn't exist, then add alias if (!(Test-Path $PROFILE)) { New-Item -Path $PROFILE -Force } Add-Content $PROFILE 'function wire { php vendor/bin/wire @args }' . $PROFILE
Windows (CMD)
Create a wire.bat file in a directory that's in your PATH:
@echo off php vendor/bin/wire %*
After Setup
Now you can use wire directly:
# Before php vendor/bin/wire page:list --template=basic-page # After ✨ wire page:list --template=basic-page wire user:create -i wire migrate:status
Quick Start
# List all available commands php vendor/bin/wire list # Get detailed help for any command php vendor/bin/wire help page:create # Create a page php vendor/bin/wire page:create --parent=/ --template=basic-page --title="Hello World" # List pages as JSON php vendor/bin/wire page:list --template=basic-page --json # Create a user with roles php vendor/bin/wire user:create # Backup the database php vendor/bin/wire db:backup # Interactive REPL php vendor/bin/wire tinker # Start Queue Worker php vendor/bin/wire queue:work
Command Reference
Pages
Manage the ProcessWire page tree — create, update, publish, move, trash, and restore pages.
page:list
List pages with filtering, sorting, and pagination.
# List the 20 most recent pages php vendor/bin/wire page:list # Filter by template and parent php vendor/bin/wire page:list --template=blog-post --parent=/blog/ --limit=50 # Sort by title ascending php vendor/bin/wire page:list --sort=title --limit=10 # Include hidden and unpublished pages php vendor/bin/wire page:list --include=all # JSON output for scripting php vendor/bin/wire page:list --template=product --json
Options:
| Option | Short | Description |
|---|---|---|
--template |
-t |
Filter by template name |
--parent |
-p |
Filter by parent path or ID |
--sort |
-s |
Sort field (default: -created). Prefix with - for descending |
--limit |
-l |
Number of results (default: 20) |
--include |
-i |
Include scope: all, hidden, unpublished (default: all) |
--json |
Output as JSON |
page:find
Find pages using a raw ProcessWire selector string.
php vendor/bin/wire page:find "template=blog-post, title%=ProcessWire, limit=10" # JSON output php vendor/bin/wire page:find "parent=/products/, sort=-created, limit=5" --json
Options:
| Option | Description |
|---|---|
selector |
Argument: ProcessWire selector string (required) |
--json |
Output as JSON |
page:create
Create a new page under a parent with a specific template.
# Basic creation php vendor/bin/wire page:create --parent=/ --template=basic-page --title="About Us" # Create as unpublished php vendor/bin/wire page:create --parent=/blog/ --template=blog-post --title="Draft Post" --unpublished # Fully interactive mode — prompts for template, parent, title, and all fields php vendor/bin/wire page:create -i # Preview without saving php vendor/bin/wire page:create --parent=/ --template=basic-page --title="Test" --dry-run
Options:
| Option | Short | Description |
|---|---|---|
--parent |
-p |
Parent page path or ID (required) |
--template |
-t |
Template name (required) |
--title |
Page title | |
--name |
URL-safe name (auto-generated from title if omitted) | |
--unpublished |
Create as unpublished | |
--interactive |
-i |
Prompt for all values including template field filling |
--dry-run |
Preview changes without saving | |
--json |
Output as JSON |
page:update
Update page properties and field values.
# Update title php vendor/bin/wire page:update --id=1042 --title="New Title" # Set arbitrary fields with --set php vendor/bin/wire page:update --id=1042 --set="summary=Updated summary" --set="body=New body content" # Change page status php vendor/bin/wire page:update --id=1042 --status=hidden # Move and rename php vendor/bin/wire page:update --id=1042 --parent=/another-parent/ --name=new-url-name # Preview changes php vendor/bin/wire page:update --id=1042 --title="Preview" --dry-run
Options:
| Option | Description |
|---|---|
--id |
Page ID (required; or use --path) |
--path |
Page path (alternative to --id) |
--title |
New title |
--name |
New URL-safe name |
--parent |
New parent path or ID |
--template |
Change template |
--status |
Status: published, unpublished, hidden, locked |
--set |
Set field value as key=value (repeatable) |
--dry-run |
Preview without saving |
--json |
Output as JSON |
page:publish / page:unpublish
php vendor/bin/wire page:publish --id=1042
php vendor/bin/wire page:unpublish --id=1042
# Skip confirmation
php vendor/bin/wire page:publish --id=1042 --force
page:trash / page:restore
php vendor/bin/wire page:trash --id=1042
php vendor/bin/wire page:restore --id=1042
# Force without confirmation
php vendor/bin/wire page:trash --id=1042 --force
page:move
php vendor/bin/wire page:move --id=1042 --parent=/new-section/ php vendor/bin/wire page:move --id=1042 --parent=1001 --dry-run
Fields
Full lifecycle management for ProcessWire fields.
field:list
# List all fields php vendor/bin/wire field:list # Filter by type php vendor/bin/wire field:list --type=FieldtypeText # Search by name or label php vendor/bin/wire field:list --search=body # JSON output php vendor/bin/wire field:list --json
field:info
php vendor/bin/wire field:info --name=body php vendor/bin/wire field:info --name=body --json
field:update
# Update label and description php vendor/bin/wire field:update --name=body --set="label=Body Text" --set="description=Main content area" # Change field settings php vendor/bin/wire field:update --name=summary --set="maxlength=500" --set="required=1"
field:rename
php vendor/bin/wire field:rename --name=old_field_name --new-name=new_field_name php vendor/bin/wire field:rename --name=body --new-name=content --dry-run
field:attach / field:detach
# Attach a field to a template php vendor/bin/wire field:attach --template=blog-post --field=images # Attach after a specific field php vendor/bin/wire field:attach --template=blog-post --field=tags --after=body # Detach a field from a template php vendor/bin/wire field:detach --template=blog-post --field=sidebar
field:delete
php vendor/bin/wire field:delete --name=unused_field php vendor/bin/wire field:delete --name=unused_field --force
Templates
template:list
php vendor/bin/wire template:list php vendor/bin/wire template:list --search=blog --json
template:info
php vendor/bin/wire template:info --name=basic-page php vendor/bin/wire template:info --name=basic-page --json
template:update
php vendor/bin/wire template:update --name=blog-post --set="label=Blog Article" php vendor/bin/wire template:update --name=blog-post --set="noChildren=1" --set="noParents=-1"
template:rename
php vendor/bin/wire template:rename --name=old-template --new-name=new-template
template:fields:reorder
# Reorder fields within a template php vendor/bin/wire template:fields:reorder --template=blog-post --fields="title,body,images,tags,summary"
template:delete
php vendor/bin/wire template:delete --name=unused-template php vendor/bin/wire template:delete --name=unused-template --force
Modules
A complete module lifecycle manager — discover, install, enable, disable, upgrade.
module:list
# List all installed modules php vendor/bin/wire module:list # Core modules only php vendor/bin/wire module:list --core # Site (third-party) modules only php vendor/bin/wire module:list --site # Search php vendor/bin/wire module:list --search=Image # JSON output php vendor/bin/wire module:list --site --json
module:install / module:uninstall
php vendor/bin/wire module:install --name=ModuleName php vendor/bin/wire module:uninstall --name=ModuleName --force
module:refresh
# Refresh all module caches php vendor/bin/wire module:refresh # Refresh a specific module php vendor/bin/wire module:refresh --name=ModuleName
module:upgrade
php vendor/bin/wire module:upgrade --name=ModuleName
Users & RBAC
Complete role-based access control management from the command line.
Users
# List all users php vendor/bin/wire user:list php vendor/bin/wire user:list --role=editor --json # Create a user (interactive prompts for password, email, roles) php vendor/bin/wire user:create # Create a user non-interactively php vendor/bin/wire user:create --name=john --email=john@example.com --password=SecretPass --role=editor --force # Update a user php vendor/bin/wire user:update --name=john --email=new@example.com php vendor/bin/wire user:update --name=john --add-role=admin --remove-role=guest # Delete a user php vendor/bin/wire user:delete --name=john php vendor/bin/wire user:delete --id=1045 --force
Roles
# List roles php vendor/bin/wire role:list php vendor/bin/wire role:list --json # Create a role php vendor/bin/wire role:create --name=editor --title="Content Editor" # Grant a permission to a role php vendor/bin/wire role:grant --role=editor --permission=page-edit # Revoke a permission from a role php vendor/bin/wire role:revoke --role=editor --permission=page-delete
Permissions
# List permissions php vendor/bin/wire permission:list # Create a custom permission php vendor/bin/wire permission:create --name=my-custom-permission # Delete a custom permission php vendor/bin/wire permission:delete --name=my-custom-permission
Cache
cache:clear
Clears compiled files, session caches, and file caches.
php vendor/bin/wire cache:clear php vendor/bin/wire cache:clear --json
cache:wire:clear
Clear WireCache entries by exact key or SQL LIKE pattern.
# Clear a specific cache key php vendor/bin/wire cache:wire:clear --key=MyModule.someData # Clear all cache entries matching a pattern php vendor/bin/wire cache:wire:clear --pattern="Template%" php vendor/bin/wire cache:wire:clear --pattern="MyModule.%" --json
Logs
log:list
List all available log files.
php vendor/bin/wire log:list php vendor/bin/wire log:list --json
log:read
Retrieve and display ProcessWire logs as a formatted table.
# Read 'errors' log php vendor/bin/wire log:read errors # Read with a specific date range php vendor/bin/wire log:read messages --from="2026-04-01" --to="today" # Limit output and filter by string php vendor/bin/wire log:read errors --limit=50 --find="Exception" # JSON output php vendor/bin/wire log:read errors --json
log:tail
Read the last N lines of a log file and follow output.
# Read errors.txt (last 200 lines by default) php vendor/bin/wire log:tail errors # Read fewer lines php vendor/bin/wire log:tail exceptions --lines=20 # Follow output (like tail -f) php vendor/bin/wire log:tail messages --follow # JSON output php vendor/bin/wire log:tail errors --json
log:clear
Clear a specific log file.
php vendor/bin/wire log:clear errors php vendor/bin/wire log:clear exceptions --force
log:clear-all
Clear all ProcessWire log files.
php vendor/bin/wire log:clear-all php vendor/bin/wire log:clear-all --force
Database & Backup
db:backup
Create a database backup using ProcessWire's native WireDatabaseBackup.
php vendor/bin/wire db:backup php vendor/bin/wire db:backup --json
db:restore
Restore the database from a backup file.
php vendor/bin/wire db:restore --file=backup-2026-04-08.sql php vendor/bin/wire db:restore --file=backup-2026-04-08.sql --force
Warning: This operation replaces your entire database. Always back up before restoring.
backup:list
php vendor/bin/wire backup:list php vendor/bin/wire backup:list --json
backup:purge
Remove old backup files, keeping only the most recent N backups.
# Keep the 5 most recent backups
php vendor/bin/wire backup:purge --keep=5
php vendor/bin/wire backup:purge --keep=3 --force
Scaffolding
make:module
Scaffold a new ProcessWire module from a professional stub.
# Standard module php vendor/bin/wire make:module HelloWorld # Fieldtype module php vendor/bin/wire make:module FieldtypeColor --type=fieldtype # Inputfield module php vendor/bin/wire make:module InputfieldColor --type=inputfield # Process (admin page) module php vendor/bin/wire make:module ProcessDashboard --type=process # With custom metadata php vendor/bin/wire make:module MyModule \ --type=module \ --title="My Custom Module" \ --summary="Does amazing things" \ --author="Developer Name" \ --mod-version=1.0.0 \ --autoload
Module types and stubs:
| Type | Base Class | Use Case |
|---|---|---|
module |
WireData |
General-purpose module with init/ready hooks |
fieldtype |
Fieldtype |
Custom database field storage |
inputfield |
Inputfield |
Custom form input widget |
process |
Process |
Admin page with URL routing |
make:migration
Scaffold a new timestamped migration file. See Migrations for full documentation.
php vendor/bin/wire make:migration create_blog_section php vendor/bin/wire make:migration add_body_field --type=create-field --field=body
Migrations
A complete migration system for ProcessWire — schema versioning with up/down methods, batch tracking, rollback support, and 7 typed stubs for common PW operations.
Migrations live in site/migrations/ and are tracked in the wire_migrations database table (auto-created on first run).
make:migration
Generate a new timestamped migration file from a stub.
# Blank migration php vendor/bin/wire make:migration create_blog_section # Create a field php vendor/bin/wire make:migration add_subtitle_field --type=create-field --field=subtitle --fieldtype=FieldtypeText # Create a template php vendor/bin/wire make:migration create_blog_template --type=create-template --template=blog-post --label="Blog Post" # Attach field to template php vendor/bin/wire make:migration attach_images_to_blog --type=attach-field --template=blog-post --field=images # Create a page php vendor/bin/wire make:migration create_about_page --type=create-page --template=basic-page --parent=/ --label="About Us" # Create a role with permissions php vendor/bin/wire make:migration create_editor_role --type=create-role --label="Content Editor" # Install a module php vendor/bin/wire make:migration install_seo_module --type=install-module --module=SeoMaestro
Stub types:
--type |
Generates | up() | down() |
|---|---|---|---|
blank |
Empty skeleton (default) | — | — |
create-field |
Field creation | new Field() + save |
delete field |
create-template |
Template + fieldgroup + file | Full scaffold | delete template + file |
attach-field |
Field-to-template attachment | fieldgroup add | fieldgroup remove |
create-page |
Page creation | new Page() + save |
delete page |
create-role |
Role + permission grants | roles add + grant | delete role |
install-module |
Module install | modules->install() |
uninstall |
Dependency Order (Critical)
ProcessWire objects have strict dependency chains. Migrations must respect this order:
CREATE order: Field → Fieldgroup → Template → Page
DELETE order: Page → Template → Fieldgroup → Field
For a typical blog setup, the correct migration sequence is:
# 1. Create fields first php vendor/bin/wire make:migration create_body_field --type=create-field --field=body php vendor/bin/wire make:migration create_summary_field --type=create-field --field=summary # 2. Create template (depends on fields existing) php vendor/bin/wire make:migration create_blog_template --type=create-template --template=blog-post # 3. Attach fields to template php vendor/bin/wire make:migration attach_body_to_blog --type=attach-field --template=blog-post --field=body php vendor/bin/wire make:migration attach_summary_to_blog --type=attach-field --template=blog-post --field=summary # 4. Create pages last (depends on template existing) php vendor/bin/wire make:migration create_blog_page --type=create-page --template=blog-post --parent=/
On rollback, these run in reverse order (last-in-first-out) — pages deleted before templates, fields detached before being deleted.
Safe-by-Default Guards
All stubs include precondition guards in down() that throw clear errors instead of cascading destructive operations:
| Stub | Guard | Error |
|---|---|---|
create-field |
Field attached to template(s)? | "Cannot delete — detach first" |
create-template |
Pages using this template? | "Cannot delete — remove pages first" |
attach-field |
Pages have data in this field? | "Cannot detach — data would be lost" |
create-page |
Page has children? | "Cannot delete — remove children first" |
create-role |
Users assigned this role? | "Cannot delete — remove from users first" |
install-module |
Other modules depend on it? | "Cannot uninstall — dependencies exist" |
make:queue
Generate a new file-based Queue class in ProcessWire Console.
php vendor/bin/wire make:queue SendEmailQueue
# Save directly to a specific module instead of the global site/queue directory
php vendor/bin/wire make:queue CompileImagesQueue --module=FieldtypeAiAssistant
Options:
| Option | Short | Description |
|---|---|---|
name |
Class name ending with Queue (required) | |
--module |
-m |
Placed inside site/modules/YourModule/queue/ |
Design philosophy: Stubs never auto-delete user content or cascade-remove dependencies. They fail loudly so you can write the correct cleanup migration yourself.
Each migration file returns an anonymous class with up() and down() methods:
<?php declare(strict_types=1); namespace ProcessWire; return new class { public function up(): void { $field = new Field(); $field->type = wire('modules')->get('FieldtypeText'); $field->name = 'subtitle'; $field->label = 'Subtitle'; wire('fields')->save($field); } public function down(): void { $field = wire('fields')->get('subtitle'); if (!$field || !$field->id) { return; } // Guard: field must not be attached to any template $fieldgroups = $field->getFieldgroups(); if ($fieldgroups->count() > 0) { $names = $fieldgroups->implode(', ', 'name'); throw new WireException( "Cannot delete field 'subtitle' — attached to: {$names}. Detach first." ); } wire('fields')->delete($field); } };
migrate
Run all pending migrations.
# Apply all pending php vendor/bin/wire migrate # Apply one at a time php vendor/bin/wire migrate --step=1 # Preview without applying php vendor/bin/wire migrate --dry-run # Non-interactive php vendor/bin/wire migrate --force # JSON output php vendor/bin/wire migrate --json
migrate:rollback
Rollback the last batch of migrations (calls down() in reverse order).
# Rollback last batch php vendor/bin/wire migrate:rollback # Rollback last 3 individual migrations php vendor/bin/wire migrate:rollback --step=3 # Preview php vendor/bin/wire migrate:rollback --dry-run
migrate:reset
Rollback ALL applied migrations.
php vendor/bin/wire migrate:reset php vendor/bin/wire migrate:reset --force
migrate:refresh
Reset + re-run all migrations (shortcut for reset then migrate).
php vendor/bin/wire migrate:refresh php vendor/bin/wire migrate:refresh --force --json
migrate:fresh
Drop the tracking table entirely and re-run all migrations from scratch.
php vendor/bin/wire migrate:fresh php vendor/bin/wire migrate:fresh --force
Warning:
migrate:freshdoes not calldown()— it drops the tracking table and re-applies all migrations. Usemigrate:refreshif you need clean rollbacks.
migrate:status
Show the status of all migration files in a formatted table.
php vendor/bin/wire migrate:status php vendor/bin/wire migrate:status --json
Output example:
+---------------------------------------------+---------+-------+
| Migration | Status | Batch |
+---------------------------------------------+---------+-------+
| 20260408120000_create_blog_template.php | Applied | 1 |
| 20260408120100_add_featured_image_field.php | Applied | 1 |
| 20260408120200_create_editor_role.php | Pending | - |
+---------------------------------------------+---------+-------+
! [NOTE] 2 applied, 1 pending.
migrate:install
Explicitly create the wire_migrations tracking table (auto-created by migrate if missing).
php vendor/bin/wire migrate:install
Version
Show the active version of ProcessWire, processwire-console, and associated ecosystem packages like processwire-boost.
php vendor/bin/wire version php vendor/bin/wire -V
Queues
ProcessWire Console offers a lightweight, file-based Queue Management System that automatically discovers *Queue.php classes.
queue:table
Create the database tables (queue_jobs and failed_jobs) required to store queue data.
php vendor/bin/wire queue:table
queue:work
Start the queue worker daemon to process background jobs continually.
php vendor/bin/wire queue:work --queue=default --sleep=3 --tries=3
Options:
| Option | Description |
|---|---|
--queue |
Name of the queue. Default: default |
--sleep |
Seconds to wait before polling again if empty. Default: 3 |
--tries |
Attempts before a job fails. Default: 3 |
queue:failed
List recent failed jobs cleanly in a table.
php vendor/bin/wire queue:failed
queue:retry
Push a failed job back onto the queue for processing, or bulk retry all.
php vendor/bin/wire queue:retry 12 php vendor/bin/wire queue:retry all
queue:clear
Wipe all logs from the failed_jobs database.
php vendor/bin/wire queue:clear
Database Seeding
Populate your database with initial data or fake data using ProcessWire Console.
Tip: If you need fake data generation, it is highly recommended to install Faker via
composer require fakerphp/faker. The base Seeder class will automatically inject a Faker instance for you to use via$this->faker. You can also just parse local JSON files natively.
make:seeder
Generate a new seeder stub.
php vendor/bin/wire make:seeder UsersSeeder php vendor/bin/wire make:seeder DemoProductsSeeder --module=Shop
db:seed
Run the database seeders. It auto-discovers seeders in site/seeders/ and site/modules/*/seeders/.
# Run all discovered seeders php vendor/bin/wire db:seed # Run a specific seeder class php vendor/bin/wire db:seed --class=UsersSeeder
Testing
ProcessWire Console integrates Pest PHP natively to bring beautiful, modern Test-Driven Development (TDD) to ProcessWire.
Tip: If Pest is not installed on your system when you run the
testcommand, the console will detect it and elegantly prompt you to install it and automatically scaffold thetests/Pest.phpconfig.
Our intelligent FeatureDiscoverer auto-magically locates all Pest tests placed in:
/tests//site/tests//site/modules/[AnyActiveModule]/tests/
...and runs them holistically via a single command!
make:test
Generate a new Pest test stub.
# Create a Feature test in site/tests/Feature/ php vendor/bin/wire make:test UserLoginTest # Create a Unit test in site/tests/Unit/ php vendor/bin/wire make:test DataSyncTest --unit # Create a test inside a specific module's tests/ directory php vendor/bin/wire make:test AiOrchestration --module=FieldtypeAiAssistant
test
Run your Pest test suite. You can pass forward any native Pest arguments (e.g., --filter, --parallel).
# Run all discovered tests across the system and active modules php vendor/bin/wire test # Run tests and filter for "Login" php vendor/bin/wire test --filter=Login
Task Scheduling
Manage scheduled, recurring tasks from a single cron entry using dragonmantank/cron-expression.
Cron Configuration: Add a single entry to your server's crontab that runs every minute:
* * * * * cd /path/to/project && php vendor/bin/wire schedule:run >> /dev/null 2>&1
make:task
Generate a new scheduled task stub.
php vendor/bin/wire make:task SyncDataTask php vendor/bin/wire make:task CleanupTask --module=AppAdmin
schedule:run
Evaluate and run any scheduled tasks that are currently due.
php vendor/bin/wire schedule:run
Maintenance Mode
Easily put your application into a maintenance or disabled state.
Requirement: For ProcessWire to respond to this state, update site/init.php to check for site/assets/down.json. (See the Boost pw-maintenance skill for a code snippet).
down
Put the application into maintenance mode.
# Basic down php vendor/bin/wire down # With a bypass secret to allow developers to access via ?secret=dev123 php vendor/bin/wire down --secret=dev123 # Redirect visitors to another URL php vendor/bin/wire down --redirect=https://status.example.com
up
Bring the application out of maintenance mode.
php vendor/bin/wire up
Tinker (REPL)
An interactive ProcessWire PHP REPL for quick testing and debugging.
php vendor/bin/wire tinker
ProcessWire Tinker — type "exit" to quit.
>>> $pages->count("template=basic-page")
12
>>> $user->name
admin
>>> $config->urls->templates
/site/templates/
Security: Set
PW_CLI_DISABLE_TINKER=1in production environments to disable this command.
JSON Output (Machine-Readable)
Every command supports the --json flag for machine-readable output. This makes ProcessWire Console first-class for CI/CD pipelines, AI agent integrations, and automation scripts.
Response format:
{
"ok": true,
"data": {
"items": [],
"total": 0
}
}
Error format:
{
"ok": false,
"error": {
"code": "NOT_FOUND",
"message": "Page not found: /nonexistent"
}
}
Examples:
# Pipe page data to jq php vendor/bin/wire page:list --template=blog-post --json | jq '.data.items[].title' # Count modules php vendor/bin/wire module:list --site --json | jq '.data.total' # Use in shell scripts BACKUP=$(php vendor/bin/wire db:backup --json | jq -r '.data.file') echo "Backup saved to: $BACKUP"
Interactive Mode
Commands that create or modify data support interactive prompts powered by Laravel Prompts:
page:create -i— Select template from a list, enter parent path, fill template fields interactivelyuser:create— Prompts for username, email, password (with confirmation), and role selection
Interactive prompts are automatically disabled when:
- The
--jsonflag is present - The
--forceflag is present - The terminal is non-interactive (piped input)
Extending the Console
Method 1: Module Feature Auto-Discovery
ProcessWire Console features a centralized FeatureDiscoverer that automatically locates and registers CLI assets from the root site/ directory and exclusively from installed/active modules. If a module is inactive or uninstalled, its CLI features are safely ignored.
Place the corresponding files inside the target hierarchy (site/... or site/modules/[ModuleName]/...):
commands/— Drop-in Symfony Console commands (*Command.php)migrations/— Database migrations (*_[name].php)seeders/— Database seeders (*Seeder.php)schedule/— Scheduled cron tasks (*Task.php)queue/— Background queue jobs (*Queue.php)
They are automatically discovered and loaded on every CLI invocation.
<?php // site/commands/SyncProductsCommand.php use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; class SyncProductsCommand extends Command { protected function configure(): void { $this ->setName('products:sync') ->setDescription('Synchronize products from external API.'); } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); // Your logic here — $pages, $modules etc. are available via wire() $io->success('Products synchronized.'); return Command::SUCCESS; } }
Method 2: Composer Package Discovery
Register commands in your package's composer.json:
{
"name": "vendor/my-pw-package",
"extra": {
"processwire-console": {
"commands": [
"Vendor\\MyPackage\\Commands\\ImportCommand",
"Vendor\\MyPackage\\Commands\\ExportCommand"
]
}
},
"autoload": {
"psr-4": {
"Vendor\\MyPackage\\": "src/"
}
}
}
The console engine reads vendor/composer/installed.json and root composer.json path repositories to discover commands automatically.
Security
Path Traversal Protection
Log commands (log:tail, log:read, log:clear) sanitize the log name argument with basename() to prevent directory traversal attacks.
Tinker Guard
The tinker command executes arbitrary PHP via eval(). To disable it in production:
export PW_CLI_DISABLE_TINKER=1
Confirmation Prompts
All destructive commands (*:delete, *:trash, db:restore, backup:purge, migrate:reset, migrate:fresh) require explicit confirmation. Use --force to bypass for scripting.
Migration Safety
Migration stubs enforce safe-by-default guards in down() — they never cascade-delete user content. If a field is in use, a template has pages, or a role is assigned to users, the rollback will throw a descriptive WireException instead of silently destroying data. See Safe-by-Default Guards for details.
Dry-Run Mode
Preview any mutation with --dry-run:
php vendor/bin/wire page:update --id=1042 --title="Test" --dry-run
php vendor/bin/wire template:delete --name=old-template --dry-run
php vendor/bin/wire db:restore --file=backup.sql --dry-run
Architecture
processwire-console/
├── bin/
│ └── wire # CLI entry point (bootstrap + command registration)
├── src/
│ ├── Commands/ # 57 command classes
│ │ ├── Page*.php # 8 page management commands
│ │ ├── Field*.php # 7 field management commands
│ │ ├── Template*.php # 6 template management commands
│ │ ├── Module*.php # 7 module lifecycle commands
│ │ ├── User*.php # 4 user commands
│ │ ├── Role*.php # 4 role commands
│ │ ├── Permission*.php # 3 permission commands
│ │ ├── Migrate*.php # 8 migration commands
│ │ ├── Cache*.php # 2 cache commands
│ │ ├── Logs*.php # 3 log commands
│ │ ├── Db*.php # 2 database commands
│ │ ├── Backup*.php # 2 backup commands
│ │ ├── Make*.php # 4 scaffolding commands (incl. make:migration)
│ │ └── TinkerCommand.php # Interactive REPL
│ └── Migration/ # Migration engine
│ ├── MigrationRepository.php # Database tracking (wire_migrations table)
│ └── Migrator.php # Core engine (discovery, up/down, batches)
├── resources/
│ └── stubs/ # Scaffolding templates
│ ├── module.stub
│ ├── fieldtype.stub
│ ├── inputfield.stub
│ ├── process.stub
│ ├── template.stub
│ ├── migration.stub # Blank migration
│ ├── migration.create-field.stub # Field creation
│ ├── migration.create-template.stub # Template + fieldgroup
│ ├── migration.attach-field.stub # Field-to-template
│ ├── migration.create-page.stub # Page creation
│ ├── migration.create-role.stub # Role + permissions
│ └── migration.install-module.stub # Module install
└── composer.json
Bootstrap Flow
- Locate Composer autoloader (supports standard and symlinked setups)
- Walk upward to find
wire/core/ProcessWire.php - Boot ProcessWire with database config (graceful fallback if unavailable)
- Register 57 built-in commands (including 8 migration commands)
- Auto-discover custom commands dynamically from:
- Site profile:
site/commands/ - Installed modules:
site/modules/*/commands/
- Site profile:
- Discover commands from Composer packages (
extra.processwire-console.commands) - Run the Symfony Console application
Design Principles
- Null-safe: All ProcessWire API
->get()calls are guarded againstnullreturns - Strict typing:
declare(strict_types=1)in every file - Consistent I/O: All commands use
SymfonyStylefor formatted output - Dual output: Human-readable tables + machine-readable JSON on every command
- Non-destructive defaults: Confirmations on mutations,
--dry-runfor previews
Requirements
| Dependency | Version |
|---|---|
| PHP | ≥ 8.3 |
| ProcessWire | 3.x |
symfony/console |
^7.0 |
laravel/prompts |
^0.3.13 |
License
MIT — see LICENSE for details.