DEV Community

Cover image for Building a Headless WordPress Theme
Nate Arnold
Nate Arnold

Posted on

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!

Top comments (1)

Collapse
 
whgandalf profile image
WhGandalf

I was about to create a repository with my own version of a headless theme but I found yours and decided to use it, so far everything is good, great job!