Skip To Main Content

Setting up Custom Post Types (CPTs) in Faust

This guide covers how to set up Custom Post Types in your Faust templates.

Note: This guide assumes you have a working Faust app using Faust templates. If you don’t have a Faust site created yet, please follow the Getting Started guide first.

Before You Start

It’s important that your CPT is registered with some specific fields and values:

  • public: Must be true. Otherwise the posts can’t be resolved in Faust.
  • has_archive: Should be true. This is so that you can create an archive (i.e. /movies) in your Faust app.
  • show_in_graphql: Must be true so the data can be queried from WPGraphQL.

For example, here is the CPT that was registered for this tutorial:

function register_movies_cpt() {
    $args = [
        'label' => esc_html__( 'Movies', 'twentytwentyone' ),
        'public' => true, // If the CPT is not public, it can not be properly resolved in Faust.
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_rest' => true,
        'rest_base' => '',
        'rest_controller_class' => 'WP_REST_Posts_Controller',
        'rest_namespace' => 'wp/v2',
        'has_archive' => true, // Important for creating an archive in Faust.
        'show_in_menu' => true,
        'show_in_nav_menus' => true,
        'delete_with_user' => false,
        'exclude_from_search' => false,
        'capability_type' => 'post',
        'map_meta_cap' => true,
        'hierarchical' => false,
        'can_export' => false,
        'rewrite' => [ 'slug' => 'movies', 'with_front' => true ],
        'query_var' => true,
        'supports' => [ 'title', 'editor', 'thumbnail' ],
        'show_in_graphql' => true,
        'graphql_single_name' => 'Movie',
        'graphql_plural_name' => 'Movies',
    ];

    register_post_type( 'movies', $args );
}

add_action( 'init', 'register_movies_cpt' );
Code language: PHP (php)

For this guide, we’ll assume you have a Custom Post Type with a slug of movies.

Verify Your CPT Was Setup Properly

Before we start creating templates in Faust, let’s first verify your CPT is setup properly and accessible in WPGraphQL.

Let’s create a post title “The Dark Knight” in our movies CPT:

Creating a post in WordPress with the title The Dark Knight

Then, let’s visit the page on WordPress to make sure it’s public:

A WordPress Page for The Dark Knight

Great! It is publicly available. Now let’s make sure we can access the movie and the archive in WPGraphQL. You can use the following query to do so:

<em>query</em> GetMovieByUri(<em>$uri</em>: String!) {
  nodeByUri(uri: <em>$uri</em>) {
    ... <em>on</em> NodeWithTitle {
      title
    }
    ... <em>on</em> NodeWithContentEditor {
      content
    }
  }
}
Code language: HTML, XML (xml)

And don’t forget the query variables:

{
  "uri": "movies/the-dark-knight"
}
Code language: JSON / JSON with Comments (json)
A WPGraphQL query being executed for getting a movie by uri

We can see we are getting the title and content back properly.

Finally, we need to check if the archive is accessible:

query ArchiveMovies($uri: String!) {
  nodeByUri(uri: $uri) {
    __typename
    ... on ContentType {
      label
    }
  }
}
Code language: PHP (php)

And the query variables:

{
  "uri": "/movies"
}Code language: JSON / JSON with Comments (json)
A WPGraphQL query being executed for getting a movie archive by uri

We’ve just confirmed that our CPT is available in WPGraphQL. Now, let’s move to the Faust app!

Generate Possible Types

Before we start creating templates, we need to generate possible types for Apollo. These possible types tell Apollo what is available in your schema, and how it caches data. Since we added a new CPT, the possible types have changed, so a regeneration is required.

You can do this by running the faust generatePossibleTypes script. In the getting started project, this is mapped to npm run generate:

A screenshot of VS Code running the Faust generatePossibleTypes script

Create Faust Templates

With possible types generated, we can now start creating Faust templates for our CPTs!

Before we begin, start the dev server in the Faust app:

npm run dev

Single Movie Template

