WordPress CMS CRUD (Part 2): Create, Update, Delete

So far we’ve installed JWT in our WordPress instance and created a basic infrastructure for the app.

This post will take care of the rest of the CRUD system, we’ll create/save a post, update it and delete it.

All the functions we’ll discuss in this post require the user to be authenticated and will not work if there is no authentication token stored in a cookie on the user’s machine. A later iteration of this code may do better error handling.


Creating a new post means, essentially, saving the content of a form into the database.

The code completes the following functions:

  1. Gets a reference to the cookie storing the JWT authentication cookie
  2. Writes a POST HTTP request to insert the data into the database and create the post
  3. Does any cleanup necessary after the post was created. Some of those things may include
    • Clearing the editor
    • Notifying the user of the successful post creation
  4. Catch any errors that are not related to the promise code in the catch statement
export function save(post) {
  const token = Cookies.get(state.token); // 1
  // Save post
    method: "post",
    url: state.restUrl + "wp/v2/posts",
    data: post,
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer " + token
  }) // 2
    .then(response => {
      // do something
  }) // 3
    .catch(error => {
    }); // 4


The update function is an almost exact copy of the save() function discussed in the previous section.

The HTTP method we use is different, rather than POST, we use PUT to update the content.

The request is made to a specific post indicated by ID rather than the general posts endpoint.

export function update(post) {
  const token = Cookies.get(state.token); // 1
    method: "put",
    url: state.restUrl + "wp/v2/posts/" +,
    data: post,
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer " + token
  }) // 2
    .then(response => {
      // Do something after the update
  }) // 3
    .catch(error => {
    }); // 4


The delete function is the most delicate operation to perform. Once the data is gone it’s impossible to recover it from WordPress unless you have backups.

The function will:

Create and display a prompt to confirm you want to delete the post

Get a reference to the cookie holding our JWT credentials

If we confirmed the removal, the function will create a delete request for the specified post

The then() portion of the promise handles any cleanup or post-deletion activities we need to do like showing the updated list of posts.

As usual, the catch() block will handle any errors that happen in our code.

