Developer wearing a WordPress shirt working at a desktop computer, illustrating WordPress REST API development. Photo by Fikret tozak on Unsplash.

Set Featured Image via WordPress REST API: Complete Guide

You generated a post, wrote the content, and shipped it to WordPress through the REST API. The post goes live. The featured image slot sits empty. Now your post looks unfinished in search results, social shares, and your blog archive, all because the image upload step is documented nowhere authoritative and the top Google results point to a 2016 guide for a deprecated client library.

Setting a featured image via the WordPress REST API requires three sequential API calls in a specific order: upload the image as raw binary to the media endpoint, create or update the post, then assign the media ID to the post’s featured_media field. Most developers get tripped up on step one because the media endpoint expects a raw binary body with a Content-Disposition header, not multipart form data. Here is the exact sequence, the common errors, and working code in curl, Python, and n8n.

The Three Calls, In Order

The WordPress REST API does not let you attach an image file and create a post in a single request. The featured image workflow is strictly sequential: upload first, capture the media ID from the response, then use that ID when creating or updating the post. Trying to shortcut this by passing a file URL or base64 string in the post body does not work.

StepEndpointMethodKey InputWhat You Capture
1. Upload image/wp/v2/mediaPOSTRaw binary body + Content-Disposition headerid from response JSON
2. Create or update post/wp/v2/posts or /wp/v2/posts/{id}POST or PUTPost content, title, categories, tagsPost id
3. Set featured_media/wp/v2/posts/{id}POST{"featured_media": media_id}Updated post object with featured_media set

Steps 2 and 3 can be combined into a single call: if you already have the post ID, you can send a POST to /wp/v2/posts/{id} with featured_media in the body. But if you are creating a new post, you must create it first (step 2), get the post ID, then update it with the featured media ID (step 3). Alternatively, you can upload the image first, then pass featured_media in the initial post creation body, combining steps 2 and 3 into one call since you already have the media ID.

The most reliable pattern: upload image, then create the post with featured_media included in the same request body. That is two calls, not three. You only need three calls if the post already exists and you are updating it.

Step 1: Upload to /wp/v2/media

WordPress PHP source code displayed on a dark monitor screen, showing the kind of code that powers the REST API media endpoint.

The media endpoint expects the image file as a raw binary body, not as multipart form data. This is the single most common mistake. If you send multipart/form-data, WordPress will accept the request but the file may not be processed correctly, or you will get a rest_upload_no_content_disposition error if the Content-Disposition header is missing or malformed.

Three headers are required for this call:

  • Content-Disposition: attachment; filename="my-image.jpg" tells WordPress the original filename and extension. The filename must include a valid extension (jpg, png, gif, webp, etc.) or the upload will fail.
  • Content-Type: image/jpeg should match the actual file type. If you omit this, WordPress will try to infer it from the Content-Disposition filename, but explicit is safer.
  • Authorization: Basic ... or your application password credentials. Media upload requires an authenticated user with the upload_files capability.

curl Example: Upload Image

Here is the minimal curl command to upload an image and capture the media ID:

curl -X POST "https://yoursite.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=hero-image.jpg"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/hero-image.jpg

The response is a JSON object. The field you need is id, which is the numeric media item ID. Save it for steps 2 and 3.

Critical: Use –data-binary, Not -d or –data

The -d flag in curl sends data as application/x-www-form-urlencoded, which strips binary data. You must use --data-binary @filename to send the raw file bytes unchanged. Using -d @filename will corrupt the upload silently, and the image will either fail to process or arrive damaged.

Step 2: Create or Update the Post

Once you have the media ID from step 1, creating a post with a featured image is a single POST to /wp/v2/posts with featured_media included in the JSON body alongside your title, content, and other fields.

curl Example: Create Post with Featured Image

curl -X POST "https://yoursite.com/wp-json/wp/v2/posts"
-u "user:app-password"
-H "Content-Type: application/json"
-d '{
"title": "How to Choose a Knee Surgeon in Atlanta",
"content": "<p>Your post content here.</p>",
"status": "draft",
"categories": [5],
"featured_media": 142
}'

If the post already exists and you just need to attach the image, POST to /wp/v2/posts/{post_id} with only the featured_media field:

curl -X POST "https://yoursite.com/wp-json/wp/v2/posts/88"
-u "user:app-password"
-H "Content-Type: application/json"
-d '{"featured_media": 142}'

Step 3: Set featured_media

