Building a Headless WordPress Theme

arnonate

Nate Arnold

Posted on September 10, 2020

Building a Headless WordPress Theme

I recently developed a WordPress theme for headless WordPress projects. This post is a simple explanation of the decisions that were made after implementing the theme on a few JAMstack projects.

Highlights

The index route forwards to the /wp-admin login screen so there is no need for any front-end code:

<script type="text/javascript">
    window.location.replace(window.location.protocol + "//" + window.location.hostname + "/wp-admin");
</script>
Enter fullscreen mode Exit fullscreen mode

There is an example Custom Post Type already set up to display in the API as well as in the Graph if you are using WPGraphQL:

  add_action( "init", "create_custom_post_type" );

  function create_custom_post_type() {
    register_post_type("custom_posts", // Register Custom Post Type
      array(
        "labels" => array(
          "name"                => "Custom Posts", // Rename these to suit
          "singular_name"       => "Custom Post",
          "add_new"             => "Add New",
          "add_new_item"        => "Add New Custom Post",
          "edit"                => "Edit",
          "edit_item"           => "Edit Custom Post",
          "new_item"            => "New Custom Post",
          "view"                => "View Custom Post",
          "view_item"           => "View Custom Post",
          "search_items"        => "Search Custom Posts",
          "not_found"           => "No Custom Posts found",
          "not_found_in_trash"  => "No Custom Posts found in Trash"
        ),
        "menu_position"         => 5,
        "menu_icon"             => "dashicons-awards",
        "public"                => true,
        "show_in_rest"          => true,
        "show_ui"               => true,
        "show_in_menu"          => true,
        "publicly_queryable"    => true,
        "capability_type"       => "page",
        "hierarchical"          => false,
        "has_archive"           => true,
        "supports"              => array("title","thumbnail","editor","revisions","excerpt","author"), // Other Options: trackbacks, custom-fields, page-attributes, comments, post-formats
        "can_export"            => true, // Allows export in Tools > Export
        "taxonomies"            => array(), // Add supported taxonomies,
        "show_in_graphql"       => true,
        "graphql_single_name"   => "CustomPost",
        "graphql_plural_name"   => "CustomPosts",
      )
    );
  }
Enter fullscreen mode Exit fullscreen mode

There are example Custom Shortcodes and Custom Taxonomies set up to display in the API as well as in the Graph:

function headless_shortcode( $atts , $content = null ) {
    // Attributes
    $args = shortcode_atts( array(
        "link" => "",
        "target" => "_self",
        "rel" => "",
        "class" => "",
    ), $atts );

    return '<a href="' . $args['link'] . '" target="' . $args['target'] . '" rel="' . $args['rel'] . '" class="btn ' . $args['class'] . '">' . $content . '</a>';

}
add_shortcode( "headless", "headless_shortcode" );
add_filter("acf/format_value/type=textarea", "do_shortcode");

function headless_taxonomy() {
    $labels = array(
      "name"                       => "Taxonomies",
      "singular_name"              => "Taxonomy",
      "menu_name"                  => "Taxonomies",
      "all_items"                  => "All Taxonomies",
      "parent_item"                => "Parent Taxonomy",
      "parent_item_colon"          => "Parent Taxonomy:",
      "new_item_name"              => "New Taxonomy",
      "add_new_item"               => "Add Taxonomy",
      "edit_item"                  => "Edit Taxonomy",
      "update_item"                => "Update Taxonomy",
      "view_item"                  => "View Taxonomy",
      "separate_items_with_commas" => "Separate Taxonomies with commas",
      "add_or_remove_items"        => "Add or remove Taxonomies",
      "choose_from_most_used"      => "Choose from the most used",
      "popular_items"              => "Popular Taxonomies",
      "search_items"               => "Search Taxonomies",
      "not_found"                  => "Not Found",
      "no_terms"                   => "No Taxonomies",
      "items_list"                 => "Taxonomies list",
      "items_list_navigation"      => "Taxonomies list navigation",
    );
    $args = array(
      "labels"                     => $labels,
      "hierarchical"               => false,
      "public"                     => true,
      "show_ui"                    => true,
      "show_in_quick_edit"         => false,
      "meta_box_cb"                => false,
      "show_admin_column"          => false,
      "show_in_nav_menus"          => false,
      "show_tagcloud"              => false,
      "show_in_rest"               => true,
      "show_in_graphql"            => true,
      "graphql_single_name"        => "Taxonomy",
      "graphql_plural_name"        => "Taxonomies",
    );

    register_taxonomy( "taxonomy", array( "page" ), $args );
  }

  add_action( "init", "headless_taxonomy", 0 );
Enter fullscreen mode Exit fullscreen mode

There are some utility functions that help with reorganizing/removing menu items and disabling RSS:

function remove_menus() {
    remove_menu_page( "index.php" ); //Dashboard
    remove_menu_page( "jetpack" ); //Jetpack*
    remove_menu_page( "edit-comments.php" ); //Comments
}

add_action( "admin_menu", "remove_menus" );

function headless_custom_menu_order( $menu_ord ) {
    if ( !$menu_ord ) return true;

    return array(
        "edit.php?post_type=page", // Pages
        "edit.php", // Posts
        "edit.php?post_type=custom_posts", // Custom Post Type
        "separator1", // First separator

        "upload.php", // Media
        "themes.php", // Appearance
        "plugins.php", // Plugins
        "users.php", // Users
        "separator2", // Second separator

        "tools.php", // Tools
        "options-general.php", // Settings
        "separator-last", // Last separator
    );
}
add_filter( "custom_menu_order", "headless_custom_menu_order", 10, 1 );
add_filter( "menu_order", "headless_custom_menu_order", 10, 1 );
Enter fullscreen mode Exit fullscreen mode

All ACF fields that are empty are nullified by default:

// Return `null` if an empty value is returned from ACF.
if (!function_exists("acf_nullify_empty")) {
  function acf_nullify_empty($value, $post_id, $field) {
      if (empty($value)) {
          return null;
      }
      return $value;
  }
}
add_filter("acf/format_value", "acf_nullify_empty", 100, 3);
Enter fullscreen mode Exit fullscreen mode

Headless is available over on GitHub. Download it, peruse it, use it and let me know what you think!

💖 💪 🙅 🚩
arnonate
Nate Arnold

Posted on September 10, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Building a Headless WordPress Theme
wordpress Building a Headless WordPress Theme

September 10, 2020