Advertisement
[Responsive Ad - 728x90]
#CustomPostTypes #Taxonomies #MetaFields #PHP8.3 WordPress 6.9

📦 WordPress 6.9 Custom Post Types & Taxonomies: Complete Guide 2026

Master custom post types in WordPress 6.9 with PHP 8.3. Create portfolios, products, listings, and complex content structures with advanced meta queries. 25+ examples with production-ready code.

25+
Code Examples
10+
CPT Examples
100%
PHP 8.3 Ready

Blogs Team

WordPress Developers • 2026 Edition

🔰 Custom Post Type Fundamentals (Tips 1-5)

1

Basic CPT Registration (Portfolio Example)

// functions.php
add_action('init', function() {
    $labels = [
        'name' => 'Portfolio',
        'singular_name' => 'Portfolio Item',
        'add_new' => 'Add New Item',
        'add_new_item' => 'Add New Portfolio Item',
        'edit_item' => 'Edit Portfolio Item',
        'new_item' => 'New Portfolio Item',
        'view_item' => 'View Portfolio Item',
        'search_items' => 'Search Portfolio',
        'not_found' => 'No portfolio items found',
        'not_found_in_trash' => 'No portfolio items found in trash',
        'menu_name' => 'Portfolio',
    ];

    $args = [
        'labels' => $labels,
        'public' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'query_var' => true,
        'rewrite' => ['slug' => 'portfolio'],
        'capability_type' => 'post',
        'has_archive' => true,
        'hierarchical' => false,
        'menu_position' => 5,
        'menu_icon' => 'dashicons-portfolio',
        'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
        'show_in_rest' => true, // Enable Gutenberg
    ];

    register_post_type('portfolio', $args);
});
2

Naming Conventions Best Practices

// Good examples
register_post_type('portfolio_item');     // ✓
register_post_type('team_member');        // ✓
register_post_type('job_listing');        // ✓

// Bad examples
register_post_type('portfolio-item');     // ✗ (hyphens)
register_post_type('Portfolio');          // ✗ (uppercase)
register_post_type('a_very_long_name');   // ✗ (>20 chars)
register_post_type('post');               // ✗ (reserved)
3

Complete CPT Registration with All Parameters

add_action('init', function() {
    register_post_type('product', [
        'labels' => ['name' => 'Products', 'singular_name' => 'Product'],
        'public' => true,
        'hierarchical' => false,
        'exclude_from_search' => false,
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'show_in_nav_menus' => true,
        'show_in_admin_bar' => true,
        'show_in_rest' => true,
        'rest_base' => 'products',
        'menu_position' => 5,
        'menu_icon' => 'dashicons-cart',
        'capability_type' => 'post',
        'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
        'taxonomies' => ['category'],
        'has_archive' => true,
        'rewrite' => ['slug' => 'shop'],
        'query_var' => 'product',
        'can_export' => true,
    ]);
});
4

PHP 8.3 Named Arguments for CPT Registration

// PHP 8.3 - Using named arguments
add_action('init', function() {
    register_post_type(
        post_type: 'event',
        args: [
            'labels' => ['name' => 'Events', 'singular_name' => 'Event'],
            'public' => true,
            'has_archive' => true,
            'rewrite' => ['slug' => 'events'],
            'supports' => ['title', 'editor', 'thumbnail'],
            'show_in_rest' => true,
            'menu_icon' => 'dashicons-calendar',
        ]
    );
});
5

Template Hierarchy for CPTs

// Single CPT: single-{post_type}.php
// Example: single-portfolio.php

// Archive: archive-{post_type}.php
// Example: archive-portfolio.php

// Taxonomy: taxonomy-{taxonomy}-{term}.php
// Example: taxonomy-project_type-design.php

// Taxonomy: taxonomy-{taxonomy}.php
// Example: taxonomy-project_type.php

// Example single-portfolio.php
get_header();
while (have_posts()) : the_post();
    $client = get_post_meta(get_the_ID(), 'project_client', true);
    ?>
    <article id="post-<?php the_ID(); ?>">
        <h1><?php the_title(); ?></h1>
        <?php if ($client): ?>
            <p>Client: <?php echo esc_html($client); ?></p>
        <?php endif; ?>
        <?php the_content(); ?>
    </article>
<?php
endwhile;
get_footer();
Advertisement
[Responsive Medium Rectangle - 300x250]