If you included featured_media in the post creation body (step 2), this step is already done. The separate step matters only when you are updating an existing post or when your workflow uploads the image after post creation.

Send a POST request to /wp/v2/posts/{post_id} with a JSON body containing {"featured_media": media_id}. The response returns the full post object. Verify that featured_media in the response matches the ID you sent. If it returns 0, the media ID was invalid or the media item does not exist.

Setting Alt Text (Do Not Skip This)

The WordPress REST API lets you set alt text on the media item via the alt_text field. This is a separate call to /wp/v2/media/{media_id} after the upload. Alt text is an SEO requirement, not a nice-to-have: Google uses it to understand image content, screen readers depend on it for accessibility, and it appears in image search results.

curl Example: Set Alt Text

curl -X POST "https://yoursite.com/wp-json/wp/v2/media/142"
-u "user:app-password"
-H "Content-Type: application/json"
-d '{"alt_text": "Orthopedic surgeon examining knee X-ray in Atlanta clinic"}'

You can also include alt_text in the initial upload request body, but only if you send it as a JSON field alongside the binary body. Since the upload uses a raw binary body (not JSON), the cleaner approach is a follow-up call to update the media item. Some developers send alt text as a query parameter on the upload (?alt_text=...), but the POST update to /wp/v2/media/{id} is the documented method and is more reliable across WordPress versions.

Good alt text describes the image specifically and includes relevant keywords naturally. “Knee surgeon examining patient X-ray” is useful. “Image” or “photo” is not. For a deeper look at on-page optimization including image SEO, see our Yoast SEO plugin review which covers how SEO plugins handle image alt text validation.

Common Errors and Fixes

Red "Failed to load resource" error messages on a dark developer console screen, representing common REST API upload errors.
ErrorCauseFix
rest_upload_no_content_dispositionMissing or malformed Content-Disposition headerEnsure header is attachment; filename="file.jpg" with a valid extension
rest_invalid_param on media uploadContent-Type header does not match the actual fileSet Content-Type to match the file (image/jpeg, image/png, etc.)
rest_upload_invalid_dispositionFilename in Content-Disposition has no extension or invalid charactersUse a simple filename with a standard extension: image.jpg, not image or im age.jpg
rest_forbidden or 401 on media uploadUser lacks upload_files capability or credentials are wrongUse an Author or higher role with application passwords; verify credentials
rest_upload_file_too_largeFile exceeds server upload limit (default 2MB on many hosts)Check upload_max_filesize and post_max_size in php.ini; compress the image
rest_upload_unknown_errorFile type not permitted by WordPress or server-level restrictionsCheck allowed MIME types; WordPress blocks some file types by default
featured_media returns 0 after POSTMedia ID does not exist or was deletedVerify the media ID from the upload response; re-upload if necessary
Image appears corrupted or will not processUsed -d instead of --data-binary in curl, or sent multipart form dataUse raw binary body with --data-binary @file; do not wrap in form-data

Multipart vs Raw Binary: Why It Matters

Many upload examples online show multipart form data with a file field. The WordPress REST API media endpoint does accept multipart uploads, but the raw binary approach is simpler and more reliable because it avoids boundary parsing issues and works consistently across hosting environments. With raw binary, the entire request body is the file content, and the Content-Disposition header provides the filename metadata.

File Type Restrictions

WordPress allows the following image types by default: jpg, jpeg, png, gif, webp, ico, and svg (SVG support depends on version and configuration). If you try to upload a file type not in the allowed list, you will get rest_upload_unknown_error or a Sorry, you are not allowed to upload this file type message. The allowed types are controlled by wp_get_mime_types() and can be filtered by plugins or server-level configurations. Check with your hosting provider if a standard image type is being rejected.

Upload Size Limits

The upload size limit is governed by two PHP settings: upload_max_filesize and post_max_size. Many shared hosts default to 2MB or 8MB. If your image exceeds this, the server returns a 413 error or a WordPress rest_upload_file_too_large response. Compress images before uploading: a 1920px wide JPEG at 80% quality is typically 150 to 400KB, well within any limit. Tools like ImageOptim, TinyPNG, or Squoosh handle this in seconds.

Permission Errors on Media Upload

Media upload requires the upload_files capability. The Contributor role does not have this capability by default. Authors, Editors, and Administrators can upload files. If you are using application passwords for authentication, ensure the WordPress user account has at least the Author role. If you get a 403 or rest_forbidden error, the user account is the first thing to check.

Related: Invalid parameter(s): categories

