How to Enable Frontend Editing with Gutenberg Blocks (Part 2)

Welcome back! In Part 1, we covered preparing our block for frontend editing by implementing unique block identifiers, rendering the block dynamically, and establishing a frontend component. Now, we’ll dive into the WordPress REST API to set up a custom route and call that route asynchronously from our frontend component.

Note: The example snippets in this post are modified from @ravewebdev/initiative-tracker, my Character Initiative Tracker block plugin for table-top role-playing games, which was built with @WebDevStudios/create-block, the WDS block scaffolding CLI tool.

3. WP REST API Implementation

Before we continue building out our frontend component, let’s take a PHP break and set up a route with the WP REST API, so we can plug that into our editing functionality:

  • Set up custom route
  • Localize route

3.1. Set Up Custom Route

If you’ve been tinkering in WordPress for a while, you may have handled frontend interactivity via the AJAX API (admin-ajax.php) with jQuery. These days, though, many engineers prefer to take advantage of the newer WordPress REST API instead, given its potential to simplify the development process and possibly improve performance. We’ll be using the WP REST API here, but feel free to go old school with the AJAX API if you want; keep in mind, however, you may need to alter a substantial amount of the code included throughout this post to make it work.

To start, we’ll register our custom route with register_rest_route(), which accepts three parameters:

  • namespace: This is a unique string representing the current plugin/package namespace, ideally specifying a version number.
  • route: This is the custom route URL, optionally with Regex patterns to match required arguments passed to our route. We’ll use the pattern (?P<id>[\d]+) to ensure a integer is passed to our route, representing our post ID.
  • args: This signifies the route options, which can be an array of options for a single endpoint or a multi-dimensional array for multiple endpoints. We’ll use the former since we’re only implementing one endpoint for our route. These are the options we need to worry about right now:
    • methods: This defines the method or methods accepted for this endpoint. We’re using WP_REST_Server::EDITABLE here, which is an alias for the POST, PUT, and PATCH HTTP methods.
    • callback: This is the function that’s called when our endpoint is successfully requested.
    • permission_callback: This function allows us to perform a permissions check when our endpoint is requested, prior to the callback being run.

Since our endpoint is accepting editing methods, we don’t want absolutely anyone to access it, right? Well, that’s where our permissions callback comes into play. As we defined our route with a required ID argument to represent the current post ID, we’ll retrieve that first, then implement several checks to ensure only permitted users are allowed to proceed.

  • Check if user is currently logged in via is_user_logged_in(), as we don’t want to allow any non-logged-in users to access our endpoint.
  • Check if the post ID we were passed represents a valid post object with get_post().
  • Check if the current user has the desired capability with current_user_can(). As we’ll be editing an already-published post with our block, we’re checking for the capability edit_published_posts, which, by default, is available to Super Admin, Administrator, Editor, and Author roles, but feel free to pick another or create your own capability for this step.
  • Finally, if the current user did not pass our previous capability check, see if they are the author of the current post.

Now, we’re on to our main callback function! We’d normally need to check for and validate our nonce, but the WP REST API handles this all automatically.

Let’s continue and retrieve the parameters from our request, similar to how we did in the permissions callback. As before, we’ll need the target post ID (id), but we’ll also need to get our target block ID (block_id) to ensure we’re modifying the correct block in our post. Likewise, here is where we’ll retrieve any other block attributes we want to work with or update.

Next, we’ll use get_post_field() to fetch the post’s content and feed it into parse_blocks(), which, as the function name suggests, parses the post’s content, converting block comments into associative arrays of block data.

Now we can iterate over the resulting array of block data, using array_map(), passing any necessary data or attributes we’ll need via use. Here, we’re merely passing our block ID, but you’ll likely want to pass some block attributes as well. We’ll perform a quick two-part check, allowing us to return early if this isn’t the block we’re looking for:

  1. Compare the current block’s name, via the blockName value, to our custom block’s name, which we set all the way back in Part 1, step 1.1.
  2. Compare the current block’s ID, via the id value within the block’s attrs array, to our target block’s ID.

Once we pass the above check, we can start modifying our block’s attributes or other values (e.g., innerContent), like in the commented-out example above, and return the updated block array.

After we’ve modified our block, we need to save the post’s content with wp_update_post(), which, for our purposes, will take an associative array of the post’s ID and content. Be careful here! Since we parsed the content earlier into an array of blocks, we’ll need to transform it back into block comments via serialize_blocks().

Finally, we’ll end our function by returning an appropriate message that we can later display to the user. I like to return an instance of WP_REST_Response, which allows us to specify an HTTP status code along with our message.

3.2. Localize Route

While it’s totally possible to skip this section and hard-code our route path in our frontend JavaScript code, let’s try localizing our route so we can access that value dynamically later on. This is particularly handy if there’s any possibility you might change anything about your route path (e.g., version number) later on.

