Custom REST API Builder
WordPress plugin that allows you to create fully customizable REST API endpoints
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/dadashzadeh/create-custom-api-in-wordpress/archive/refs/heads/main.zipA powerful WordPress plugin that allows you to create fully customizable REST API endpoints for your posts, custom post types, and taxonomies with complete CRUD operations and API key authentication.
Description
Custom REST API Builder (CRAB) gives you complete control over your WordPress REST API. Unlike the default WordPress REST API, this plugin lets you choose exactly which fields to expose, in what order, and with granular permission controls.
Key Features
- 🔧 Fully Customizable Fields - Select and reorder exactly which fields appear in your API responses
- 🔐 API Key Authentication - Secure your endpoints with API keys and granular permissions
- 📝 Full CRUD Operations - Create, Read, Update, and Delete posts and terms via API
- 🎯 Custom Post Type Support - Works with all public post types and taxonomies
- 🔍 Advanced Filtering - Filter by date, meta fields, taxonomies, and search
- ⚡ ACF Compatible - Full support for Advanced Custom Fields
- 🧪 Built-in API Tester - Test your endpoints directly from the admin panel
- 📱 Mobile App Ready - Perfect for headless WordPress and mobile applications
Installation
From WordPress Admin
- Download the plugin ZIP file
- Go to Plugins → Add New → Upload Plugin
- Upload the ZIP file and click Install Now
- Activate the plugin
Manual Installation
- Download and extract the plugin
- Upload the
custom-rest-api-builderfolder to/wp-content/plugins/ - Activate the plugin through the Plugins menu in WordPress
Requirements
- WordPress 4.5 or higher
- PHP 5.6 or higher
Quick Start
1. Configure Your Fields
- Navigate to REST API Builder in your WordPress admin menu
- Click on a post type tab (e.g., "Post", "Page")
- Check the fields you want to include in API responses
- Drag to reorder fields
- Click Save All Settings
2. Generate an API Key
- Go to the API Keys tab
- Enter a name for your key (e.g., "Mobile App")
- Select permissions (Read, Write, Delete)
- Click Generate API Key
- Copy and save the key immediately - it won't be shown again!
3. Make Your First Request
curl -H "X-API-Key: crab_YOUR_API_KEY" \
https://yoursite.com/wp-json/custom/v1/type/post
API Endpoints
Base URL
https://yoursite.com/wp-json/custom/v1
Posts & Custom Post Types
| Method | Endpoint | Description |
|---|---|---|
GET |
/type/{post_type} |
List all items |
POST |
/type/{post_type} |
Create new item |
GET |
/item/{id} |
Get single item |
PUT |
/item/{id} |
Update item |
DELETE |
/item/{id} |
Delete item |
GET |
/search |
Search content |
Taxonomies
| Method | Endpoint | Description |
|---|---|---|
GET |
/taxonomy/{taxonomy} |
List all terms |
POST |
/taxonomy/{taxonomy} |
Create new term |
GET |
/term/{id} |
Get single term |
PUT |
/term/{id} |
Update term |
DELETE |
/term/{id} |
Delete term |
Authentication
API Key Methods
Method 1: HTTP Header (Recommended)
curl -H "X-API-Key: crab_YOUR_API_KEY" \
https://yoursite.com/wp-json/custom/v1/type/post
Method 2: Query Parameter
https://yoursite.com/wp-json/custom/v1/type/post?api_key=crab_YOUR_API_KEY
Permission Levels
| Permission | Operations |
|---|---|
| Read | GET requests |
| Write | POST, PUT, PATCH requests |
| Delete | DELETE requests |
Authentication Settings
By default, GET requests are public. Enable "Require API Key for Read" in the API Keys tab to require authentication for all requests.
Query Parameters
Collection Endpoints (/type/{post_type})
| Parameter | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 10 | Items per page (max 100) |
page |
integer | 1 | Page number |
search |
string | - | Search keyword |
orderby |
string | date | Order by field |
order |
string | DESC | Sort order (ASC/DESC) |
date_after |
string | - | Filter posts after date (YYYY-MM-DD) |
date_before |
string | - | Filter posts before date (YYYY-MM-DD) |
meta_key |
string | - | Meta key to filter by |
meta_value |
string | - | Meta value to match |
meta_compare |
string | = | Comparison operator |
tax_query |
JSON | - | Taxonomy filter |
Taxonomy Endpoints (/taxonomy/{taxonomy})
| Parameter | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 100 | Terms per page |
page |
integer | 1 | Page number |
hide_empty |
boolean | false | Hide terms with no posts |
parent |
integer | - | Filter by parent term ID |
search |
string | - | Search terms |
Search Endpoint (/search)
| Parameter | Type | Required | Description |
|---|---|---|---|
q |
string | Yes | Search query |
post_type |
string | No | Limit to specific post type |
per_page |
integer | No | Results per page |
page |
integer | No | Page number |
Request & Response Examples
Get All Posts
Request:
GET /wp-json/custom/v1/type/post?per_page=10&page=1
Response:
{
"success": true,
"post_type": "post",
"total": 42,
"total_pages": 5,
"current_page": 1,
"per_page": 10,
"count": 10,
"items": [
{
"id": 123,
"title": "Hello World",
"slug": "hello-world",
"date": "2026-07-20 10:30:00",
"excerpt": "Welcome to WordPress...",
"content": "<p>Welcome to WordPress...</p>"
}
]
}
Get Single Item
Request:
GET /wp-json/custom/v1/item/123
Response:
{
"success": true,
"item": {
"id": 123,
"title": "Hello World",
"slug": "hello-world",
"date": "2026-07-20 10:30:00",
"modified": "2026-07-20 11:00:00",
"content": "<p>Welcome to WordPress...</p>",
"featured_image": "https://yoursite.com/wp-content/uploads/image.jpg",
"meta:custom_field": "Custom value"
}
}
Create New Post
Request:
POST /wp-json/custom/v1/type/post
Content-Type: application/json
X-API-Key: crab_YOUR_API_KEY
{
"fields": {
"post_title": "My New Post",
"post_content": "<p>This is the content.</p>",
"post_status": "publish",
"post_excerpt": "A short summary",
"meta:custom_field": "Custom value",
"taxonomies": {
"category": [1, 5],
"post_tag": [10, 15]
},
"featured_image": 456
}
}
Response:
{
"success": true,
"message": "Item created successfully.",
"id": 789,
"item": {
"id": 789,
"title": "My New Post",
"slug": "my-new-post",
"date": "2026-07-20 11:43:00"
}
}
Update Existing Post
Request:
PUT /wp-json/custom/v1/item/123
Content-Type: application/json
X-API-Key: crab_YOUR_API_KEY
{
"fields": {
"post_title": "Updated Title",
"meta:price": "99.99"
}
}
Response:
{
"success": true,
"message": "Item updated successfully.",
"id": 123,
"updated_fields": ["post_title", "meta:price"],
"item": {
"id": 123,
"title": "Updated Title",
"modified": "2026-07-20 11:43:00",
"meta:price": "99.99"
}
}
Note: Only the fields you send will be updated. All other fields remain unchanged.
Delete Post
Request:
DELETE /wp-json/custom/v1/item/123?force=true
X-API-Key: crab_YOUR_API_KEY
Response:
{
"success": true,
"message": "Item permanently deleted.",
"id": 123
}
| Parameter | Description |
|---|---|
force=false |
Move to trash (default) |
force=true |
Permanently delete |
Advanced Filtering
Filter by Date
GET /wp-json/custom/v1/type/post?date_after=2026-01-01&date_before=2026-12-31
Filter by Meta Field
GET /wp-json/custom/v1/type/product?meta_key=_price&meta_value=100&meta_compare=>=
Available Comparisons: =, !=, >, >=, <, <=, LIKE, NOT LIKE, IN, NOT IN
Filter by Taxonomy
GET /wp-json/custom/v1/type/post?tax_query=[{"taxonomy":"category","terms":[5,10],"operator":"IN"}]
Tax Query Structure:
[
{
"taxonomy": "category",
"field": "term_id",
"terms": [5, 10],
"operator": "IN"
}
]
Available Fields
Post Fields
| Field Key | Description |
|---|---|
id |
Post ID |
title |
Post title |
slug |
URL slug |
date |
Publish date |
date_gmt |
Publish date (GMT) |
modified |
Last modified date |
modified_gmt |
Last modified date (GMT) |
status |
Post status |
type |
Post type |
excerpt |
Post excerpt |
content |
Rendered content |
content_raw |
Raw content (no filters) |
author_id |
Author user ID |
author_name |
Author display name |
author_email |
Author email |
author_avatar |
Author avatar URL |
featured_image |
Featured image URL |
featured_image_id |
Featured image attachment ID |
featured_image_alt |
Featured image alt text |
featured_image_sizes |
All image sizes with URLs |
permalink |
Full post URL |
parent |
Parent post ID |
menu_order |
Menu order |
comment_count |
Number of comments |
comment_status |
Comment status |
ping_status |
Ping status |
guid |
Global unique identifier |
tax:{taxonomy} |
Taxonomy terms |
meta:{key} |
Custom meta field |
acf:{field} |
ACF field (if ACF is active) |
Taxonomy Term Fields
| Field Key | Description |
|---|---|
term_id |
Term ID |
name |
Term name |
slug |
Term slug |
description |
Term description |
count |
Post count |
parent |
Parent term ID |
taxonomy |
Taxonomy name |
link |
Term archive URL |
meta:{key} |
Term meta field |
acf:{field} |
ACF field for term |
Writable Fields
| Field Key | Description |
|---|---|
post_title |
Post title |
post_content |
Post content |
post_excerpt |
Post excerpt |
post_name |
Post slug |
post_status |
Status (publish, draft, pending, private) |
post_parent |
Parent post ID |
menu_order |
Menu order |
post_date |
Publish date |
meta:{key} |
Any meta field |
taxonomies |
Object with taxonomy => term IDs |
featured_image |
Attachment ID |
Code Examples
JavaScript (Fetch)
// Get posts
const getPosts = async () => {
const response = await fetch('https://yoursite.com/wp-json/custom/v1/type/post', {
headers: {
'X-API-Key': 'crab_YOUR_API_KEY'
}
});
return response.json();
};
// Create post
const createPost = async (title, content) => {
const response = await fetch('https://yoursite.com/wp-json/custom/v1/type/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'crab_YOUR_API_KEY'
},
body: JSON.stringify({
fields: {
post_title: title,
post_content: content,
post_status: 'publish'
}
})
});
return response.json();
};
// Update post
const updatePost = async (id, fields) => {
const response = await fetch(`https://yoursite.com/wp-json/custom/v1/item/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'crab_YOUR_API_KEY'
},
body: JSON.stringify({ fields })
});
return response.json();
};
// Delete post
const deletePost = async (id, force = false) => {
const response = await fetch(`https://yoursite.com/wp-json/custom/v1/item/${id}?force=${force}`, {
method: 'DELETE',
headers: {
'X-API-Key': 'crab_YOUR_API_KEY'
}
});
return response.json();
};
PHP (WordPress)
<?php
$api_key = 'crab_YOUR_API_KEY';
$base_url = 'https://yoursite.com/wp-json/custom/v1';
// Get posts
$response = wp_remote_get($base_url . '/type/post', array(
'headers' => array(
'X-API-Key' => $api_key
)
));
$posts = json_decode(wp_remote_retrieve_body($response), true);
// Create post
$response = wp_remote_post($base_url . '/type/post', array(
'headers' => array(
'Content-Type' => 'application/json',
'X-API-Key' => $api_key
),
'body' => json_encode(array(
'fields' => array(
'post_title' => 'New Post',
'post_content' => 'Content here',
'post_status' => 'publish'
)
))
));
// Update post
$response = wp_remote_request($base_url . '/item/123', array(
'method' => 'PUT',
'headers' => array(
'Content-Type' => 'application/json',
'X-API-Key' => $api_key
),
'body' => json_encode(array(
'fields' => array(
'post_title' => 'Updated Title'
)
))
));
// Delete post
$response = wp_remote_request($base_url . '/item/123?force=true', array(
'method' => 'DELETE',
'headers' => array(
'X-API-Key' => $api_key
)
));
Python (Requests)
import requests
import json
API_KEY = 'crab_YOUR_API_KEY'
BASE_URL = 'https://yoursite.com/wp-json/custom/v1'
headers = {
'X-API-Key': API_KEY,
'Content-Type': 'application/json'
}
# Get posts
response = requests.get(f'{BASE_URL}/type/post', headers=headers)
posts = response.json()
# Create post
data = {
'fields': {
'post_title': 'New Post from Python',
'post_content': 'Content here',
'post_status': 'publish'
}
}
response = requests.post(f'{BASE_URL}/type/post', headers=headers, json=data)
result = response.json()
# Update post
data = {
'fields': {
'post_title': 'Updated Title'
}
}
response = requests.put(f'{BASE_URL}/item/123', headers=headers, json=data)
result = response.json()
# Delete post
response = requests.delete(f'{BASE_URL}/item/123?force=true', headers=headers)
result = response.json()
React Native / Axios
import axios from 'axios';
const api = axios.create({
baseURL: 'https://yoursite.com/wp-json/custom/v1',
headers: {
'X-API-Key': 'crab_YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
// Get posts
export const getPosts = (page = 1, perPage = 10) =>
api.get(`/type/post?page=${page}&per_page=${perPage}`);
// Get single post
export const getPost = (id) =>
api.get(`/item/${id}`);
// Create post
export const createPost = (fields) =>
api.post('/type/post', { fields });
// Update post
export const updatePost = (id, fields) =>
api.put(`/item/${id}`, { fields });
// Delete post
export const deletePost = (id, force = false) =>
api.delete(`/item/${id}?force=${force}`);
// Search
export const search = (query, postType = 'any') =>
api.get(`/search?q=${encodeURIComponent(query)}&post_type=${postType}`);
Error Handling
Error Response Format
{
"code": "error_code",
"message": "Human readable error message",
"data": {
"status": 400
}
}
Common Error Codes
| HTTP Code | Error Code | Description |
|---|---|---|
| 400 | invalid_fields |
Invalid or missing fields in request body |
| 400 | missing_title |
Post title is required for creation |
| 401 | missing_api_key |
API key not provided |
| 401 | invalid_api_key |
API key is invalid |
| 403 | forbidden |
No permission for this operation |
| 404 | not_found |
Item or resource not found |
| 404 | invalid_post_type |
Post type does not exist |
| 404 | invalid_taxonomy |
Taxonomy does not exist |
| 500 | create_failed |
Failed to create item |
| 500 | update_failed |
Failed to update item |
| 500 | delete_failed |
Failed to delete item |
Hooks & Filters
Available Filters
// Modify fields before saving
add_filter('crab_before_save_settings', function($settings) {
// Modify $settings
return $settings;
});
// Modify API response
add_filter('crab_api_response', function($response, $post_type) {
// Modify $response
return $response;
}, 10, 2);
// Customize available fields
add_filter('crab_post_type_fields', function($fields, $post_type) {
// Add custom fields
$fields['my_custom'] = 'My Custom Field';
return $fields;
}, 10, 2);
FAQ
Q: Why don't I see all my custom fields?
A: Click the "Clear Meta Fields Cache" button in the Endpoints tab. The plugin caches meta keys for performance. After adding new custom fields, clear the cache to see them.
Q: Does updating a post change the modified date?
A: Yes, WordPress automatically updates post_modified and post_modified_gmt whenever a post is updated via the API.
Q: Can I use this with WooCommerce products?
A: Yes! WooCommerce products are a custom post type (product). Select the fields you need in the Products tab.
Q: Is it compatible with ACF (Advanced Custom Fields)?
A: Yes, the plugin automatically detects ACF fields and shows them with the acf: prefix.
Q: What happens when I deactivate the plugin?
A: All plugin data (settings, API keys) is deleted from the database when deactivated.
Q: How do I update only one field without affecting others?
A: Send only the field you want to update in the fields object. Other fields remain unchanged:
{
"fields": {
"meta:price": "99.99"
}
}
Q: Can I create posts with a future publish date?
A: Yes, use post_status: "future" and set post_date:
{
"fields": {
"post_title": "Scheduled Post",
"post_status": "future",
"post_date": "2026-12-25 10:00:00"
}
}
Changelog
1.0.0 (2026-07-20)
- Initial release
- Full CRUD support for posts and taxonomies
- API key authentication with permissions
- Customizable field selection and ordering
- Advanced filtering (date, meta, taxonomy)
- Built-in API tester
- ACF integration
- Live preview in admin
License
This plugin is licensed under the GPL-2.0+ license. See LICENSE for details.
Credits
Author: dadashzadeh
Contributors: Community contributions welcome!
Made with ❤️ for the WordPress community