CLI
The api command group lets you version-control custom WordPress REST API endpoints as plain PHP files in Git. Each file becomes one REST route on the site, no other plugin required. See Writing Route Files for everything a route file can do: request handling, responses, authentication, CORS, and the security model.
Once deployed, every route file shows up in the API tab of the plugin’s admin UI.
Typical workflow
Section titled “Typical workflow”# 1. Download existing route files from WordPresslps api pull
# 2. Edit locally, commit to Gitgit add api/ && git commit -m "feat: add webhook endpoint"
# 3. Deploy back to WordPresslps api pushThe local directory
Section titled “The local directory”pull, push, and publish each operate on one local directory, resolved the same way:
- The
pathargument, if given - The
apiDirkey in the project’sloopress.json, if set ./api, the default
Commands
Section titled “Commands”lps api pull
Section titled “lps api pull”Download all custom route files from WordPress and write them as .php files, one per route.
lps api pull [path]| Argument | Default | Description |
|---|---|---|
path |
./api (or loopress.json’s apiDir) |
Local directory where route files are written |
| Flag | Description |
|---|---|
--dry-run / -d |
Show what would be written without touching the filesystem |
Local .php files whose route no longer exists on WordPress are removed on pull, so the directory always mirrors the site. In a terminal the files are listed and a confirmation is asked first (--yes skips it); in scripts and CI they are removed with a warning. --dry-run announces them ahead of time. Files without the .php extension are never touched.
The files you receive are exactly the source you (or a teammate) pushed: the ABSPATH guard the plugin injects at deploy time is stripped before the file is sent back, so pulls never introduce noise in your Git diffs.
Example:
lps api pull --dry-runlps api push
Section titled “lps api push”Upload .php files from a local directory to WordPress. Each file is matched by filename: pushing hello-world.php creates or overwrites the route file of the same name on the site.
lps api push [path]| Argument | Default | Description |
|---|---|---|
path |
./api (or loopress.json’s apiDir) |
Local directory to read .php files from |
| Flag | Description |
|---|---|
--dry-run / -d |
Show what would be pushed without making any changes |
--prune |
After pushing, delete route files on WordPress that have no local counterpart |
--yes / -y |
Skip the --prune confirmation prompt (required when --prune runs in a non-interactive shell) |
What push does, and deliberately does not do:
- Files are validated before anything is written. The CLI rejects filenames that aren’t lowercase kebab-case with an explicit message, and the plugin rejects files with a malformed
declare(strict_types=1);line, zero or more than one class declared, or a class name that collides with WordPress core, another active plugin, or anotherapi/file. Invalid PHP syntax is also rejected (returning the parse error) when the server can run the check; where it’s unavailable (no PHP CLI binary,execdisabled), the push succeeds and a broken file is instead caught later, whenRouteLoadertries to load it. See Writing Route Files for the exact rules. - One bad file doesn’t block the rest. Files are pushed one by one; a failure is reported per file and the remaining files still go through. The command exits with an error summarizing how many failed.
- Public routes are called out. A pushed route that declares
#[Permission(public: true)]gets a warning: it runs for anyone, with no authentication. The push still succeeds; see the blast radius of a public route. - Without
--prune, pushing never deletes a route on WordPress, even if the local file is gone.--pruneopts into removing routes absent locally: it lists them, asks for confirmation, then deletes each. In a non-interactive shell it refuses unless--yesis also given, because, unlikelps api pull’s local cleanup, a pruned route on the server is not recoverable from your repo.
Example:
lps api push ./apilps api push --prune # also remove routes deleted locallylps api rm
Section titled “lps api rm”Delete one route file from WordPress. This is the only way to take a route off the server through the CLI: route files live under wp-content/, outside the plugin directory, so deactivating the plugin does not remove them.
lps api rm <filename>| Argument | Description |
|---|---|
filename |
The route slug without .php, e.g. hello-world or invoice-pdf/[order_id] |
| Flag | Description |
|---|---|
--yes / -y |
Skip the confirmation prompt (required in a non-interactive shell) |
--dry-run / -d |
Show what would be removed without deleting anything |
Example:
lps api rm legacy-webhooklps api rm legacy-webhook --yes # in CIlps api list
Section titled “lps api list”Print all custom route files currently on WordPress.
lps api list| Flag | Description |
|---|---|
--json |
Output raw JSON (filename, full file content, and public per route) instead of formatted text |
A route that runs with no authentication (#[Permission(public: true)]) is badged [PUBLIC], with a warning after the list. See the blast radius of a public route.
Example output:
Found 2 route files:
hello-world open-webhook [PUBLIC]
» Warning: [PUBLIC] routes run for anyone, with no authentication. Review them.lps api publish
Section titled “lps api publish”Publish local route files to your Loopress account so they can be deployed to other projects. This does not touch any WordPress site, it uploads to Loopress only.
Requires lps login first, and the current project must be linked to your Loopress account (lps project push).
lps api publish [path]| Argument | Default | Description |
|---|---|---|
path |
./api (or loopress.json’s apiDir) |
Local directory to read .php files from |
Example:
lps api publishlps api publish ./apiFile format
Section titled “File format”Each file is named {slug}.php and declares exactly one class, named however you like, with one public method per HTTP verb:
<?php
declare(strict_types=1);
class HelloWorld{ public function get(): array { return ['message' => 'Hello, world!']; }}Pushed, this answers at /wp-json/loopress-api/v1/hello-world.
That’s the shape; the full reference lives in Writing Route Files: reading the request, response types and status codes, opening up a route with permission(), CORS via headers(), the configurable namespace, using Composer packages, and how broken files are isolated.