🏷️ Creating Custom Taxonomies (Tips 6-10)

6

Registering a Custom Taxonomy

add_action('init', function() {
    $labels = [
        'name' => 'Genres',
        'singular_name' => 'Genre',
        'search_items' => 'Search Genres',
        'all_items' => 'All Genres',
        'parent_item' => 'Parent Genre',
        'edit_item' => 'Edit Genre',
        'update_item' => 'Update Genre',
        'add_new_item' => 'Add New Genre',
        'new_item_name' => 'New Genre Name',
        'menu_name' => 'Genres',
    ];

    register_taxonomy(
        taxonomy: 'genre',
        object_type: ['book'],
        args: [
            'labels' => $labels,
            'hierarchical' => true,
            'public' => true,
            'show_ui' => true,
            'show_admin_column' => true,
            'show_in_nav_menus' => true,
            'show_in_rest' => true,
            'rest_base' => 'genres',
            'query_var' => 'genre',
            'rewrite' => ['slug' => 'genre'],
        ]
    );
});
7

Hierarchical vs Non-Hierarchical Taxonomies

// Hierarchical (like categories)
register_taxonomy('location', 'property', [
    'hierarchical' => true,
    // Parent-child relationships
    // Dropdown UI in admin
]);

// Non-Hierarchical (like tags)
register_taxonomy('amenities', 'property', [
    'hierarchical' => false,
    // Flat structure
    // Tag input UI
]);
8

Attaching Taxonomies to Multiple Post Types

// During taxonomy registration
register_taxonomy(
    'feature',
    ['post', 'page', 'portfolio_item'], // Multiple post types
    [
        'labels' => ['name' => 'Features'],
        'hierarchical' => false,
        'show_in_rest' => true,
    ]
);

// After registration
register_taxonomy('department', ['employee'], [
    'labels' => ['name' => 'Departments'],
    'hierarchical' => true,
]);

// Also attach to 'project' post type
register_taxonomy_for_object_type('department', 'project');
9

Get Terms with Counts

// Get all terms with post counts
$terms = get_terms([
    'taxonomy' => 'genre',
    'hide_empty' => false, // Include empty terms
]);

// Get terms sorted by count
$popular_terms = get_terms([
    'taxonomy' => 'genre',
    'orderby' => 'count',
    'order' => 'DESC',
    'number' => 10,
]);

// Display as dropdown
wp_dropdown_categories([
    'taxonomy' => 'genre',
    'show_option_none' => 'Select Genre',
    'option_none_value' => '',
    'name' => 'genre',
]);
10

Add Custom Fields to Taxonomies

// Add fields to taxonomy term form
add_action('genre_add_form_fields', function() {
    ?>
    <div class="form-field">
        <label for="genre_color">Color</label>
        <input type="color" name="genre_color" id="genre_color">
        <p class="description">Choose a color for this genre</p>
    </div>
    <?php
});

// Save custom field
add_action('created_genre', function($term_id) {
    if (isset($_POST['genre_color'])) {
        update_term_meta($term_id, 'color', sanitize_hex_color($_POST['genre_color']));
    }
});

// Display in admin edit form
add_action('genre_edit_form_fields', function($term) {
    $color = get_term_meta($term->term_id, 'color', true);
    ?>
    <tr class="form-field">
        <th><label for="genre_color">Color</label></th>
        <td>
            <input type="color" name="genre_color" id="genre_color" value="<?php echo esc_attr($color); ?>">
        </td>
    </tr>
    <?php
});

📊 Meta Boxes & Custom Fields (Tips 11-15)

11

Adding Meta Boxes

add_action('add_meta_boxes', function() {
    add_meta_box(
        id: 'property_details',
        title: 'Property Details',
        callback: 'render_property_details_meta_box',
        screen: 'property',
        context: 'normal',
        priority: 'high'
    );
});

function render_property_details_meta_box($post) {
    wp_nonce_field('save_property_details', 'property_details_nonce');
    
    $price = get_post_meta($post->ID, 'property_price', true);
    $bedrooms = get_post_meta($post->ID, 'property_bedrooms', true);
    ?>
    <p>
        <label for="property_price">Price ($):</label><br>
        <input type="number" id="property_price" name="property_price" value="<?php echo esc_attr($price); ?>" class="widefat">
    </p>
    <p>
        <label for="property_bedrooms">Bedrooms:</label><br>
        <input type="number" id="property_bedrooms" name="property_bedrooms" value="<?php echo esc_attr($bedrooms); ?>" class="widefat">
    </p>
    <?php
}
12

