iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Custom Post Types

Custom Post Types let you model entities beyond posts and pages — products, events, team members, properties. Register one with register_post_type and the WP admin gets a full CRUD UI for free.

Register, taxonomies, meta, REST

EXAMPLE
<?php
// 1) Register a CPT
add_action('init', function () {
    register_post_type('product', [
        'label'          => 'Products',
        'public'         => true,
        'menu_position'  => 5,
        'menu_icon'      => 'dashicons-cart',
        'supports'       => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
        'has_archive'    => true,
        'rewrite'        => ['slug' => 'products', 'with_front' => false],
        'show_in_rest'   => true,         // Gutenberg + REST API
        'capability_type'=> 'post',
        'labels'         => [
            'name'          => 'Products',
            'singular_name' => 'Product',
            'add_new_item'  => 'Add New Product',
            'edit_item'     => 'Edit Product',
            'view_item'     => 'View Product',
        ],
    ]);

    // 2) Custom taxonomy — like categories, but for your CPT
    register_taxonomy('product_category', 'product', [
        'hierarchical'  => true,                 // like categories (true) or tags (false)
        'label'         => 'Product Categories',
        'rewrite'       => ['slug' => 'product-cat'],
        'show_in_rest'  => true,
    ]);

    register_taxonomy('product_tag', 'product', [
        'hierarchical'  => false,
        'label'         => 'Product Tags',
        'show_in_rest'  => true,
    ]);
});

// 3) Flush rewrite rules — register at activation, flush there
register_activation_hook(__FILE__, function () {
    flush_rewrite_rules();
});
register_deactivation_hook(__FILE__, 'flush_rewrite_rules');

// 4) Add meta fields (price, SKU)
add_action('init', function () {
    register_post_meta('product', 'price', [
        'type'              => 'number',
        'single'            => true,
        'show_in_rest'      => true,
        'sanitize_callback' => 'floatval',
        'auth_callback'     => fn() => current_user_can('edit_posts'),
    ]);
    register_post_meta('product', 'sku', [
        'type'              => 'string',
        'single'            => true,
        'show_in_rest'      => true,
        'sanitize_callback' => 'sanitize_text_field',
    ]);
});

// 5) Query CPTs
$query = new WP_Query([
    'post_type'      => 'product',
    'posts_per_page' => 12,
    'meta_query'     => [
        ['key' => 'price', 'value' => 100, 'compare' => '>=', 'type' => 'NUMERIC'],
    ],
    'tax_query' => [
        ['taxonomy' => 'product_category', 'field' => 'slug', 'terms' => 'gadgets'],
    ],
]);

while ($query->have_posts()) {
    $query->the_post();
    $price = get_post_meta(get_the_ID(), 'price', true);
    the_title('<h2>', '</h2>');
    echo '<p class="price">$' . number_format((float) $price, 2) . '</p>';
}
wp_reset_postdata();

// 6) Single-product template — single-product.php
// Theme falls back: single-product.php → single.php → index.php

// 7) Add a custom column to the admin list
add_filter('manage_product_posts_columns', function ($cols) {
    $cols['price'] = 'Price';
    $cols['sku']   = 'SKU';
    return $cols;
});

add_action('manage_product_posts_custom_column', function ($col, $post_id) {
    if ($col === 'price') echo '$' . number_format((float) get_post_meta($post_id, 'price', true), 2);
    if ($col === 'sku')   echo esc_html(get_post_meta($post_id, 'sku', true));
}, 10, 2);

// 8) REST API auto-exposed when show_in_rest = true
// GET /wp-json/wp/v2/product/?per_page=20
// GET /wp-json/wp/v2/product/123
// POST /wp-json/wp/v2/product   (with auth)

// 9) Best practices
//   • Always pass show_in_rest => true unless you have a strong reason not to
//   • Use a unique slug (longer / namespaced) to avoid plugin conflicts: 'mycorp_product'
//   • Use register_post_meta with auth_callback to gate edits
//   • Don't register CPTs inside a theme — move into a plugin (CPTs survive theme switches)

Why it matters

Custom Post Types + show_in_rest give you a typed admin UI and a REST API for free. Build the model once; theme renders + headless apps + Gutenberg blocks all read from the same source.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
register_post_type('book', [
    'public' => true,
    'label'  => 'Books',
    'supports' => ['title', 'editor', 'thumbnail'],
]);
Try it Yourself »

Exercise

Register a CPT with…

('product', $args);

Discussion

Loading…