export function deletePost(post) {
  const confirm = window.confirm(`Delete Post: "${post.title.rendered}"`); // 1
  const token = Cookies.get(state.token); // 2

  // If user confirms delete then proceed
  if (true === confirm) {
      method: "delete",
      url: state.restUrl + "wp/v2/posts/" +,
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer " + token
    }) // 3
      .then(response => {
        // Display the list of posts
        // without the post we deleted
      .catch(error => {
      }); // 4

And that’s it. We now have a very basic CRUD application that will talk to a WordPress installation with JWT support enabled.

The code can certainly be enhanced, either by incorporating it into a framework that will make it easier to create individual components or by using a templating engine like Handlebars or Nunjucks.

There are other items related to WordPress and its REST API that we’ll discuss in the next post.


WordPress CMS CRUD (part 1): Getting Started

The whole idea of the WordPress REST API is to allow developers to use whatever tools they choose to create front-ends for WordPress systems. We’re no longer limited to PHP and the Gutenberg editor and the possibilities are endless.

This post will cover authentication using JWT, listing pages of posts, viewing individual posts, and the basic CRUD operations: create, update, and delete. We covered setting up JWT authentication in WordPress in Adding JWT to WordPress

We will write this using ES Modules, so we’ll have to build a quick WebPack build system to convert it into something that works in browsers.

The build system

Because we will be working with modules we need to write a quick WebPack build script that will use Babel to convert this into something that browsers will read without having to inline scripts with type=module.

const path = require("path");
const TerserPlugin = require('terser-webpack-plugin');

module.exports = (env, arg) => {
  config = {
    entry: ["./src/index.js"],
    output: {
      filename: "bundle.min.js",
      path: path.resolve(__dirname, "dist")
    devServer: {
      port: 3000,
      https: true,
      contentBase: path.join(__dirname, "dist")
    module: {
      rules: [
          test: /\.js$/,
          exclude: /(node_modules)/,
          use: {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-modules"],
    optimization: {
      minimize: true,
      minimizer: [new TerserPlugin()],
  if (arg.mode === "development") {
    config.devtool = "source-map";
  return config;

We then install the Node modules that we’ll need for WebPack to run.

The only module I’ll mention is @babel/preset-modules. It is an improvement over preset-env in that it fixes additional problems that would cause bundled code to be larger than necessary.

npm i -D @babel/cli \
@babel/core \
@babel/preset-modules \
babel-loader \
terser-webpack-plugin \
webpack webpack-cli \

We will also install the packages that we’ll use directly in the code we write.

npm i axios \
form-urlencoded \

axios is the package that we’ll use to handle fetching data from the server.

form-urlencoded will serialize the data we send to the server

js-cookie provides a programmatic way to set and read cookies. We’ll store the JSON Web Token in a cookie that we’ll read in different parts of the code.

Create Initial State

We will create a sate constant where we’ll set initial values for our authentication, and data storage for the values we get from the API.

We also create a function to add elements to the state. This is a very simplified version of what a state management library would do and, if we use React or Vue it would be unnecessary.

// Set state object with values that are changed programatically
const state = {
  loggedIn: false,
  restUrl: "http://localhost:8888/wordpress/wp-json/",
  token: "wp-token",

const setState = (toSet, newValue) => {
  state[toSet] = newValue;

export { state, setState };


To authenticate we first import all the packages that we’ll use: Axios, js-cookie, and form-urlencoded. We also import our state script.

// Import libraries
import axios from "axios";
import Cookies from "js-cookie";
import formurlencoded from "form-urlencoded";

// Import configs
import { state, setState } from "../state";

We then check if the cookie with our JWT token exists, meaning that the user is authenticated.

If it does then we initiate the login process and display the log out button.

If we’re not logged in then we display the login form and initiate the logout process.

export function init() {
  if (Cookies.get(state.token) === undefined) {
  } else {

The code makes the following assumptions:

  • We have a login form where the user will enter his username and password
  • On successful login, the form will be replaced with a logout button

The code proceeds as follows:

  1. Capture the username and password values from the form
  2. Use Axios to make a POST request to the JWT token endpoint with the urlencoded credentials as the payload data
  3. If we get back a 200 response code, and only then, we set a cookie with the token as the value
  4. We call the init() function
  5. If we do not receive a 200 code there was a problem and we tell the user that we couldn’t log her in
  6. If there was an error with the promise, the catch() block is called and we tell the user what the error was
export function initLogin() {
  getEl(loginForm).addEventListener("submit", event =>
    const creds = {
      username: document.getElementById(username).value,
      password: document.getElementById(password).value
    }; // 1

    // Make request to authenticate
      method: "post",
      url: state.restUrl + "jwt-auth/v1/token",
      data: formurlencoded(creds),
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
    }) // 2
      .then(response => {
        if (200 === response.status) {
          Cookies.set(state.token,, {
            expires: 1,
            secure: true
          }); // 3
          init(); // 4
        } else {
          // Executed when response code is not 200
          alert("Login failed, please check credentials and try again!");
      }) // 5
      .catch(error => {
        // Also log the actual error
      }); // 6

The login() function does housekeeping. It removes the login form, it shows the listing of posts and does any other task that you need to do once the user is logged in.

export function login() {
  // Set the loggedIn status to true
  setState("loggedIn", true);

  // Remove login form and replace it
  // with log out button

  // Show the listing of posts

  // Do whatever else you need to

The initLogout() function handles the logout functionality.

It is conceptually simpler than initLogin(). It removes the token cookie and it calls the logout() function.

export function initLogout() {
  // Setup event listeners for logout form
  getEl(logoutForm).addEventListener("click", event => {
    Cookies.remove(state.token, { secure: true });


The logout() function, like its login counterpart, does the housekeeping we need to do to make sure this works.

We set the loggedIn state to false so that the workflow will prompt for a login.

We swap the logout button for the login form and we show the posts available when the user is not logged in.

export function logout() {
  // Set the loggedIn statis to false
  setState("loggedIn", false);

  // Remove logout button and adds login form

  // Show unauthenticated posts listing

Read Content

To display the content of our WordPress blog we need to do two things: display a list of all posts, like what you’d see in a default WordPress home page, and display individual posts.

We’ll tackle these tasks separately.

Post Listing

To get a listing of the first posts on the database, 10 by default, we run code similar to the init function below.

We use Axios to make a GET request to the posts API endpoint. We save the posts data to our state object and call the render function.

export function init(event) {
  // If coming from an event,
  // prevent default behavior
  if (event) event.preventDefault();

  // Make API request with Axios
    .get(state.restUrl + "wp/v2/posts", {
      params: {
        per_page: 10
    .then(({ data: posts }) => {
      setState("posts", posts);

The render() function is what creates the content that will create the content we display to the users. It is cumbersome and, for a production application, I would consider using Handlebars or some kind of templating solution but in this case I’m more concerned with the authentication and how it works.

For each post that we get from the database via ou state object we do the following:

  1. We create an article element
  2. We add the post class to the article we just created
  3. We then create the content using the article’s innerHTML attribute with a Template Literal
    • The template literal interpolates values from the posts we store in our state object
  4. We add an event listener to the post title to link to the post and then call the post() function
export function render() {
  // Map through the posts => {
    // Setup the post article element
    const article = createEl("article"); // 1
    article.classList.add("post"); // 2
    article.innerHTML = `
      <h2 class="entry-title">
        <a href="#${post.slug}">
      <div class="entry-content">
    `; // 3

  article.querySelector(".entry-title a")
    .addEventListener("click", event => {
      setState("post", post);
  }); // 4

Single Post

The single post render function does as the title says, it renders the content of a single post on the screen and, if the user is logged in, displays links to edit and delete the current post.

The function does the following:

  1. Create the article and a
  2. Attach the post CSS class to it.
  3. Add the content using innerHTML.
    • The content includes a link to go back to the post listing; we’ll attach an event to the link later
    • We also use the rendered versions of the title and the content for the post
  4. We attach a click event to the back link so that clicking on the link will display a list of the available posts
  5. If the user is logged in, provide links to edit and delete the post
export function render(event) {
  // Setup the post article element
  const article = createEl("article"); // 1
  article.classList.add("post"); // 2
  article.innerHTML = `
    <p><a id="${backBtn}" href="#">
    &lt; Back to Posts</a></p>
    <h1 class="entry-title">
    <div class="entry-content">
  `; // 3

  // Attach event listeners to back button
  article.querySelector(`#${backBtn}`).addEventListener("click", event => {
    setState("post", null);
  }); .// 4

  // If logged in, display edit link
  if (state.loggedIn) {
  } // 5

  // Clear the posts from the page

  // Add the single post to the page

The last two functions of this section, editLink() and deleteLink() wor similarly so we’ll describe them together.

They will create a link element and add the respective class (edit and delete) and content text.

They will add a click event handler that will call the appropriate function, loadPost() for editLink() and deletePost() for deleteLink().

export function editLink(post) {
  // Setup the edit link
  const link = document.createElement("a");
  link.href = "#edit-post";
  link.innerText = "Edit";

  link.addEventListener("click", () => {

  // Return the link element
  return link;

export function deleteLink(post) {
  // Setup the delete link
  const link = document.createElement("a");
  link.href = "#delete-post";
  link.innerText = "Delete";

  link.addEventListener("click", event => {

  // Return the delete link
  return link;

With the code so far we have the basic authentication structure and a way listing of posts and individual posts.

We’ll explore more details in the next post.


Adding JWT to WordPress

One of the things I’ve struggled with when working with headless WordPress is getting stuff that I need to be authenticated to accomplish; for example, I can get a list of posts and get an individual post but I can’t create, update and delete posts from a remote client.

While reviewing a Udemy Course on the subject (Zach Gordon’s Headless WordPress REST API Authentication I discovered that the reason this happens is that WordPress doesn’t provide third-party clients authentication by default. SO if you want to authenticate you have to set up your authentication infrastructure in WordPress.

This post will cover how to implement JSON Web Token (JWT) authentication on a WordPress installation.

I chose JWT as my login solution not just because it’s what the course I took used but because other options are too cumbersome and do way more than what I need them to, opening too many potential security holes.

I will use the JWT Authentication for WP REST API plugin as suggested in Gordon’s course. It hasn’t been tested in about a year but it works, for now, so I’ll continue using it.

The first step is to download and install the plugin. You can do so from inside your WordPress installation by going to the Plugins menu and selecting add new, searching for JWT authentication, and then installing the plugin. Don’t activate it yet, we still need to configure it.

The next steps are done outside of the administrator’s GUI.

We first need to add

RewriteEngine on
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]

The JWT needs a secret key to sign the token this secret key must be unique and never revealed.

To add the secret key edit your wp-config.php file and add JWT_AUTH_SECRET_KEY constant

define('JWT_AUTH_SECRET_KEY', 'your-top-secret-key');

You can use a string from the WordPress Salt Generator as the value for the key.

The wp-api-jwt-auth plugin has the option to activate CORS support.

To enable the CORs Support, edit your wp-config.php file and add a JWT_AUTH_CORS_ENABLE constant

define('JWT_AUTH_CORS_ENABLE', true);

Finally activate the plugin. The best way to test it is to point your browser to https://<your-server-ulr>/wp-json/jwt-auth/v1. It should return a list of the available endpoints.

We’re ready to begin working with WordPress as a headless CMS


Deep Dive: OpenType features in CSS

In previous posts (this one from 2015, this one from 2017) I did brief writeups about OpenType features and what they were. In Open Type Features in CSS from 2019, I wrote about OpenType features in the context of variations or what makes variable fonts and briefly touched on other features.

In this post, I will dive deeper into other features available on OpenType fonts, how to implement them directly in CSS and how to implement them with CSS custom properties

Getting started

The first thing we need to do to enable OpenType features is to figure out what features are available to the specific font we are using and what font provider we’re using.

If you use Adobe Fonts (FKA Typekit) you can see the OpenType features available when you edit the fonts in a kit.

Fontkit OpenType edit font dialogue
Fontkit OpenType feaature dialogue, part of the font section of the kit editing screen.

The next image shows the details of the font editing screen that presents the available OpenType features of a font.

Fontkit OpenType feature dialogue detail
Detail of the font editing dialogue showing OpenType Features

Unfortunately, Google Fonts doesn’t provide an interface to the OpenType features supported on each font. thisarmy created fontsinfo as a way to address this issue.

For all the fonts listed in Google Fonts Github Repo it will list and provide a visual demo of the OpenType features available to each font.

Fontsinfo web interfacce
Fontsinfo web interfacce

There is also Google WebFonts Helper that provides a better, in my opinion, and easier to use a mirror of Google Fonts.

The final way to do it is using tools like Wakamaifondue to get a list of the features available to that particular font.

Using OpenType Features directly

We will use Roslindale Text for the small caps examples and Recursive as for the stylistic set examples.

The first way to use OpenType features is to add them directly to the CSS we want to use them on.

The advantage of doing this is that, overall, there is less code to type. The disadvantage is that browser support is uneven so you have to work with multiple versions of each feature if you want to support older browsers.

We’ll take two Open Type features and analyze how they work individually and together.

The first feature is Small Caps. This feature converts lower-case characters

The Codepen below shows the complete result of using two features: Small Caps and Caps to Small Caps.

The idea is to make all the text into smaller version of the capital letters. This is different than using text-transform: uppercase as it will produce smaller text than regular uppercase characters.

The first step is to load the font using @font-face. Notice how we define the same font twice using different values for the format. I’m following Jason Pamental’s advice on the formats to make sure my variable font works everywhere by not relying on woff2 to work for variable fonts.

The next step is to use the font. I put the font-family declaration in the root html element to make sure it cascades down throughout the document unless I override it.

@font-face {
  font-family: "Roslindale Text";
  src: url("")
      format("woff2 supports variations"),
  font-display: swap;

body {
  font-family: "Roslindale Text", sans-serif;

We create multiple classes for different font features that we want to use.

We use the corresponding font-variant selection for the browsers that support it and three different versions of the low-level font-feature-settings, one prefixed for Firefox, one prefixed for WebKit browsers and an unprefixed versions, supposed to work everywhere.

The values for font-variant-caps are defined in the CSS Fonts Module Level 4. The values for all font-feature-settings are dictated by the OpenType specification.

.smcp {
  font-variant-caps: small-caps;
  -moz-font-feature-settings: "smcp";
  -webkit-font-feature-settings: "smcp";
  font-feature-settings: "smcp";

We do the same thing for the caps to small caps. Note that the font-variant-caps value is all-small-caps and not the direct equivalent of c2sc like the values for font-feature-settings.

.c2sc {
  font-variant-caps: all-small-caps;
  -moz-font-feature-settings: "c2sc";
  -webkit-font-feature-settings: "c2sc";
  font-feature-settings: "c2sc";

We can also combine both small caps examples into a single set of rules. We use all-small-caps for font-variant and a comma-separated list of all the features that we want to use.

.small-caps {
  font-variant-caps: all-small-caps;
  -moz-font-feature-settings: "c2sc", "smcp";
  -webkit-font-feature-settings: "c2sc", "smcp";
  font-feature-settings: "c2sc", "smcp";

The result of these three classes is show in the Codepen below.

The first paragraph uses the small-caps class and keeps all elements at the same size.

The second paragraph uses small caps (smcp) to only turn the lowercase letters into small caps. You can see the difference between lower and uppercase letters.

Another interesting case to look at is the stylistic sets available to a font. Not all fonts have the same number of stylistic sets available and some stylistic sets will change the same letters in different ways so with these features you must be very careful when using them and deciding which one to use.

This example also assumes that we don’t want to use all stylistic alternates available to the font so we won’t be able to use the all alternates OpenType feature.

Also, because we’re using multiple values of the same feature we can’t call the same feature multiple times or it will override the values with the last one in document order, so we’ll cheat and leverage Using multiple OpenType features to accomplish our goals.

Because I want to make sure that we cover as many supported browsers as possible, I’ve written the property three times, once for Firefox, once for WebKit browsers and an unprefixed version. The code looks like this:

.styled {
  -moz-font-feature-settings: "ss01", "ss02", 
  "ss03", "ss04", "ss05", "ss06", "ss07";
  -webkit-font-feature-settings: "ss01", "ss02", 
  "ss03", "ss04", "ss05", "ss06", "ss07";
  font-feature-settings: "ss01", "ss02","ss03", 
  "ss04", "ss05", "ss06", "ss07";

CSS Custom Properties

Another option, slightly more verbose, is to use CSS custom properties, then insert these custom properties into classes and finally create a single font-feature-settings selector so that if any of the custom properties

We could also use custom properties rather than spell out the individual properties. That is left as an exercise to the reader 🙂

Links and Resources.


Finally learning how to uses gulp-newer

One of the nice things about Gulp workflows if that you can minimize the amount of work that you have to do if you use the tools correctly.

I just discovered today that I wasn’t using gulp-newer properly. It’s almost an RTFM moment so I’m documenting it to prevent it from happening to me again 🙁

The idea behind gulp-newer is to reduce the number of files that we work on in a Gulp task by only passing around the files that are newer than the same file on the destination directory. For this technique to work you have to tell Newer what are you comparing with and yes, you guessed it, I never did.

The working example below taken from gulp-newer‘s Readme shows how the plugin works.

const gulp = require('gulp');
const newer = require('gulp-newer');
const imagemin = require('gulp-imagemin');

const imgSrc = 'src/img/**';
const imgDest = 'build/img';

// Minify any new images
gulp.task('images', function() {

  // Add the newer pipe to pass through newer images only
  return gulp.src(imgSrc)


The plugin will take all images in the src directory and compare them with the same images in the destination directory. If any of the sources images are newer, they will be processed with Imagemin but if they are not, they will be skipped.

The key is the parameter to newer that tells it where to look for the files to compare with. Without the parameter, it can’t do its work.