If you are setting a featured image, you are probably also assigning categories and tags in the same request. The Invalid parameter(s): categories error is the second most common REST API failure after image upload issues, and developers hit both together because they appear in the same post creation call.

The error occurs when you pass category values that WordPress cannot resolve to valid term IDs. There are three causes:

  • Passing category names instead of IDs. The categories field expects an array of integer term IDs, not slugs or names. "categories": [5, 12] is correct. "categories": ["News", "Updates"] fails.
  • Passing IDs for categories that do not exist or belong to a different taxonomy. If you pass an ID from a custom taxonomy, it will be rejected. The categories field maps to the category taxonomy only.
  • Passing string IDs instead of integers. "categories": ["5"] may fail depending on WordPress version. Always use integers: "categories": [5].

How to Fix It

  • List your categories first with a GET request to /wp/v2/categories to capture the correct term IDs.
  • Pass only integer IDs in the categories array. If you need to create a new category, POST to /wp/v2/categories first, capture the returned ID, then use it in the post creation call.
  • For custom taxonomies, use the /wp/v2/{taxonomy_slug} endpoint to list or create terms, then pass them in the post body under the taxonomy’s REST field name (not under categories).

If you are building an automated content pipeline and need to handle category creation and assignment programmatically, the same REST API patterns apply. Our guide on programmatic SEO on WordPress covers the broader publishing workflow for bulk content, including how to structure taxonomy assignment at scale.

Complete Examples: curl, Python, n8n

Dual-monitor developer workstation showing code on screens with colorful syntax highlighting, keyboard, and ambient RGB lighting.

Full curl Workflow (Upload + Create Post + Set Alt Text)

This three-command sequence uploads the image, creates the post with the featured image attached, and sets alt text on the media item. Replace the URL, credentials, and file path with your own.

# Step 1: Upload the image, capture media ID
RESPONSE=$(curl -s -X POST "https://yoursite.com/wp-json/wp/v2/media"
-u "user:app-password"
-H "Content-Disposition: attachment; filename=hero-image.jpg"
-H "Content-Type: image/jpeg"
--data-binary @/path/to/hero-image.jpg)

MEDIA_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Media ID: $MEDIA_ID"

# Step 2: Create the post with featured_media
POST_RESPONSE=$(curl -s -X POST "https://yoursite.com/wp-json/wp/v2/posts"
-u "user:app-password"
-H "Content-Type: application/json"
-d "{
"title": "How to Choose a Knee Surgeon in Atlanta",
"content": "<p>Post content here.</p>",
"status": "draft",
"categories": [5],
"featured_media": $MEDIA_ID
}")

# Step 3: Set alt text on the media item
curl -s -X POST "https://yoursite.com/wp-json/wp/v2/media/$MEDIA_ID"
-u "user:app-password"
-H "Content-Type: application/json"
-d '{"alt_text": "Orthopedic surgeon examining knee X-ray in Atlanta clinic"}'

Python (requests) Full Workflow

The Python version uses the requests library. The key detail: the file is sent as data=file_content (raw binary), not as files={...} (which triggers multipart form data).

import requests
import json

BASE_URL = "https://yoursite.com/wp-json/wp/v2"
AUTH = ("user", "app-password")

# Step 1: Upload image as raw binary
image_path = "/path/to/hero-image.jpg"
with open(image_path, "rb") as f:
image_data = f.read()

headers = {
"Content-Disposition": "attachment; filename=hero-image.jpg",
"Content-Type": "image/jpeg"
}

resp = requests.post(
f"{BASE_URL}/media",
headers=headers,
auth=AUTH,
data=image_data # raw binary, NOT files={...}
)
media_id = resp.json()["id"]
print(f"Uploaded media ID: {media_id}")

# Step 2: Create post with featured_media
post_data = {
"title": "How to Choose a Knee Surgeon in Atlanta",
"content": "<p>Post content here.</p>",
"status": "draft",
"categories": [5],
"featured_media": media_id
}

resp = requests.post(
f"{BASE_URL}/posts",
json=post_data,
auth=AUTH
)
post_id = resp.json()["id"]
print(f"Created post ID: {post_id}")

# Step 3: Set alt text
alt_resp = requests.post(
f"{BASE_URL}/media/{media_id}",
json={"alt_text": "Orthopedic surgeon examining knee X-ray in Atlanta clinic"},
auth=AUTH
)
print(f"Alt text set: {alt_resp.json()['alt_text']}")

n8n / Make Configuration

