Headless framework
Basics
Custom post types

Custom post types

The SiteBox framework leverages the GraphQL mechanism shipped with Gatsby.js, which is a data layer API used to consume external data within a Gatsby-generated website. There is an extensive explanation of how to use data fetched from different providers in the official Gatsby documentation. However, if your project consumes only WordPress-generated data, it is handled out of the box by the SiteBox WordPress plugin. It exposes the GraphQL mechanism as a wp-json extension, which is consumed during the Gatsby.js building process.

Models

The models exposed in the GraphQL interface depend on the project configuration. By default, the SiteBox plugin exposes a list of WordPress native post types, as well as some added by the framework such as Documents, Authors, or People. To find out the full list of available models, simply visit the GraphiQL panel within the WordPress Dashboard, then navigate to SiteBox > GraphQl IDE.

GraphiQL IDE within WordPress

Native Models

Your boilerplate-based project comes with some models ready to use without any configuration. The list may change over time and may be different when manipulated by integrations or extensions, so always refer to the list available in the WordPress dashboard (or GraphiQL interface in Gatsby).

List of native models

  • Posts – a collection of posts.
  • Categories – a collection of post categories.
  • Tags – a collection of post tags.
  • Pages – a collection of pages.
  • People – a collection of people custom post types enabled by the SiteBox Framework.
  • PeopleCategories – a collection of category-like taxonomies applied to people custom post types enabled by the SiteBox Framework.
  • PeopleTags – a collection of tag-like taxonomies applied to people custom post types enabled by the SiteBox Framework.
  • Documents – a collection of documents custom post types enabled by the SiteBox Framework.
  • DocumentCategories – a collection of category-like taxonomies applied to documents custom post types enabled by the SiteBox Framework.
  • DocumentTags – a collection of tag-like taxonomies applied to documents custom post types enabled by the SiteBox Framework.
  • GutenbergBlocks – a collection of Gutenberg blocks. Please note that blocks are applied as children of custom post types with Gutenberg experience enabled. These nodes, as long as provided as properties to the <StatikBlocks> component, result in rendered Gutenberg content.
  • MediaItems – a collection of media in the library.
  • Megamenus – a collection of WordPress entities with Gutenberg content available to render as a mega menu.
  • Menus – a collection of WordPress native menus.
  • MenuItems – a collection of items added to WordPress native menus.

Please note, some collections are considered as child nodes applied to other models. The approach allows to query data more efficiently within the Gatsby app.

Custom Post Types

One of the most important features of WordPress is the ability to add custom post types to manage content efficiently. Considering the fact that data is always presented in the front-end application, it needs to be enabled in the WordPress-based GraphQL interface.

The SiteBox Framework covers adding a GraphQL-enabled custom post types using one of the following ways:

  • Developers can add CPTs by modifying the WordPress child theme.
  • Content editors can use the CPT generator plugin.

Adding CPT by modifying WordPress SiteBox theme

To ensure that WordPress uses the most up-to-date version of a theme, it is recommended not to edit any of its files directly.

Assuming that developers will follow the directory hierarchy of the parent SiteBox theme, to add a custom post type named "Movies," make sure that the following snippet within the functions.php file is available:

backend/themes/statik/functions.php
/**
 * Load Custom post types.
 */
require_once __DIR__ . '/includes/custom-post-types/index.php';

File backend/themes/statik/includes/custom-post-types/index.php which is required inside backend/themes/statik/functions.php is a hub for all custom post types added at a project level.

Next step is to create CPT file and place it to the backend/themes/statik/includes/custom-post-types/movies/ directory. This file will hold all information about the new CPT as shown below.

movies-cpt.php
<?php
 
declare(strict_types=1);
 
\defined('ABSPATH') || exit('Direct access is not permitted!');
 
\add_action('init', 'statik_register_movies_cpt', 1);
 
