LESSON 4 โฑ๏ธ 13 min read

Write Operations: Create, Update & Delete

Creating Content

Use POST requests to create new content.

Creating a Post

async function createPost(data) {
    const response = await fetch('/wp-json/wp/v2/posts', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            title: data.title,
            content: data.content,
            excerpt: data.excerpt,
            status: 'publish',  // or 'draft'
            categories: [5, 6],
            tags: [10, 11],
            featured_media: 123,  // Media ID
            meta: {
                custom_field: 'value'
            }
        })
    });
    
    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.message);
    }
    
    return response.json();  // Returns created post with ID
}

Creating a Page

async function createPage(title, content, parentId = 0) {
    const response = await fetch('/wp-json/wp/v2/pages', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            title,
            content,
            status: 'publish',
            parent: parentId,  // For child pages
            menu_order: 0,
            template: ''  // Page template slug
        })
    });
    
    return response.json();
}

Uploading Media

async function uploadMedia(file) {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('title', file.name);
    formData.append('alt_text', 'Image description');
    
    const response = await fetch('/wp-json/wp/v2/media', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`
            // Don't set Content-Type - browser sets it with boundary
        },
        body: formData
    });
    
    return response.json();  // Returns media object with ID
}

// Usage with featured image
const media = await uploadMedia(imageFile);
const post = await createPost({
    title: 'My Post',
    content: 'Post content',
    featured_media: media.id
});

Updating Content

Use PUT (full update) or PATCH (partial update).

Updating a Post

async function updatePost(postId, updates) {
    const response = await fetch(`/wp-json/wp/v2/posts/${postId}`, {
        method: 'PUT',  // or 'PATCH' for partial
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(updates)
    });
    
    return response.json();
}

// Update only specific fields
await updatePost(123, {
    title: 'Updated Title'
});

// Update multiple fields
await updatePost(123, {
    title: 'New Title',
    content: 'Updated content',
    status: 'draft',
    categories: [7, 8]
});

Updating Meta Fields

// Meta fields must be registered with 'show_in_rest' => true
await updatePost(123, {
    meta: {
        _custom_field: 'new value',
        _another_field: 42
    }
});
Important: Custom meta fields must be registered with register_post_meta() and show_in_rest => true to be accessible via the API.
// In your plugin or theme
register_post_meta('post', '_custom_field', [
    'show_in_rest' => true,
    'single'       => true,
    'type'         => 'string',
    'auth_callback' => function() {
        return current_user_can('edit_posts');
    }
]);

Deleting Content

Use DELETE requests. By default, posts go to trash.

Delete (Trash) a Post

async function trashPost(postId) {
    const response = await fetch(`/wp-json/wp/v2/posts/${postId}`, {
        method: 'DELETE',
        headers: {
            'Authorization': `Bearer ${token}`
        }
    });
    
    return response.json();  // Returns trashed post
}

Permanently Delete

async function deletePost(postId, force = false) {
    const url = force 
        ? `/wp-json/wp/v2/posts/${postId}?force=true`
        : `/wp-json/wp/v2/posts/${postId}`;
    
    const response = await fetch(url, {
        method: 'DELETE',
        headers: {
            'Authorization': `Bearer ${token}`
        }
    });
    
    return response.json();
}

// Trash (can be restored)
await deletePost(123);

// Permanently delete
await deletePost(123, true);

Working with Terms

Create a Category

async function createCategory(name, parent = 0) {
    const response = await fetch('/wp-json/wp/v2/categories', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            name,
            parent,
            description: '',
            slug: ''  // Auto-generated if empty
        })
    });
    
    return response.json();
}

Assign Terms to Post

// Categories and tags are arrays of term IDs
await updatePost(123, {
    categories: [1, 2, 3],
    tags: [10, 11, 12]
});

Bulk Operations

The API doesn't have built-in bulk operations, but you can use Promise.all:

async function bulkUpdateStatus(postIds, status) {
    const updates = postIds.map(id => 
        updatePost(id, { status })
    );
    
    return Promise.all(updates);
}

// Update multiple posts to draft
await bulkUpdateStatus([1, 2, 3, 4, 5], 'draft');
Performance Tip: For many updates, consider creating a custom batch endpoint in your plugin to reduce HTTP overhead.

Error Handling

async function safeApiCall(endpoint, options) {
    try {
        const response = await fetch(endpoint, options);
        const data = await response.json();
        
        if (!response.ok) {
            // WordPress returns error details
            return {
                success: false,
                code: data.code,
                message: data.message,
                data: data.data
            };
        }
        
        return { success: true, data };
        
    } catch (error) {
        return {
            success: false,
            code: 'network_error',
            message: error.message
        };
    }
}

// Usage
const result = await safeApiCall('/wp-json/wp/v2/posts', {
    method: 'POST',
    headers: { /* ... */ },
    body: JSON.stringify({ title: '' })  // Empty title = error
});

if (!result.success) {
    console.error(`${result.code}: ${result.message}`);
    // "rest_invalid_param: title is required"
}

Next Steps

In the next lesson, we'll build custom REST endpoints for your own data and functionality.

๐ŸŽฏ Lesson Complete! You can now create, update, and delete WordPress content through the REST API.