Saving Meta Box Data

add_action('save_post_property', function($post_id, $post, $update) {
    // Security checks
    if (!isset($_POST['property_details_nonce']) || 
        !wp_verify_nonce($_POST['property_details_nonce'], 'save_property_details')) {
        return;
    }
    
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    
    if (!current_user_can('edit_post', $post_id)) {
        return;
    }
    
    // Save fields
    $fields = [
        'property_price' => 'floatval',
        'property_bedrooms' => 'intval',
    ];
    
    foreach ($fields as $field => $sanitizer) {
        if (isset($_POST[$field])) {
            $value = $sanitizer($_POST[$field]);
            update_post_meta($post_id, $field, $value);
        }
    }
}, 10, 3);
13

Different Field Types in Meta Boxes

// Text field
<input type="text" name="text_field" value="<?php echo esc_attr($value); ?>">

// Textarea
<textarea name="textarea_field" rows="5"><?php echo esc_textarea($value); ?></textarea>

// Select dropdown
<select name="select_field">
    <option value="option1" <?php selected($value, 'option1'); ?>>Option 1</option>
    <option value="option2" <?php selected($value, 'option2'); ?>>Option 2</option>
</select>

// Checkbox
<input type="checkbox" name="checkbox_field" value="1" <?php checked($value, 1); ?>>

// Radio buttons
<input type="radio" name="radio_field" value="yes" <?php checked($value, 'yes'); ?>> Yes
<input type="radio" name="radio_field" value="no" <?php checked($value, 'no'); ?>> No

// Date picker
<input type="date" name="date_field" value="<?php echo esc_attr($value); ?>">

// Color picker
<input type="color" name="color_field" value="<?php echo esc_attr($value); ?>">
14

Custom Columns in Admin List

// Add custom columns
add_filter('manage_portfolio_posts_columns', function($columns) {
    $columns['featured_image'] = 'Image';
    $columns['client'] = 'Client';
    $columns['year'] = 'Year';
    return $columns;
});

// Populate custom columns
add_action('manage_portfolio_posts_custom_column', function($column, $post_id) {
    switch ($column) {
        case 'featured_image':
            if (has_post_thumbnail($post_id)) {
                echo get_the_post_thumbnail($post_id, [50, 50]);
            }
            break;
            
        case 'client':
            echo esc_html(get_post_meta($post_id, 'project_client', true));
            break;
            
        case 'year':
            echo esc_html(get_post_meta($post_id, 'project_year', true));
            break;
    }
}, 10, 2);

// Make columns sortable
add_filter('manage_edit-portfolio_sortable_columns', function($columns) {
    $columns['client'] = 'client';
    $columns['year'] = 'year';
    return $columns;
});
15

Quick Edit & Bulk Edit Support

// Add to quick edit
add_action('quick_edit_custom_box', function($column_name, $post_type) {
    if ($post_type !== 'portfolio') return;
    
    if ($column_name === 'client') {
        ?>
        <fieldset class="inline-edit-col-right">
            <div class="inline-edit-col">
                <label>
                    <span class="title">Client</span>
                    <input type="text" name="project_client" value="">
                </label>
            </div>
        </fieldset>
        <?php
    }
}, 10, 2);

// Save quick edit
add_action('save_post_portfolio', function($post_id) {
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if (!current_user_can('edit_post', $post_id)) return;
    
    if (isset($_POST['project_client'])) {
        update_post_meta($post_id, 'project_client', sanitize_text_field($_POST['project_client']));
    }
});
Advertisement
[Responsive Leaderboard]

⚡ Advanced Meta Queries (Tips 16-20)

16

Basic Meta Query Examples

// Simple meta query
$properties = get_posts([
    'post_type' => 'property',
    'meta_key' => 'price',
    'meta_value' => 500000,
    'meta_compare' => '>'
]);

// Multiple conditions (AND)
$properties = get_posts([
    'post_type' => 'property',
    'meta_query' => [
        [
            'key' => 'price',
            'value' => 500000,
            'compare' => '>=',
            'type' => 'NUMERIC'
        ],
        [
            'key' => 'bedrooms',
            'value' => 3,
            'compare' => '>=',
            'type' => 'NUMERIC'
        ]
    ]
]);

