DEV3 Technologies home

Custom post type with a linked taxonomy

Register a bookable-room post type with a hierarchical amenity taxonomy, REST support and a safe rewrite flush on theme switch.

WordPressDatabasecpttaxonomyrewriterest

Code

<?php
/**
 * Room post type and Amenity taxonomy.
 *
 * @package Acme_Theme
 */

defined( 'ABSPATH' ) || exit;

function acme_register_room_content(): void {
	register_post_type(
		'acme_room',
		array(
			'labels'       => array(
				'name'          => __( 'Rooms', 'acme-theme' ),
				'singular_name' => __( 'Room', 'acme-theme' ),
				'add_new_item'  => __( 'Add new room', 'acme-theme' ),
				'all_items'     => __( 'All rooms', 'acme-theme' ),
			),
			'public'       => true,
			'has_archive'  => 'rooms',
			'menu_icon'    => 'dashicons-admin-home',
			'show_in_rest' => true, // Block editor + REST.
			'rewrite'      => array( 'slug' => 'rooms', 'with_front' => false ),
			'supports'     => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields' ),
		)
	);

	register_taxonomy(
		'acme_amenity',
		array( 'acme_room' ),
		array(
			'labels'            => array(
				'name'          => __( 'Amenities', 'acme-theme' ),
				'singular_name' => __( 'Amenity', 'acme-theme' ),
			),
			'hierarchical'      => true,
			'show_admin_column' => true,
			'show_in_rest'      => true,
			'rewrite'           => array( 'slug' => 'amenity' ),
		)
	);

	// Expose a numeric nightly rate on the REST response.
	register_post_meta(
		'acme_room',
		'acme_nightly_rate',
		array(
			'type'              => 'number',
			'single'            => true,
			'show_in_rest'      => true,
			'sanitize_callback' => 'floatval',
			'auth_callback'     => static fn(): bool => current_user_can( 'edit_posts' ),
		)
	);
}
add_action( 'init', 'acme_register_room_content' );

// Flush once, after the types exist, when the theme is activated.
function acme_flush_on_activation(): void {
	acme_register_room_content();
	flush_rewrite_rules();
}
add_action( 'after_switch_theme', 'acme_flush_on_activation' );

How it works

What it solves

A theme that lists rooms, services or properties needs its own content type, not pages with copied layouts. This snippet registers a post type and a taxonomy that belongs to it, with block-editor support, a clean archive URL and typed meta exposed to the REST API.

How it works

register_post_type() and register_taxonomy() run on init, the only hook where both are safe. The taxonomy is attached to the post type through its object-type array, so the admin gets a filterable column automatically. register_post_meta() declares the nightly rate with a type, a sanitizer and an auth_callback, which is what lets the block editor and REST clients read and write it without a custom endpoint.

On after_switch_theme the types are registered once more and rewrite rules are flushed, so /rooms/ works immediately after activation.

Adapting it

Change the acme_ prefix, the labels and the rooms slug. Add 'show_in_nav_menus' => false for internal types. Hierarchical taxonomies behave like categories (checkbox list); set hierarchical to false for a tag-like input. If the types should survive a theme change, move this file into a small plugin and flush on plugin activation instead.

Pitfalls

  • Never call flush_rewrite_rules() on every request; it is expensive. Activation is enough.
  • Post type keys are limited to 20 characters and should be prefixed to avoid clashes.
  • Without show_in_rest the block editor falls back to the classic editor for that type.
  • After renaming the slug, visit Settings, Permalinks and save once.

How to use it

Save as inc/post-types.php and load it from functions.php with require get_template_directory() . '/inc/post-types.php';.

Activate the theme (or save permalinks) once, then add rooms from the new admin menu.

Query them anywhere with new WP_Query( array( 'post_type' => 'acme_room' ) ), or filter by tax_query on acme_amenity.

Licence: GPL-2.0-or-later. This snippet comes from WordPress code and is GPL-2.0-or-later.

Related snippets