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 betrue
. Otherwise the posts can’t be resolved in Faust.has_archive
: Should betrue
. This is so that you can create an archive (i.e./movies
) in your Faust app.show_in_graphql
: Must betrue
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:
Then, let’s visit the page on WordPress to make sure it’s public:
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)
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)
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
:
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:
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:
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:
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:
You now have two new Faust templates resolving for your Movies custom post type!