rest-api
Skill4 starsChanged 4 months agoMITView on GitHub
Build custom WordPress REST API endpoints — register_rest_route, controllers, a mandatory permission_callback, argument sanitization and validation, responses and errors, and authentication with cookie plus nonce or application passwords. Use when the user wants to expose a custom REST API route in WordPress with register_rest_route to read or write data; the user needs to authenticate a WordPress REST request or restrict a route to certain capabilities; the user needs to sanitize and validate incoming arguments on a WordPress REST endpoint. Keywords: wordpress, rest-api, register-rest-route, permission-callback, authentication, sanitization, validation, input, auth, api.
7 other projects use a version of this.
Adopted by
Most recently taken up by
- G1Joshi/Agent-Skillstheir own version, first seen Oct 13, 2026
- Abdox-menara/guard-skillstheir own version, first seen Aug 24, 2026
- cusyio/agentic-software-engineering_detheir own version, first seen Jul 18, 2026
- aborroy/aiup-alfrescotheir own version, first seen Jun 22, 2026
- cusyio/agentic-software-engineeringtheir own version, first seen Jun 21, 2026
- yiweiwan/skillstheir own version, first seen May 12, 2026
Other versions7 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 7
- G1Joshi/Agent-Skills★ 15same name
- aborroy/aiup-alfresco★ 14same name
- ffsshhttiikk/opencode-agents-skills★ 2same name
- yiweiwan/skills★ 1same name
- Abdox-menara/guard-skills★ 0same name
- cusyio/agentic-software-engineering★ 0same name
- cusyio/agentic-software-engineering_de★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository119 other files it gives its agents.
- strategy-bitrix-d7-incrementalSkill
- strategy-explicit-over-magicSkill
- strategy-framework-conventionsSkill
- strategy-legacy-safe-editsSkill
- strategy-minimal-diffSkill
- strategy-no-untested-refactorSkill
- strategy-preserve-public-apiSkill
- strategy-readonly-immutabilitySkill
- strategy-security-firstSkill
- strategy-test-drivenSkill
- openapiSkill
- paginationSkill
- rest-designSkill
- versioningSkill
- cqrsSkill
- dependency-inversionSkill
- hexagonalSkill
- repository-patternSkill
- service-layerSkill
- bitrixSkill
- findings-reportSkill
- laravelSkill
- performanceSkill
- phpSkill
- project-analysisSkill
- refactoringSkill
- roi-prioritizationSkill
- securitySkill
- stack-detectionSkill
- cache-layersSkill
- component-engineSkill
- d7-eventsSkill
- d7-loaderSkill
- d7-orm-queriesSkill
- iblock-elementsSkill
- kernel-main-moduleSkill
- localizationSkill
- controllersSkill
- ormSkill
- validationSkill
- controllersSkill
- query-builderSkill
- routingSkill
- autoload-psr4Skill
- dependency-managementSkill
- scriptsSkill
- connection-poolingSkill
- mysql-indexingSkill
- redisSkill
- transactionsSkill
- ci-github-actionsSkill
- deployment-strategiesSkill
- docker-phpSkill
- env-configSkill
- associationsSkill
- entities-mappingSkill
- orm-basicsSkill
- entity-apiSkill
- module-developmentSkill
- routing-controllersSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
7 rules. 0 are common, 6 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 6 edited versions we read keep these.
6
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 76
Do not build a REST route for a single internal admin-only action that fits admin-ajax or an admin-post handler.
- Distinctive: line 77
Do not hand-roll a route when a core endpoint (posts, users, media) or a post type with show_in_rest already exposes the resource.
- Distinctive: line 125
Verify: The localized settings object exposes a nonce string in the page.
- Distinctive: line 128
Verify: The logged-in request authenticates and no longer returns 401 rest_cookie_invalid_nonce.
- Very specific: line 144
Verify: GET /wp-json/acme/v1/books resolves to the callback.
- Distinctive: line 147
Verify: Anonymous callers get 401/403 and invalid arguments return a WP_Error.
- Distinctive: line 150
Verify: A valid request returns JSON with status 200 and no stray output.
“Common” is measured: kept by at least 6 in 10 of the 6 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<!-- generated: do not edit — source: knowledge/atoms/wordpress/rest-api/ --><!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 --><!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->WordPress REST API
When to use
- The user wants to expose a custom REST API route in WordPress with register_rest_route to read or write data.
- The user needs to authenticate a WordPress REST request or restrict a route to certain capabilities.
- The user needs to sanitize and validate incoming arguments on a WordPress REST endpoint.
When NOT to use
- Do not build a REST route for a single internal admin-only action that fits admin-ajax or an admin-post handler.
- Do not hand-roll a route when a core endpoint (posts, users, media) or a post type with show_in_rest already exposes the resource.
Core guidance
- Always set a real permission_callback — Give every route a
permission_callbackthat performs a real capability or ownership check; use__return_trueonly for a deliberately public read and never omit it. - Register routes on rest_api_init with a versioned namespace — Call
register_rest_route()from arest_api_initcallback using a vendor, versioned namespace such asacme/v1and a resource-style route.
Decision Engine
Walk this section before choosing an implementation path.
Who calls this route — anonymous readers, your own logged-in pages, or an
external machine client?
├── External server or integration, no browser session: → application-passwords — the client sends Basic auth with a per-application,
revocable credential over HTTPS; still enforce capabilities per route.
│ └── Client needs stateless bearer tokens, not Basic auth: → jwt — add a JWT authentication layer when clients need short-lived stateless
bearer tokens; keep the same per-route capability checks.
├── Your own theme or admin pages, logged-in user: → cookie-nonce — rely on the auth cookie plus the wp_rest nonce in the
X-WP-Nonce header, with a capability check in permission_callback.
└── Anonymous read of already-public data: → public — permission_callback __return_true, read-only methods, and no private
fields; still sanitize and validate every argument.
Full engine: references/decision-engine.md — Decision Tree, Priority Matrix, Decision Table.
Workflows
Workflow: Authenticate Client
Progress:
- [ ] Enqueue your script and `wp_localize_script` a value from
`wp_create_nonce('wp_rest')` so front-end JavaScript can read the nonce
- [ ] In the fetch call set the `X-WP-Nonce` header to the localized nonce and send
credentials so the auth cookie accompanies the request- Enqueue your script and
wp_localize_scripta value fromwp_create_nonce('wp_rest')so front-end JavaScript can read the nonce.- Verify: The localized settings object exposes a nonce string in the page.
- In the fetch call set the
X-WP-Nonceheader to the localized nonce and send credentials so the auth cookie accompanies the request.- Verify: The logged-in request authenticates and no longer returns 401 rest_cookie_invalid_nonce.
Workflow: Create Endpoint
Progress:
- [ ] Hook `rest_api_init` and call `register_rest_route('acme/v1', '/books', [...])`
with the methods, callback, permission_callback, and args
- [ ] Set a `permission_callback` that checks the capability and define each argument
with type, required, sanitize_callback, and validate_callback
- [ ] In the callback read params with `$request->get_param()`, do the work, and
return `new WP_REST_Response($data, 200)` or a WP_Error on failure- Hook
rest_api_initand callregister_rest_route('acme/v1', '/books', [...])with the methods, callback, permission_callback, and args.- Verify:
GET /wp-json/acme/v1/booksresolves to the callback.
- Verify:
- Set a
permission_callbackthat checks the capability and define each argument with type, required, sanitize_callback, and validate_callback.- Verify: Anonymous callers get 401/403 and invalid arguments return a WP_Error.
- In the callback read params with
$request->get_param(), do the work, and returnnew WP_REST_Response($data, 200)or a WP_Error on failure.- Verify: A valid request returns JSON with status 200 and no stray output.
Best practices
- Declare each parameter in the
argsarray withrequired,type,sanitize_callback, andvalidate_callbackso input is cleaned and rejected before the handler runs - For anything beyond a single route, extend
WP_REST_Controller(or a class like it) with register_routes, permission methods, get_item_schema, and handlers, instead of many loose closures - For logged-in, same-origin (theme or admin) requests send the
wp_restnonce in theX-WP-Nonceheader alongside the auth cookie; use application passwords for external, machine-to-machine clients - Return a
WP_REST_Response(or a value REST will wrap) on success and aWP_Errorwith an HTTP status in its data on failure, rather than echoing or using wp_send_json
Anti-patterns
| Anti-pattern | Why it fails | Do instead |
| Reading input from superglobals instead of the request | Superglobals bypass the args schema, so values arrive unsanitized and unvalidated, and the route's self-documented schema no longer matches its behavior. | Declare parameters in the args array and read them with $request->get_param('x') so sanitize and validate callbacks apply. |
| permission_callback set to __return_true on a write route | __return_true grants access to every anonymous caller, turning the route into an unauthenticated write or data-leak endpoint. | Replace it with a real check such as current_user_can('edit_posts') or an ownership test, reserving __return_true for genuinely public reads. |
| Registering routes under an unversioned or generic namespace | Generic namespaces collide with other plugins and leave no room to evolve the API, forcing breaking changes on existing clients. | Use a vendor-versioned namespace such as acme/v1 and bump to acme/v2 for incompatible changes. |
See references/anti-patterns.md.
Debugging
- Logged-in requests get 401 rest_cookie_invalid_nonce — Cookie authentication only applies when the request carries a valid
wp_restnonce; without the X-WP-Nonce header WordPress treats the caller as anonymous. Fix: Localize the nonce withwp_create_nonce('wp_rest')(or wpApiSettings.nonce) and send it in theX-WP-Noncerequest header. - doing_it_wrong notice for a missing permission_callback — Since WordPress 5.5 the permission_callback argument is required; omitting it is
a security and correctness bug, not a harmless default. Fix: Add an explicit permission_callback — a capability check, an ownership check,
or
__return_trueonly for an intentionally public read. - Echoing output corrupts the JSON response — REST serializes the returned value into the response body; anything echoed is written first and breaks the JSON envelope. Fix: Return a WP_REST_Response or WP_Error instead of echoing, and remove stray debug output before shipping.
Production checklist
- Every accepted parameter is in the args schema with type, sanitize_callback, and validate_callback.
- Same-origin clients send the X-WP-Nonce header; external clients use application passwords over HTTPS.
- Request values are sanitized on input and escaped or bound before reaching SQL, the filesystem, or output.
- Each register_rest_route supplies a permission_callback with a genuine capability or ownership check.
- Routes are registered under a vendor-versioned namespace such as acme/v1.
- Handlers return WP_REST_Response on success and WP_Error with a status on failure, never echo.
Version notes
- Application passwords built in since WordPress 5.6 (PHP >=7.4) — Application passwords ship in core from WordPress 5.6 and require HTTPS by default; before that, external clients needed a third-party auth plugin such as Basic Auth or JWT.
- permission_callback required since WordPress 5.5 (PHP >=7.4) — Since WordPress 5.5 a route registered without a permission_callback triggers a doing_it_wrong notice; earlier versions silently allowed it, which produced accidentally public endpoints.
Security
- permission_callback is mandatory on every route — Treat
permission_callbackas required security, not optional plumbing: every register_rest_route MUST supply one that enforces authentication and the right capability, and it must never be left off or set to__return_trueon a route that reads private data or mutates state. - Treat every REST argument as untrusted — Sanitize and validate every request argument through the args schema and never pass raw parameters into SQL, the filesystem, shell, or output; escape on output as well.
Testing
- Activation and decision behavior are verified in
eval/scenarios/wordpress/rest-api.yaml.
Additional resources
- references/anti-patterns.md
- references/core.md
- references/decision-engine.md
- references/examples.md
- references/pitfalls.md
- references/recipes.md
- references/version-differences.md
Knowledge graph
Prerequisites: wordpress/plugin-structure
Related: security/jwt-php
<!-- graph-fragment: graph/fragments/wordpress/rest-api.yaml -->Skill contract
Produces: wp.rest-route
Requires: wp.plugin-scaffolded
Risk: medium · Confidence: 0.9
<!-- contract-fragment: contracts/fragments/wordpress/rest-api.yaml -->Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