// OR relationship
$properties = get_posts([
    'post_type' => 'property',
    'meta_query' => [
        'relation' => 'OR',
        [
            'key' => 'featured',
            'value' => 'yes',
            'compare' => '='
        ],
        [
            'key' => 'price',
            'value' => [200000, 400000],
            'type' => 'NUMERIC',
            'compare' => 'BETWEEN'
        ]
    ]
]);
17

Complex Nested Meta Queries

$properties = get_posts([
    'post_type' => 'property',
    'posts_per_page' => 10,
    'meta_query' => [
        'relation' => 'AND',
        // Price condition
        [
            'key' => 'price',
            'value' => [300000, 800000],
            'type' => 'NUMERIC',
            'compare' => 'BETWEEN'
        ],
        // Location conditions (OR)
        [
            'relation' => 'OR',
            [
                'key' => 'city',
                'value' => 'Manhattan',
                'compare' => '='
            ],
            [
                'key' => 'city',
                'value' => 'Brooklyn',
                'compare' => '='
            ]
        ],
        // Features conditions (AND)
        [
            'relation' => 'AND',
            [
                'key' => 'bedrooms',
                'value' => 2,
                'type' => 'NUMERIC',
                'compare' => '>='
            ],
            [
                'key' => 'bathrooms',
                'value' => 1,
                'type' => 'NUMERIC',
                'compare' => '>='
            ]
        ]
    ]
]);
18

Meta Query with EXISTS/NOT EXISTS

// Find posts with a specific meta key
$posts_with_gallery = get_posts([
    'post_type' => 'portfolio',
    'meta_query' => [
        [
            'key' => 'gallery_images',
            'compare' => 'EXISTS'
        ]
    ]
]);

// Find posts without a specific meta key
$posts_without_price = get_posts([
    'post_type' => 'product',
    'meta_query' => [
        [
            'key' => 'price',
            'compare' => 'NOT EXISTS'
        ]
    ]
]);

// Combine with other conditions
$featured_without_image = get_posts([
    'post_type' => 'portfolio',
    'meta_query' => [
        'relation' => 'AND',
        [
            'key' => 'featured',
            'value' => 'yes',
            'compare' => '='
        ],
        [
            'key' => '_thumbnail_id',
            'compare' => 'NOT EXISTS'
        ]
    ]
]);
19

Meta Query with Date Comparisons

// Events in next 30 days
$upcoming_events = get_posts([
    'post_type' => 'event',
    'posts_per_page' => 10,
    'meta_key' => 'event_date',
    'orderby' => 'meta_value',
    'order' => 'ASC',
    'meta_query' => [
        [
            'key' => 'event_date',
            'value' => date('Y-m-d'),
            'compare' => '>=',
            'type' => 'DATE'
        ],
        [
            'key' => 'event_date',
            'value' => date('Y-m-d', strtotime('+30 days')),
            'compare' => '<=',
            'type' => 'DATE'
        ]
    ]
]);

// Events in specific month
$month_events = get_posts([
    'post_type' => 'event',
    'meta_query' => [
        [
            'key' => 'event_date',
            'value' => ['2026-03-01', '2026-03-31'],
            'compare' => 'BETWEEN',
            'type' => 'DATE'
        ]
    ]
]);
20

Optimize CPT Queries

// Bad - loads all data
$posts = get_posts([
    'post_type' => 'portfolio',
    'posts_per_page' => -1
]);

// Good - only needed fields
$posts = get_posts([
    'post_type' => 'portfolio',
    'posts_per_page' => -1,
    'fields' => 'ids',  // Only get IDs
    'no_found_rows' => true,  // Skip pagination count
    'update_post_meta_cache' => false,  // Skip meta cache
    'update_post_term_cache' => false,  // Skip term cache
]);

// Cache expensive meta queries
function get_featured_projects() {
    $cache_key = 'featured_projects';
    $cached = wp_cache_get($cache_key, 'portfolio');
    
    if ($cached !== false) {
        return $cached;
    }
    
    $results = get_posts([
        'post_type' => 'portfolio',
        'meta_key' => 'featured',
        'meta_value' => 'yes',
        'posts_per_page' => 10
    ]);
    
    wp_cache_set($cache_key, $results, 'portfolio', HOUR_IN_SECONDS);
    
    return $results;
}
Advertisement
[Responsive Large Rectangle]

