Uploading media using the WP REST API and JavaScript

Recently, I have been working on this React project that allows users to upload some documents which will be tagged to their profile. My initial thought was to figure out how to use the WP REST API to upload to the Media Library. After that’s done, I would need to save the URL of the uploaded files as a custom user meta.

This ‘front-end upload’ feature seems very common in web apps but I couldn’t really find any tutorial on it so I thought it would be useful to create a simple example and explain how I got it to work.

Here’s how the end product looks like:

Simple Profile Card JS app

It’s just a simple JS application that displays a user profile card, which includes an editable profile image. For styling, I’ve opted to use Bootstrap v4.

There are three essential parts to this:

  1. Upload to Media Library
  2. Save generated URL as custom user meta
  3. Re-fetch data to display updated image

1. Upload to Media Library

The WP REST API has a Media endpoint that allows for creating a media item. The tricky part is to make sure the data we are sending via the POST request is valid.

Preparing the data

const profilePicInput = document.getElementById("profile-pic-input");
const formData = new FormData();
formData.append("file", profilePicInput.files[0]);

First, we need to create a new FormData object which allows us to construct a set of form fields with key/value pairs. Since the WP REST API requires a form field with the key set to file, we use the append()method to add that new property and set its value to be the files attribute of the profilePicInput element, which is just a HTML Input with type set to file.

If you want to add a title/caption to the image, you can create another set of form field using the append() method again. For e.g.:

formData.append("title", "Hello World!");
formData.append("caption", "Have a wonderful day!");

These values will appear in their respective fields when the image is uploaded to the Media Library.

Once the data is prepared, we will use the browser’s Fetch API to send a HTTP request.

fetch(mediaEndpoint, {
  method: "POST",
  headers: {
   //when using FormData(), the 
   //'Content-Type' will automatically be set to 'form/multipart'
   //so there's no need to set it here
   Authorization: "Basic " + window.btoa("admin:password")
  },
  body: formData
})

Endpoint

The endpoint to use is /wp-json/wp/v2/media, and since we are sending data, the method to be used is POST.

Authentication

Authentication is required because we are sending data to the backend. To simplify things, I’ve decided to use the WP Basic Auth plugin. If you are having issues sending authenticated requests, there are two possible solutions to fix that.

Authorization: "Basic " + window.btoa("admin:password")

Note: this method shouldn’t be used in production sites as your credentials will be exposed!

Body

In the request body, we will just send the prepared form data.

Here’s the final code for handling image upload to the Media Library:

/*********************
 * VARIABLES         *
 *********************/
const mediaEndpoint = rootURL + "/wp-json/wp/v2/media";
const form = document.getElementById("profile-form");
const profilePicInput = document.getElementById("profile-pic-input");

/*********************
 * EVENT HANDLERS    *
 *********************/

//Submit image to media library
form.addEventListener("submit", function(e) {
  e.preventDefault();
  const formData = new FormData();
  formData.append("file", profilePicInput.files[0]);
  //display spinner
  toggleSpinner();
  //send image to media library
  fetch(mediaEndpoint, {
    method: "POST",
    headers: {
      //when using FormData(), the 'Content-Type' will automatically be set to 'form/multipart'
      //so there's no need to set it here
      Authorization: "Basic " + window.btoa("admin:password")
    },
    body: formData
  })
    .then(res => res.json())
    .then(data => {
      const input = {
        profile_image: data.source_url
      };
      //send image url to backend
    })
    .catch(err => {
      console.log(err);
    });
});

Note: If you are getting the Specified file failed upload test. error message in your response, it’s likely that the file property is missing (see Preparing the data).

If the upload is successful, we will receive a JSON response with information regarding the uploaded media. The info we need is the image URL, which is stored in the source_url property.

2. Save generated URL as custom user meta

What we want to do now is assign the image to the user account. And to achieve it, we can save the URL of the uploaded media as a user meta.

There are two parts to this – 1) sending the data to an endpoint and 2) creating a plugin that handles the incoming data.

Sending the data

The WP REST API provides a Users endpoint that allows updating of a user profile. Since we are updating our own profile, we can use the /users/me endpoint.

const userEndpoint = rootURL + "/wp-json/wp/v2/users/me";

Right after we receive the image source_url, we want to run another POST request, sending that URL to the backend:

const input = {
  profile_image: data.source_url
};
//send image url to backend
fetch(userEndpoint, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Basic " + window.btoa("admin:password")
  },
  //Since the input variable is an object, 
  //we need to convert it to a string before sending it
  body: JSON.stringify(input)
})

Handle incoming data

Register new REST field

By default, the Users endpoint doesn’t have a “profile image” field for storing the URL so we need to register that as part of the schema first.

Let’s create a plugin to handle this.

<?php
/**
 * Plugin Name: Personal Profile App
 * Description: Customise Users Endpoint for Profile Image upload
 * Author: Edmundcwm
 * Author URI: https://edmundcwm.com
 * Version: 0.1
 */

/**
 * Register field for storing Profile Image URL
 */
