The admin UI
A local web dashboard for migrations, mail delivery, database seeders, every code generator, sessions and the template cache — started by pow ui, wired into nothing else.
$ pow ui
Starting the admin UI — migrations, mail, routes, seeders, sessions, storage and generators.
phponwheels listening on http://127.0.0.1:8081 (production mode) — Ctrl-C to stopA second, self-contained web application for setting up and maintaining a project: how many migrations are pending, what mail failed to send or is waiting for delivery, which routes compiled, which seeders are available, and how much is sitting in the session store and template cache. It also provides forms for every pow make:* generator. It reads and writes the same database, mail configuration and filesystem phponwheels itself uses; nothing here is a preview.
Why a second application
It would be simpler to add these pages to app/routes.php. That is exactly why they are not there. A page that can run migrations and seeders, deliver mail and wipe the template cache, merged into the real route table, ships with every deployment unless a developer remembers to strip it back out before going to production — and "remembers to remove it" is not a security boundary.
Instead, Admin\AdminApplication::boot() builds an independent Kernel\Application — its own routes, its own middleware, its own TemplateEngine pointed at templates that ship with the framework in src/Admin/templates rather than living in app/templates. It exists only for the lifetime of the process pow ui starts. Nothing about running it changes what app/routes.php serves, and nothing about deploying normally exposes it.
What stands in for one: pow ui binds to 127.0.0.1 by default — the same as pow serve — and warns loudly if told to bind anywhere else. Treat it the way you would a database console left open on your own machine: fine on localhost or over an SSH tunnel, never behind a public host or a port forward. Adding real authentication was deliberately left out rather than done halfway; see "Not built" below.
What it can do
| Page | Shows | Actions |
|---|---|---|
| Overview | One tile per area below | — |
| Migrations | Every migration file, applied or pending, with its batch | Run pending, roll back the last batch |
mail_log, filterable by status | Deliver up to 50 pending messages, resend one, resend every failed message | |
| Seeders | Seeder files in filename order | Run all seeders or one by name |
| Routes | The project's real, compiled route table | — |
| Sessions | Session files on disk | Remove the expired ones |
| Storage | Compiled templates on disk, and their size | Clear the template cache |
| Generate | Eight forms: authentication, class, controller, form, middleware, migration, model, seeder | Write the file(s); show what the CLI would have printed |
| Tools | The configured mail driver | Print a new APP_KEY; send one real test message |
These are the project-level CLI operations — pow migrate, pow db:seed, pow mail:list / mail:resend / mail:send-pending, pow routes, pow sessions:gc, pow storage:clear, every pow make:* generator, pow key:generate and pow mail:test — documented on The phponwheels CLI. The UI reuses the same Migrator, SeedRunner, PersistingMailer and Console\*Maker classes. A delivery or generation triggered by a button uses the same service as its terminal equivalent.
The routes page is worth being precise about: it reads app/routes.php directly — compiling a throwaway RouteCollection just to list what is in it — rather than serving those routes itself. The admin app's own router never touches them.
Generate
Eight pages under /generate, one per pow make:* command, each a plain form for the same options the CLI flags accept — a name, a lifetime, or model fields. Submitting one calls the same maker class the CLI uses, on the real project, and shows what it wrote — file paths, and any lines still left to paste into app/routes.php or app/bootstrap.php. Neither the CLI nor this UI edits those files for you; see The phponwheels CLI for why.
A submission that fails — a reserved word, a name that collides with an existing file, a field name that clashes with a form's own honeypot — redisplays the same form with the message and what was typed, rather than a redirect that would lose both. A successful one clears the name field but keeps the other choices, so generating several related classes with the same lifetime is a few clicks, not a re-selection each time.
The Authentication generator at /generate/auth runs make:auth without overwrite options. It creates verified registration, login, MFA, profiles and application-level user management. It does not add authentication to pow ui itself. New blank projects load the generated module automatically; older projects need the printed boot callback. Configure SMTP, migrate, register and verify, then use pow auth:admin <email> locally to appoint the first administrator. See authentication scaffolding.
Seeders
The Seeders page lists files from database/seeders/ and lets you run all of them in filename order or select one. It calls the framework's SeedRunner, just like pow db:seed. Seeders run on every invocation and have no migration ledger, so their database writes must be idempotent. Running a seeder executes project PHP code; only use the unauthenticated UI locally or through a trusted tunnel.
The Mail page also has a Deliver pending action, equivalent to pow mail:send-pending. It processes up to 50 queued messages per submission and records each outcome on its existing mail_log row.
Tools
pow key:generate and pow mail:test, as two small forms on one page. Generating a key prints it — the same as the CLI — and writes nothing; copying it into .env is still a deliberate, separate step. Sending a test message goes through the same PersistingMailer every controller uses, so it is one more row on the Mail page and, if it fails, resendable from there once whatever was wrong is fixed.
State-changing actions are still forms
Every action — running a migration or seeder, delivering mail, clearing a cache — is an ordinary <form method="post"> carrying a CSRF token, checked by the same CsrfMiddleware every other phponwheels application uses. There is no JavaScript anywhere in this interface, matching the rule the framework's own demo holds itself to: no <script>, no inline style=, no onclick. A destructive-looking action like "roll back last batch" is not gated behind a confirmation dialog, because that would need one — the button's label is the only warning it gets, the same way notes.onwheels.php's delete button works in the demo application.
Session cookie
Named phponwheels_admin_session, not the project's own SESSION_COOKIE. Cookies are scoped by host, not port — pow serve and pow ui both default to 127.0.0.1, just on different ports — so sharing a cookie name would let the two silently overwrite each other's session while running side by side, which is a completely ordinary way to run both during development.
Before the first migration
The Migrations page always works — Migrator creates its own bookkeeping table on first use, on any database, before a single project migration has run. The Mail page depends on mail_log existing and says so plainly rather than raising a raw database error:
$ pow ui
# visit /mail before running migrations
The mail_log table does not exist yet.
Run pending migrations to enable this page.Not built
- Authentication. Binding to
127.0.0.1is the only protection. A real login would need password storage, session handling for a second identity system and a decision about who is allowed to administer what — all reasonable, none of it built here. Put it behind an SSH tunnel or a VPN if it needs to be reached from anywhere but the machine it runs on. - A confirmation step before destructive actions. No JavaScript means no dialog; the button label is the whole warning.
- Editing data. Generate writes code — classes, controllers, migrations — the same files
pow make:*writes. It does not edit rows. Changing a note's title or a user's email is still a job for the database directly or a purpose-built admin resource in the application itself. - Concurrency beyond what OnWheelsServer already has. Connections are served sequentially, the same as
pow serve— fine for one developer administering one project, not a target for real traffic.