💡 Real-World CPT Examples (Tips 21-25)

21

Real Estate Listings CPT

// Register Property CPT
add_action('init', function() {
    register_post_type('property', [
        'labels' => [
            'name' => 'Properties',
            'singular_name' => 'Property',
        ],
        'public' => true,
        'has_archive' => true,
        'rewrite' => ['slug' => 'properties'],
        'supports' => ['title', 'editor', 'thumbnail'],
        'show_in_rest' => true,
    ]);
    
    register_taxonomy('property_type', 'property', [
        'labels' => ['name' => 'Property Types'],
        'hierarchical' => true,
        'show_in_rest' => true,
    ]);
});

// Property meta fields
add_action('add_meta_boxes', function() {
    add_meta_box('property_details', 'Property Details', 'render_property_details', 'property');
});

function render_property_details($post) {
    $fields = [
        'price' => 'Price ($)',
        'bedrooms' => 'Bedrooms',
        'bathrooms' => 'Bathrooms',
        'area' => 'Area (sq ft)',
        'year_built' => 'Year Built',
    ];
    
    foreach ($fields as $key => $label) {
        $value = get_post_meta($post->ID, 'property_' . $key, true);
        ?>
        <p>
            <label for="property_<?php echo $key; ?>"><?php echo $label; ?>:</label><br>
            <input type="text" id="property_<?php echo $key; ?>" 
                   name="property_<?php echo $key; ?>" 
                   value="<?php echo esc_attr($value); ?>" 
                   class="widefat">
        </p>
        <?php
    }
}
22

Events Calendar CPT

// Register Event CPT
add_action('init', function() {
    register_post_type('event', [
        'labels' => [
            'name' => 'Events',
            'singular_name' => 'Event',
        ],
        'public' => true,
        'has_archive' => true,
        'rewrite' => ['slug' => 'events'],
        'supports' => ['title', 'editor', 'thumbnail'],
        'show_in_rest' => true,
    ]);
});

// Event date handling
add_action('save_post_event', function($post_id) {
    if (isset($_POST['event_start_date'])) {
        update_post_meta($post_id, 'event_start_date', sanitize_text_field($_POST['event_start_date']));
    }
    if (isset($_POST['event_end_date'])) {
        update_post_meta($post_id, 'event_end_date', sanitize_text_field($_POST['event_end_date']));
    }
});

// Query upcoming events
function get_upcoming_events($count = 5) {
    $today = current_time('Y-m-d H:i:s');
    
    return get_posts([
        'post_type' => 'event',
        'posts_per_page' => $count,
        'meta_key' => 'event_start_date',
        'orderby' => 'meta_value',
        'order' => 'ASC',
        'meta_query' => [
            [
                'key' => 'event_start_date',
                'value' => $today,
                'compare' => '>=',
                'type' => 'DATETIME'
            ]
        ]
    ]);
}
23

Job Listings CPT

// Register Job CPT
register_post_type('job', [
    'labels' => [
        'name' => 'Jobs',
        'singular_name' => 'Job',
    ],
    'public' => true,
    'has_archive' => true,
    'rewrite' => ['slug' => 'careers'],
    'supports' => ['title', 'editor', 'excerpt'],
]);

// Job meta fields
$job_fields = ['location', 'type', 'salary', 'department'];
foreach ($job_fields as $field) {
    add_action('add_meta_boxes', function() use ($field) {
        add_meta_box(
            'job_' . $field,
            ucfirst($field),
            function($post) use ($field) {
                $value = get_post_meta($post->ID, 'job_' . $field, true);
                ?>
                <input type="text" name="job_<?php echo $field; ?>" 
                       value="<?php echo esc_attr($value); ?>" class="widefat">
                <?php
            },
            'job'
        );
    });
}

// Query open jobs
$open_jobs = get_posts([
    'post_type' => 'job',
    'meta_query' => [
        [
            'key' => 'job_status',
            'value' => 'open',
            'compare' => '='
        ]
    ]
]);
24

Team Members CPT