function ppa_register_rest_fields() {
	register_rest_field( 'user', 'profile_image', array(
		'get_callback' => 'ppa_get_user_profile_image',
		'update_callback' => 'ppa_update_user_profile_image',
	));
}
add_action( 'rest_api_init', 'ppa_register_rest_fields' );

register_rest_field() allows us to add a new field to the existing User Object Schema. In this case, the new field is named ‘profile_image’:

New profile_image field in User Object Schema

Next, we need to create a callback function for the GET method. The function should retrieve the profile_image user meta, which holds the image URL:

/**
 * Retrieve 'Profile Image' URL
 */
function ppa_get_user_profile_image( $object ) {
	//get user id
	$userid = $object['id'];
	
	if ( ! $userid ) {
		return new WP_Error( 'invalid_user', __( 'Invalid User ID.' ), array( 'status' => 400 ) );
	}

	//return user 'profile_image' meta
	return get_user_meta( $userid, 'profile_image', true );
}

Another callback function we need is for the POST method. This takes care of saving the image URL to the database.

/**
 * Update 'Profile Image' URL
 */
function ppa_update_user_profile_image( $value, $object, $field ) {
	// //get user id
	$userid = $object->ID;
	if ( ! $userid ) {
		return new WP_Error( 'invalid_user', __( 'Invalid User ID.' ), array( 'status' => 400 ) );
	}

	//return user 'profile_image' meta
	return update_user_meta( $userid, $field, esc_url( $value ) );
}

Here’s how the entire plugin looks:

<?php
/**
 * Plugin Name: Personal Profile App
 * Description: Customise Users Endpoint for Profile Image upload
 * Author: Edmundcwm
 * Author URI: https://edmundcwm.com
 * Version: 0.1
 */

/**
 * Register field for storing Profile Image URL
 */
function ppa_register_rest_fields() {
	register_rest_field( 'user', 'profile_image', array(
		'get_callback' => 'ppa_get_user_profile_image',
		'update_callback' => 'ppa_update_user_profile_image',
	));
}
add_action( 'rest_api_init', 'ppa_register_rest_fields' );

/**
 * Retrieve 'Profile Image' URL
 */
function ppa_get_user_profile_image( $object ) {
	//get user id
	$userid = $object['id'];
	
	if ( ! $userid ) {
		return new WP_Error( 'invalid_user', __( 'Invalid User ID.' ), array( 'status' => 400 ) );
	}

	//return user 'profile_image' meta
	return get_user_meta( $userid, 'profile_image', true );
}

/**
 * Update 'Profile Image' URL
 */
function ppa_update_user_profile_image( $value, $object, $field ) {
	// //get user id
	$userid = $object->ID;
	if ( ! $userid ) {
		return new WP_Error( 'invalid_user', __( 'Invalid User ID.' ), array( 'status' => 400 ) );
	}

	//return user 'profile_image' meta
	return update_user_meta( $userid, $field, esc_url( $value ) );
}

3. Re-fetch data to display updated image

If everything is successful, we want to close the modal and re-fetch the data from the server to show the latest values:

.then(res => res.json())
.then(data => {
  //clear file input text content
  fileLabel.textContent = "";
  //hide spinner
  toggleSpinner();
  //close modal     
  $("#exampleModal").modal("hide");
  //re-fetch data from server
  getData();
})
.catch(err => {
  console.log(err.message);
});

The getData() helper function handles the fetching of data from the server. Here’s how it looks like:

//Handle fetching of data from server
function getData() {
  //hide Card display to mimic loading
  wrapper.style.opacity = 0;
  fetch(userEndpoint + "?context=edit", {
    method: "GET",
    headers: {
      Authorization: "Basic " + window.btoa("admin:password")
    }
  })
    .then(res => res.json())
    .then(data => {
      //re-display card after response is received
      wrapper.style.opacity = 1;
      name.innerHTML = `${data.last_name} ${data.first_name}`;
      description.innerHTML = data.description;
      profilePic.innerHTML = data.profile_image
        ? `<img class="card-img-top" src="${data.profile_image}" alt="${data.last_name}" />`
        : "No Image found";
    })
    .catch(err => {
      console.log(err);
    });
}

Do note that for fetching of data, I added the context=edit argument because I want to retrieve the user’s first and last name. If the argument is omitted, the value will be set to default and the info returned will not contain what I want.

And that’s it! We have successfully uploaded a media document to the WP Media Library from a JavaScript application using the WP REST API.

Happy JavaScript-ing!

Source code

12 thoughts on “Uploading media using the WP REST API and JavaScript”

  1. I am using the external plugin “user avatar”, is it possible for make the same with user_avatar?
    Seems it’s 2 steps, first step we upload to media, and second step we use it from media?

    Reply
    • Hello Flane!

      Not sure if I’m understanding you correctly. But if the plugin you are using is saving the “User Avatar” as a user meta, then this method should work as well.

      Instead of using – get_user_meta( $userid, ‘profile_image’, true ) – you probably want to replace ‘profile_image’ with the meta key that the User Avatar plugin is using.

      Hope it makes sense!

      Reply
  2. {“code”:”rest_cannot_create”,”message”:”Sorry, you are not allowed to create posts as this user.”,”data”:{“status”:401}}

    I am getting abpove error

    Reply

Leave a Comment