if (false === \function_exists('statik_register_movies_cpt')) {
    /**
     * Register custom post type for Movies.
     *
     * @since 2.0.0
     */
    function statik_register_movies_cpt(): void
    {
        $labels = [
            'name' => \_x('Movies', 'Post Type General Name', 'statik'),
            'singular_name' => \_x('Movie', 'Post Type Singular Name', 'statik'),
            'menu_name' => \__('Movies', 'statik'),
            'name_admin_bar' => \__('Movies', 'statik'),
            'archives' => \__('Movie Archives', 'statik'),
            'attributes' => \__('Movie Attributes', 'statik'),
            'parent_item_colon' => \__('Parent Movie:', 'statik'),
            'all_items' => \__('All Movies', 'statik'),
            'add_new_item' => \__('Add New Movie', 'statik'),
            'add_new' => \__('Add New', 'statik'),
            'new_item' => \__('New Movie', 'statik'),
            'edit_item' => \__('Edit Movie', 'statik'),
            'update_item' => \__('Update Movie', 'statik'),
            'view_item' => \__('View Movie', 'statik'),
            'view_items' => \__('View Movies', 'statik'),
            'search_items' => \__('Search Movie', 'statik'),
            'not_found' => \__('Not found', 'statik'),
            'not_found_in_trash' => \__('Not found in Trash', 'statik'),
            'featured_image' => \__('Featured Image', 'statik'),
            'set_featured_image' => \__('Set featured image', 'statik'),
            'remove_featured_image' => \__('Remove featured image', 'statik'),
            'use_featured_image' => \__('Use as featured image', 'statik'),
            'insert_into_item' => \__('Insert into Movie', 'statik'),
            'uploaded_to_this_item' => \__('Uploaded to this Movie', 'statik'),
            'items_list' => \__('Movies list', 'statik'),
            'items_list_navigation' => \__('Movies list navigation', 'statik'),
            'filter_items_list' => \__('Filter Movies list', 'statik'),
        ];
 
        \register_post_type(
            'movie',
            [
                'label' => \__('Movie', 'statik'),
                'description' => \__('Movies', 'statik'),
                'labels' => $labels,
                'supports' => ['title', 'custom-fields', 'revisions'],
                'hierarchical' => false,
                'public' => true,
                'show_ui' => true,
                'show_in_menu' => true,
                'menu_position' => 20,
                'menu_icon' => 'dashicons-media-archive',
                'show_in_admin_bar' => true,
                'show_in_nav_menus' => true,
                'can_export' => true,
                'has_archive' => false,
                'exclude_from_search' => true,
                'publicly_queryable' => true,
                'capability_type' => 'post',
                'show_in_rest' => true,
                'rest_base' => 'documents',
                'rewrite' => ['with_front' => false],
                'show_in_graphql' => true,
                'graphql_single_name' => 'movie',
                'graphql_plural_name' => 'movies',
            ]
        );
    }
}

Note last properties: show_in_graphql, graphql_single_name and graphql_plural_name enforce the custom post type to be shown within the WordPress GraphQL interface, making its data available to use in a Gatsby instance.

The next step is to add a reference to the CPT files held in a separate directory.

backend/themes/statik/includes/custom-post-types/index.php
<?php
 
declare(strict_types=1);
 
\defined('ABSPATH') || exit('Direct access is not permitted!');
 
/**
 * In this file can be added more WordPress Custom Post Types.
 * Each CPT should be in the separate directory and each file should
 * be included there using the `require_once` function.
 */
require_once __DIR__ . '/movies/movies-cpt.php';

Using CPT generator plugin

WIP

Querying data

Any data available in the WordPress GraphQL interface (@TBD: Damian to confirm) can be queried out of front-end Gatsby application. This approach allows to generate data JSONs that hydrates the front-end website during the build time. No further communication with WordPress is required – more about this concept is explained on the Foundation page.

Backend query

Custom post type Movies should be listed in the WordPress backend. Go to 'Movies' and create new movie post type.

Movies custom post type