register_post_type('team_member', [
    'labels' => [
        'name' => 'Team Members',
        'singular_name' => 'Team Member',
        'add_new' => 'Add Member',
    ],
    'public' => true,
    'supports' => ['title', 'editor', 'thumbnail'],
    'menu_icon' => 'dashicons-groups',
]);

// Team member meta
add_action('add_meta_boxes', function() {
    add_meta_box('team_details', 'Member Details', function($post) {
        $position = get_post_meta($post->ID, 'position', true);
        $email = get_post_meta($post->ID, 'email', true);
        $phone = get_post_meta($post->ID, 'phone', true);
        $linkedin = get_post_meta($post->ID, 'linkedin', true);
        ?>
        <p>
            <label>Position:<br>
                <input type="text" name="position" value="<?php echo esc_attr($position); ?>" class="widefat">
            </label>
        </p>
        <p>
            <label>Email:<br>
                <input type="email" name="email" value="<?php echo esc_attr($email); ?>" class="widefat">
            </label>
        </p>
        <p>
            <label>Phone:<br>
                <input type="text" name="phone" value="<?php echo esc_attr($phone); ?>" class="widefat">
            </label>
        </p>
        <p>
            <label>LinkedIn:<br>
                <input type="url" name="linkedin" value="<?php echo esc_attr($linkedin); ?>" class="widefat">
            </label>
        </p>
        <?php
    }, 'team_member');
});
25

Testimonials CPT with Rating

register_post_type('testimonial', [
    'labels' => [
        'name' => 'Testimonials',
        'singular_name' => 'Testimonial',
    ],
    'public' => true,
    'supports' => ['title', 'editor'],
    'menu_icon' => 'dashicons-testimonial',
]);

// Testimonial meta
add_action('add_meta_boxes', function() {
    add_meta_box('testimonial_details', 'Testimonial Details', function($post) {
        $client = get_post_meta($post->ID, 'client_name', true);
        $company = get_post_meta($post->ID, 'client_company', true);
        $rating = get_post_meta($post->ID, 'rating', true);
        ?>
        <p>
            <label>Client Name:<br>
                <input type="text" name="client_name" value="<?php echo esc_attr($client); ?>" class="widefat">
            </label>
        </p>
        <p>
            <label>Company:<br>
                <input type="text" name="client_company" value="<?php echo esc_attr($company); ?>" class="widefat">
            </label>
        </p>
        <p>
            <label>Rating (1-5):<br>
                <select name="rating">
                    <?php for ($i = 1; $i <= 5; $i++): ?>
                        <option value="<?php echo $i; ?>" <?php selected($rating, $i); ?>>
                            <?php echo $i; ?> Stars
                        </option>
                    <?php endfor; ?>
                </select>
            </label>
        </p>
        <?php
    }, 'testimonial');
});

// Display testimonials
function display_testimonials($count = 5) {
    $testimonials = get_posts([
        'post_type' => 'testimonial',
        'posts_per_page' => $count,
        'meta_key' => 'rating',
        'orderby' => 'meta_value_num',
        'order' => 'DESC'
    ]);
    
    foreach ($testimonials as $testimonial) {
        $rating = get_post_meta($testimonial->ID, 'rating', true);
        $client = get_post_meta($testimonial->ID, 'client_name', true);
        ?>
        <div class="testimonial">
            <div class="rating">⭐ x <?php echo $rating; ?></div>
            <p><?php echo $testimonial->post_content; ?></p>
            <p>- <?php echo esc_html($client); ?></p>
        </div>
        <?php
    }
}
Advertisement
[Responsive Large Rectangle]

✅ CPT Development Checklist

  • ✓ Unique post type name (<20 chars)
  • ✓ Proper labels defined
  • ✓ Show in REST enabled
  • ✓ Rewrite rules set
  • ✓ Template files created
  • ✓ Taxonomies registered
  • ✓ Meta boxes added
  • ✓ Custom columns in admin
  • ✓ Queries optimized
  • ✓ Capabilities set

📥 Download PDF Checklist

❓ CPT FAQ

When to use CPT vs taxonomy?

Use CPT for distinct content types (products, events). Use taxonomy for grouping existing content (categories, tags).

How to flush rewrite rules?

Go to Settings → Permalinks and click Save. Or call flush_rewrite_rules() once after registration.

📬 Get More WordPress Development Tips

Weekly tutorials on CPTs, taxonomies, and advanced WordPress development.

📢 Share this CPT guide with fellow developers