In n8n, the workflow uses three HTTP Request nodes connected in sequence. The critical configuration is in the first node: you must set the body type to “Binary” and reference the binary property that holds the file data, not use the default JSON or Form-Data body type.

Node 1: HTTP Request (Upload Image)

  • Method: POST
  • URL: https://yoursite.com/wp-json/wp/v2/media
  • Authentication: Basic Auth (username + application password)
  • Headers: Content-Disposition: attachment; filename=hero-image.jpg and Content-Type: image/jpeg
  • Body Type: Binary Data (this is the critical setting)
  • Binary Property: data (or whichever property holds the file from the previous node, such as a Read Binary File node)
  • Capture output: The JSON response includes id. Store it in a variable or pass it to the next node via {{$json.id}}.

Node 2: HTTP Request (Create Post)

  • Method: POST
  • URL: https://yoursite.com/wp-json/wp/v2/posts
  • Authentication: Basic Auth (same as node 1)
  • Body Type: JSON
  • Body: {"title": "How to Choose a Knee Surgeon in Atlanta", "content": "<p>Post content here.</p>", "status": "draft", "categories": [5], "featured_media": {{$json.id}}}. The {{$json.id}} expression pulls the media ID from node 1’s response.

Node 3: HTTP Request (Set Alt Text)

  • Method: POST
  • URL: https://yoursite.com/wp-json/wp/v2/media/{{$node["Node 1"].json.id}}
  • Body Type: JSON
  • Body: {"alt_text": "Orthopedic surgeon examining knee X-ray in Atlanta clinic"}

Make.com (Integromat) Configuration

In Make.com, the setup is similar. Use a “Create a post” WordPress module for step 2, which natively supports a “Featured Media ID” field. For step 1, use an HTTP module with the binary payload. Make.com’s WordPress app module does not expose the media upload with raw binary control, so the HTTP module is required for the upload step. The native “Upload Media” module in Make’s WordPress app may work, but verify that it sends the Content-Disposition header correctly. If it fails, fall back to the raw HTTP module.

If you are building this workflow because you are tired of manually uploading images and copy-pasting content between tools, that is exactly the problem ClearPost was built to solve. ClearPost handles the image upload, post creation, featured image assignment, alt text, and SEO metadata in a single automated pipeline inside WordPress. You approve every post before it goes live. No manual API calls, no curl scripts to maintain, no missing featured images on published posts.

Frequently Asked Questions

See the FAQ section below for answers to common questions about setting featured images via the WordPress REST API.

The featured image workflow comes down to three things: send raw binary (not form data), include a valid Content-Disposition header with a filename and extension, and use the returned media ID in the featured_media field. Get those three right and the rest follows. For a complete look at how automated content pipelines fit into your SEO strategy, see our analysis of WordPress content scheduling tools and how they compare on automation and publishing control.

Try ClearPost free for 7 days. AI does the heavy lifting, you approve every post before it goes live. No long onboarding, no agency overhead, cancel anytime.

Frequently Asked Questions

Can I set a featured image and create a post in a single REST API call?

No. You must first upload the image to /wp/v2/media (capturing the returned media ID), then include that ID in the featured_media field when creating or updating the post. The upload and post creation are always separate requests. However, you can combine the post creation and featured_media assignment into one call if you already have the media ID.

Why do I get rest_upload_no_content_disposition when uploading media?

This error means the Content-Disposition header is missing or malformed. The header must follow the format: attachment; filename=”your-image.jpg”. The filename must include a valid file extension (jpg, png, gif, webp). Without this header, WordPress cannot determine the filename and rejects the upload.

Should I send multipart form data or raw binary to the WordPress media endpoint?

Raw binary is the recommended approach. Send the file contents directly as the request body with Content-Disposition and Content-Type headers. Multipart form data can work but introduces boundary parsing issues and is less reliable across hosting environments. In curl, use –data-binary @filename, not -d or -F.

How do I set alt text on a featured image via the REST API?

After uploading, send a POST request to /wp/v2/media/{media_id} with a JSON body containing the alt_text field. For example: {“alt_text”: “Description of the image”}. Alt text is required for SEO and accessibility, and should describe the image specifically rather than using generic placeholders like “image” or “photo”.

What causes ‘Invalid parameter(s): categories’ when creating a post via REST API?

This error occurs when you pass category names or slugs instead of integer term IDs, when the IDs do not exist in the category taxonomy, or when string IDs are used instead of integers. List categories first via GET /wp/v2/categories to find valid IDs, then pass them as an array of integers in the categories field.