A lightweight WordPress plugin that lets you assign and create tags and categories for posts via the REST API — with automatic term creation, append mode, and granular error reporting.
Features
- Set categories and tags on any public post type via a single REST call
- Resolve categories by ID, slug, or slug + custom name
- Auto-create missing tags (and categories, if the user has
manage_categories) - Append mode — add terms without overwriting existing ones
- Structured response with
existing,created, anderrorsarrays - Per-request term cap (50) to prevent bulk abuse
- Full capability enforcement — no privilege escalation
Installation
- Download
taxonomy-sync-engine.php. - Upload it to your site's
/wp-content/plugins/taxonomy-sync-engine/directory, or zip it and install via Plugins → Add New → Upload Plugin. - Activate the plugin from the Plugins screen.
- The REST endpoint is immediately available — no configuration required.
Authentication
All requests must be authenticated. The plugin uses WordPress's built-in REST authentication. The recommended approaches are:
Application Passwords (built into WordPress 5.6+)
Authorization: Basic base64(username:application_password)
Cookie authentication (for requests made from within WordPress, e.g. admin-ajax or the block editor). Include a valid nonce:
X-WP-Nonce: <nonce>
Unauthenticated requests will receive a 401 Unauthorized response.
API Reference
Endpoint
POST /wp-json/tse/v1/update-post-terms
Content-Type: application/json
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
post_id | integer | ✅ | — | ID of the post to update. Must be a public, non-trashed post. |
categories | array of objects | ❌ | [] | Categories to assign. Each object may contain id, slug, and/or name. See details below. |
tags | array of strings | ❌ | [] | Tag names to assign. Missing tags are created automatically. |
append | boolean | ❌ | false | When true, terms are added to existing ones. When false, existing terms are replaced. |
Maximum 50 items per categories or tags array per request.
categories item schema
Each entry in the categories array is an object. The plugin resolves it in this priority order:
| Field | Type | Description |
|---|---|---|
id | integer | Resolve by exact term ID. If provided, slug and name are ignored. |
slug | string | Resolve by slug. If not found, a new category is created (requires manage_categories). |
name | string | Human-readable name used only when creating a new category. Ignored if the category already exists. |
If only slug is given and the category doesn't exist, the name defaults to a slug-derived value (e.g. "machine-learning" → "Machine Learning").
Response
Success — 200 OK (all terms applied without errors)
Partial success — 207 Multi-Status (some terms applied, some failed — check errors)
{ "post_id": 42, "post_title": "Hello World", "post_status": "publish", "categories": { "set": [3, 7], // Term IDs now assigned to the post "existing": [ // Terms that already existed before the request { "id": 3, "name": "News", "slug": "news" } ], "created": [ // Terms created by this request { "id": 7, "name": "Machine Learning", "slug": "machine-learning" } ] }, "tags": { "set": [12, 15], "existing": [ { "id": 12, "name": "WordPress" } ], "created": [ { "id": 15, "name": "REST API" } ] }, "errors": [] }
Error — 400 / 401 / 403 / 404 (invalid request or insufficient permissions)
{ "code": "rest_forbidden", "message": "You do not have permission to edit this post.", "data": { "status": 403 } }
Error Handling
Non-fatal errors (e.g. one bad category ID among several valid ones) are collected in the errors array and return a 207 status. The rest of the request is still applied.
Fatal errors (missing post_id, bad authentication, insufficient capabilities) return a standard WP REST error object with an appropriate HTTP status code.
Examples
1. Set categories by slug
curl -X POST https://yoursite.com/wp-json/tse/v1/update-post-terms \ -H "Content-Type: application/json" \ -H "Authorization: Basic <base64-credentials>" \ -d '{ "post_id": 42, "categories": [ { "slug": "news" }, { "slug": "technology" } ] }'
2. Set categories by ID
curl -X POST https://yoursite.com/wp-json/tse/v1/update-post-terms \ -H "Content-Type: application/json" \ -H "Authorization: Basic <base64-credentials>" \ -d '{ "post_id": 42, "categories": [ { "id": 3 }, { "id": 7 } ] }'
3. Set categories with a custom name
Useful when you want to create a category and control its display name precisely:
curl -X POST https://yoursite.com/wp-json/tse/v1/update-post-terms \ -H "Content-Type: application/json" \ -H "Authorization: Basic <base64-credentials>" \ -d '{ "post_id": 42, "categories": [ { "slug": "ai-ml", "name": "AI & Machine Learning" } ] }'
4. Set tags
Missing tags are created automatically. No special permissions are required for tag creation.
curl -X POST https://yoursite.com/wp-json/tse/v1/update-post-terms \ -H "Content-Type: application/json" \ -H "Authorization: Basic <base64-credentials>" \ -d '{ "post_id": 42, "tags": ["WordPress", "REST API", "Headless CMS"] }'
5. Append instead of replace
By default the plugin replaces all existing terms. Pass "append": true to add to them instead:
curl -X POST https://yoursite.com/wp-json/tse/v1/update-post-terms \ -H "Content-Type: application/json" \ -H "Authorization: Basic <base64-credentials>" \ -d '{ "post_id": 42, "tags": ["New Tag"], "append": true }'
6. Categories and tags together
curl -X POST https://yoursite.com/wp-json/tse/v1/update-post-terms \ -H "Content-Type: application/json" \ -H "Authorization: Basic <base64-credentials>" \ -d '{ "post_id": 42, "categories": [ { "slug": "news" }, { "slug": "open-source", "name": "Open Source" } ], "tags": ["WordPress", "Plugin", "REST API"], "append": false }'
Permissions
| Action | Required Capability |
|---|---|
| Call the endpoint | edit_post on the specific post |
| Assign existing categories | edit_post on the specific post |
| Create new categories | manage_categories |
| Assign / create tags | edit_post on the specific post |
If a user has edit_post but not manage_categories, the request will still succeed for existing categories and tags — the missing-category entries will appear in the errors array and be skipped.
Limits
| Limit | Value |
|---|---|
| Max categories per request | 50 |
| Max tags per request | 50 |
| Supported post types | All public post types (excludes attachment, nav_menu_item, etc.) |
Author: Pawan
Contributors: paw1xd
License: GPLv2
Current Version: 1.0