Here we’ll localize our custom route with wp_localize_script(), which takes three parameters:

  • handle: The script handle to attach our data to—here it’s the handle for our frontend script file
  • object_name: The JavaScript object name, which we’ll use later to retrieve our route path
  • data: This is an array of data to assign as properties of our object variable. We’ll pass the simple relative path of our route (without the post ID route argument).

Note: Depending on how you implement your REST request on the frontend, you may also want to set up and localize a nonce here manually, using wp_rest as the nonce action, given that the WP REST API has some built-in handling for this nonce action; however, since we’re using the WP JavaScript API here, that’s handled automagically.

4. Frontend Updates

Now, we’ll dive back into our frontend component and pull everything together:

  • Handle component state (optional)
  • Perform request

4.1. Handle Component State

To prepare for sending the edit request to our route, we’ll set up some more state variables to help keep track of everything. This section is technically optional, as it’s not actually required to implement our frontend editing process, but these extra state variables help provide feedback to the end user.

While adding loading state isn’t strictly necessary, it is useful for letting the user know that something is happening behind the scenes. There are many possibilities, such as disabling a submit button (via setting its disabled property to the value of our loading variable—e.g., disabled={ isLoading })—displaying a loading GIF, or otherwise communicating to the user that processing is occurring.

Next, we’ll set up another state variable to assist with displaying success and error messages to the user. We’ll utilize useEffect() again, this time passing an array with a single value, notice, which will instruct our effect to run each time the value of notice changes. Our purpose here is to automatically clear the notice after a set period of time—one minute (60,000 ms) in our case—using setTimeout(), then return a call to clearTimeout() to remove the timer.

That said, feel free to adjust the timing or remove this block of code altogether, if you want to display the notice indefinitely or remove it another way (e.g., with a close button).

One thing to keep in mind here: state variables don’t always work as expected within setTimeout() callbacks. The value of a state variable within the callback will be its value when the timeout was scheduled and not when the callback actually runs. Luckily, we can ignore this quirk here, as we don’t care what the value was, only what we want to set it to (null); however, if we did want to check the current value of our state variable from within our callback, we could make use of another React hook, useRef().

4.2. Perform Request and Update Block

At last, we’re ready to implement a fetch request to our custom WP REST API route, save our block data, and show some update notices.

To start, we’ll create a new asynchronous function to hold our update process and begin by changing our state: setting our loading variable to true and our notice variable to null.

Then we’ll make an asynchronous call to our custom route via apiFetch(), which is a wrapper around window.fetch(), accepts an object of options as a parameter, and returns a Promise object. We’ll pass the following options:

  • path: This is the relative path to our REST route, which is automatically prepended with the current site’s WP REST API’s root URL.
  • method: This is the HTTP method we’ll be using to access our route. Here we’ll simply employ POST.
  • data: This is an object of data to be passed to our route. We’ll use the spread operator to pass all our component attributes through to our route.

Since we already set up our route callback back in step 3.1., we can now proceed to handle the response from our callback.

For a successful response, we’ll create a then() block, in which we can update any component props. While not required, this allows us to “reset” the data attributes in our frontend component’s props to the values newly saved in the database. Then we’ll return a formatted object with the response type and message.

In the event of an error, our catch() block will similarly return a formatted response type and message, with a slightly different format. In our callback, we return an instance of WP_REST_Response on success, but an instance of WP_Error is generally passed on failure; the former returns a string for a message, whereas the latter returns an object, with the actual error message saved as the message property.

Next, we’ll toggle our isLoading state back to false and update our notice state to the value of the response object we just created.

Finally, we’ll output any success/error notice to the end user in the return statement of our component. We’ll use the type property to determine the class and role attributes for our DOM element (the latter of which is valuable for accessibility), then output the actual message as text.

Here’s how it looks in action:


Congrats! If you followed all that, you’re well on your way to implementing frontend editing with blocks!

There’s plenty to keep in mind when transforming blocks in this way, from block identifiers, to double rendering with PHP and JavaScript, to creating custom routes via the REST API, to performing fetch requests and displaying status messages. In the end, it boils down to leveraging as much of the built-in codebase as possible and letting it do the heavy lifting, with a healthy dose of experimentation, of course.

If you use this tutorial to build or extend your own block with frontend editing, let us know how it goes by commenting below. And if you’ve implemented frontend block editing by another method, please share your tips and techniques with us in the comments.


Have a comment?

Your email address will not be published. Required fields are marked *

accessibilityadminaggregationanchorarrow-rightattach-iconbackupsblogbookmarksbuddypresscachingcalendarcaret-downcartunifiedcouponcrediblecredit-cardcustommigrationdesigndevecomfriendsgallerygoodgroupsgrowthhostingideasinternationalizationiphoneloyaltymailmaphealthmessagingArtboard 1migrationsmultiple-sourcesmultisitenewsnotificationsperformancephonepluginprofilesresearcharrowscalablescrapingsecuresecureseosharearrowarrowsourcestreamsupportunifiedupdatesvaultwebsitewordpress