In order to test 'Movies' custom post type query, go to the SiteBox -> GraphiQL IDE. In 'Query composer' select allMovies query and then nodes. For this test purpose we will select title, url, movieId, seo title and description. After clicking 'play' button data from the Movies custom post type should be generated in the right column.

GraphiQL Movies query

As you can see, data has been pulled from the newly created post inside 'Movies' custom post type.

Frontend query

Example presented below will create a single page that holds a post data. It will be returned for /insights/hello-world route. Also, the example implements Gutenberg Blocks support which is explained more in detail on Blocks page.

A component that is expected to render a Hello World page looks for a post with ID 18 in the GraphQL query. Please note, no JavaScript variables can be passed to the GraphQL call as it is performed outside the regular JavaScript lifecycle. If you need to make calls more dynamic, simply consider using Collection Routes explained more in detail on the Routing page – Collection Routes allows to pass dynamic variables that were picked out of the URL.

frontend/src/pages/hello-world.js
import React from 'react';
import PropTypes from 'prop-types';
import { graphql } from 'gatsby';
 
const HelloWorld = props => {
  const {
    data: {
      post: {
        title,
        gutenbergBlocks,
      },
    },
  } = props;
 
  return (
    <main>
      <h1>
      {gutenbergBlocks && gutenbergBlocks.nodes && (
        <StatikBlocks blocks={gutenbergBlocks.nodes} />
      )}
    </main>
  );
};
 
HelloWorld.propTypes = {
  PropTypes.shape({
    post: PropTypes.shape({
      title: PropTypes.string,
      gutenbergBlocks: PropTypes.shape({
        nodes: PropTypes.array,
      }),
    }),
  }),
};
 
export default HelloWorld;
 
export const query = graphql`
  query Query {
    post(id: 18) {
      title
      gutenbergBlocks {
        nodes {
          ...GutenbergBlockFragment
        }
      }
    }
  }
`;
 

Fragments

In the example above there's a reference to ...GutenbergBlockFragment, which is a fragment. Fragments are destructed into additional properties to be fetched in the GraphQL call. In other words, during the build phase, the script will consider this code

nodes {
  ...GutenbergBlockFragment
}

as the following

nodes {
  id
  databaseId
  parentId
  parentDatabaseId
  name
  attributes {
    key
    value
  }
  rawHtml
}

It will be possible to reference any of the properties in Gatsby frontend application without actually requesting them in the GraphQL call. Developers are in position to create their own fragments, however some of them are already bundled in the SiteBox Framework. Below you can find a list of fragments that might be helpful in the development:

...PostFragment

id
slug
uri
date
seo {
  node {
    ...NodeSeoFieldFragment
  }
}
featuredImage {
  node {
    ...MediaItemFragment
  }
}
author {
  node {
    name
  }
}
categories {
  nodes {
    slug
    name
  }
}
gutenbergBlocks {
  nodes {
    ...GutenbergBlockFragment
  }
}

...PageFragment

id
slug
uri
seo {
  node {
    ...NodeSeoFieldFragment
  }
}
gutenbergBlocks {
  nodes {
    ...GutenbergBlockFragment
  }
}

...MediaItemFragment

sourceUrl
srcSet
placeholderBase64
mediaDetails {
  height
  width
}
title
altText
id
parentId
databaseId
parentDatabaseId
url
label
cssClasses
order
childItems {
  nodes {
    id
  }
}
megamenu {
  node {
    gutenbergBlocks(first: 1000) {
      nodes {
        ...GutenbergBlockFragment
      }
    }
  }
}

...GutenbergBlockFragment

id
databaseId
parentId
parentDatabaseId
name
attributes {
  key
  value
}
rawHtml
...GutenbergFormsBlockFieldFragment
...GutenbergCoverBlockFieldFragment
...GutenbergCardsBlockFieldFragment
...GutenbergTeamMemberBlockFieldFragment

...NodeSeoFieldFragment

canonicalUrl
description
imageUrl
noarchive
nofollow
noindex
ogDescription
ogTitle
redirectUrl
title
twitterDescription
twitterTitle