Let’s get started by building our “single movie” template. If you navigate to http://localhost:3000/movies/the-dark-knight you will get a 404. But upon inspecting the server output, you will see the possible templates for the route:

A screenshot of the output from Faust describing the possible templates for the route

You can see using any of the following templates will resolve for this route:

['single-movies-the-dark-knight', 'single-movies', 'singular', 'index'];Code language: CSS (css)

Since we want this template to be just for any single movie, we’ll create a template called single-movies. In your wp-templates directory, create a file called single-movies.js with the following content:

import { gql } from '@apollo/client';

export default function SingleMovie(props) {
  const { title, content } = props.data.nodeByUri;

  return (
    <>
      <h1>{title}</h1>
      <div dangerouslySetInnerHTML={{ __html: content }} />
    </>
  );
}

SingleMovie.variables = ({ uri }) => {
  return { uri };
};

SingleMovie.query = gql`
  query GetMovieByUri($uri: String!) {
    nodeByUri(uri: $uri) {
      ... on NodeWithTitle {
        title
      }
      ... on NodeWithContentEditor {
        content
      }
    }
  }
`;
Code language: JavaScript (javascript)

Note: Notice we are using the same query from WPGraphQL. You can test your queries in WPGraphQL IDE and copy and paste them into Faust.

Now that we have created our template, we need to register it by adding it to the object in wp-templates/index.js:

// wp-templates/index.js

import category from './category';
import tag from './tag';
import frontPage from './front-page';
import page from './page';
import single from './single';
import SingleMovie from './single-movies';

export default {
  category,
  tag,
  'front-page': frontPage,
  page,
  single,
  'single-movies': SingleMovie,
};
Code language: JavaScript (javascript)

Our single movie template has now been registered! If you visit http://localhost:3000/movies/the-dark-knight again you can see the template properly resolves:

The finished Faust template for the single movie being resolved in the browser

Archive Movie Template

With our single movie template created, let’s create our template for the movie archive next.

If you navigate to http://localhost:3000/movies you will get a 404. Inspect the server output, and you will see the possible templates for the route:

A screenshot of the output from Faust describing the possible templates for the route

You can see using any of the following templates will resolve for this route:

['archive-movies', 'archive', 'index'];Code language: CSS (css)

Since we want this template to be just for our movie archive, we’ll create a template called archive-movies. In your wp-templates directory, create a file called archive-movies.js with the following content:

import { gql } from '@apollo/client';
import Link from 'next/link';

export default function ArchiveMovies(props) {
  const { label, contentNodes } = props.data.nodeByUri;

  return (
    <>
      <h1>{label} Archive</h1>

      <ul>
        {contentNodes.nodes.map((node) => (
          <li>
            <Link href={node.uri}>{node.title}</Link>
          </li>
        ))}
      </ul>
    </>
  );
}

ArchiveMovies.variables = ({ uri }) => {
  return { uri };
};

ArchiveMovies.query = gql`
  query MovieArchive($uri: String!) {
    nodeByUri(uri: $uri) {
      ... on ContentType {
        label
        description
        contentNodes {
          nodes {
            databaseId
            uri
            ... on NodeWithTitle {
              title
            }
          }
        }
      }
    }
  }
`;
Code language: JavaScript (javascript)

Don’t forget to register your new archive template in the wp-templates/index.js file:

// wp-templates/index.js

import category from './category';
import tag from './tag';
import frontPage from './front-page';
import page from './page';
import single from './single';
import SingleMovie from './single-movies';
import ArchiveMovies from './archive-movies';

export default {
  category,
  tag,
  'front-page': frontPage,
  page,
  single,
  'single-movies': SingleMovie,
  'archive-movies': ArchiveMovies,
};
Code language: JavaScript (javascript)

Our archive movie template has now been registered! If you visit http://localhost:3000/movies again you can see the template properly resolves:

The finished Faust template for the movie archive being resolved in the browser

You now have two new Faust templates resolving for your Movies custom post type!