WordPress Custom Post Type code on a monitor in VS Code.

How to Code WordPress Custom Post Types Without Plugin

Introduction: Why Code Beats the Plugin Bloat

Welcome to the ultimate guide for taking control of your WordPress site!

If you’ve been building WordPress sites for a while, you know that plugins like CPT UI or ACF are great—but they come at a cost. Every plugin adds overhead, slows your site down, and creates a dependency you can’t easily escape.

As developers, we want a faster, cleaner solution.

In this tutorial, you will learn how to manually code a new content type—a “Review” Custom Post Type (CPT)—using just your theme’s functions.php file. You will learn the exact structure needed to create and display your content, making your site lighter, faster, and truly custom.

What you will build: A dedicated Reviews section in your WordPress Admin panel, separate from your regular Posts and Pages.

Prerequisites:

  • A local or live WordPress installation.
  • A code editor (like VS Code).
  • Basic understanding of PHP and WordPress file structure.

Phase 1: The Code Container and Setup

Before we write any actual code, we need to create a container function and hook it into the WordPress loading sequence. This is standard PHP practice for keeping your code safe and organized.

Important Note: To ensure your changes aren’t erased when your theme updates, always use a Child Theme for this code. We will be editing the functions.php file inside your child theme.

Step 0: Locating the functions.php File (The Starting Line)

The location of this crucial file depends on whether you are working locally or on a live server.

A. For Local Host Users (e.g., XAMPP, Local by Flywheel)

  1. Navigate to the folder where your local WordPress sites are stored. This path is often called your “Web Root” or “htdocs” folder.
  2. Follow this specific file path inside your local site folder:

your-site-name→wp-content→themes→your-child-theme-name→functions.php

  1. Open the functions.php file using VS Code directly from this folder location.

B. For Live Server Users (Using an FTP Client like FileZilla)

  1. Connect to your web server using your FTP client (host, username, password).
  2. Once connected, navigate through the remote directories using this exact path:

public_html→wp-content→themes→your-child-theme-name→functions.php

  1. Right-click on the functions.php file and select “View/Edit” to open it in VS Code. This downloads a temporary copy for editing and prompts you to upload the changes when you save.

Step 1: Open functions.php and Define the Function

Open your theme’s functions.php file in VS Code. At the very bottom, paste the following structure:

PHP

// 1. Define the function
function register_review_cpt() {

    // All our CPT code will go here...

}
// 2. Tell WordPress to run this code on initialization
add_action( 'init', 'register_review_cpt', 0 );

This tells WordPress: “When you start up (init), run the code inside register_review_cpt().”


Phase 2: Defining the Labels and Arguments

The register_post_type() function takes two main pieces of data: the Labels (what the user sees) and the Arguments (how the CPT behaves).

Step 2: Set the Labels (What the Admin Sees)

The labels are the human-friendly names that appear in your admin menu.

PHP

    // Define the labels (what the user sees)
    $labels = array(
        'name'                  => _x( 'Reviews', 'Post Type General Name', 'textdomain' ),
        'singular_name'         => _x( 'Review', 'Post Type Singular Name', 'textdomain' ),
        'menu_name'             => _x( 'Reviews', 'Admin Menu text', 'textdomain' ),
        'all_items'             => __( 'All Reviews', 'textdomain' ),
        'add_new_item'          => __( 'Add New Review', 'textdomain' ),
        'new_item'              => __( 'New Review', 'textdomain' ),
        'edit_item'             => __( 'Edit Review', 'textdomain' ),
        'view_item'             => __( 'View Review', 'textdomain' ),
        'search_items'          => __( 'Search Reviews', 'textdomain' ),
    );

Step 3: Set the Arguments (How the CPT Behaves)

The arguments control the behavior. These are the most important settings for a functional CPT:

PHP

    // Define the arguments (how the CPT behaves)
    $args = array(
        'label'                 => __( 'Review', 'textdomain' ),
        'description'           => __( 'Content for product and game reviews', 'textdomain' ),
        'labels'                => $labels,
        'supports'              => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ), // Enable essential features
        'public'                => true, // MUST be TRUE to display on the front-end
        'show_ui'               => true, // Show in Admin Menu
        'menu_icon'             => 'dashicons-star-half', // A nice, recognizable icon
        'has_archive'           => true, // MUST be TRUE: Enables the main archive page (e.g., yoursite.com/reviews/)
    );

Step 4: Register the CPT

Finally, you put it all together using your chosen slug: review.

PHP

    // Register the post type with a unique slug ('review')
    register_post_type( 'review', $args );

→ At this point, save your functions.php file in VS Code and refresh your WordPress Admin. You should see the new “Reviews” menu item!

Phase 3: The Critical Fixes (Displaying Content)

If you follow the first two phases, your CPT exists, but it won’t show up on your website. This is where most developers get stuck!

Fix 1: Flush the Permalinks Cache

WordPress needs to be told about the new URL structure (/reviews/).

  1. Go to Settings → Permalinks in your WordPress Admin.
  2. Scroll down and click “Save Changes” (you don’t need to change the structure).

This action forces WordPress to flush its internal rules, recognizing your has_archive => true setting.

Fix 2: Creating the Theme Templates

Your theme doesn’t magically know how to display the “Review” content. You need to provide two new template files in your theme folder:

File NamePurposeWhat to do
archive-review.phpThe main page that lists all reviews (e.g., /reviews/).Duplicate your theme’s archive.php or index.php and rename it.
single-review.phpThe page for an individual review post.Duplicate your theme’s single.php and rename it.

By simply duplicating and renaming these files, you are telling WordPress: “For the ‘review’ post type, use these templates.” You can then customize them later!

Design Synergy: CPTs for Better UX (By Zara)

Architect’s Tip: When you use a Custom Post Type for Reviews, you are creating a clean data structure. In design, a clean structure makes for a better user experience (UX). Because your Reviews are separate from your blog Posts, your designer (like me!) can create a completely unique, visually compelling layout for the Review pages in tools like Adobe XD or Blender without risking the design of the main blog. Clean code enables beautiful, focused design.

⋆ The Complete Custom Post Type Code (Copy & Paste)

For quick deployment or error checking, here is the complete PHP code block, consolidating all the steps above. You can paste this entire section into the bottom of your child theme’s functions.php file.

PHP

// ==========================================================
// THE COMPLETE 'REVIEW' CUSTOM POST TYPE CODE
// Paste this entire block into functions.php
// ==========================================================

function register_review_cpt() {

    // 1. LABELS: What the user sees in the WordPress Admin
    $labels = array(
        'name'                  => _x( 'Reviews', 'Post Type General Name', 'textdomain' ),
        'singular_name'         => _x( 'Review', 'Post Type Singular Name', 'textdomain' ),
        'menu_name'             => _x( 'Reviews', 'Admin Menu text', 'textdomain' ),
        'all_items'             => __( 'All Reviews', 'textdomain' ),
        'add_new_item'          => __( 'Add New Review', 'textdomain' ),
        'new_item'              => __( 'New Review', 'textdomain' ),
        'edit_item'             => __( 'Edit Review', 'textdomain' ),
        'view_item'             => __( 'View Review', 'textdomain' ),
        'search_items'          => __( 'Search Reviews', 'textdomain' ),
    );

    // 2. ARGUMENTS: How the CPT behaves on the site
    $args = array(
        'label'                 => __( 'Review', 'textdomain' ),
        'description'           => __( 'Content for product and game reviews', 'textdomain' ),
        'labels'                => $labels,
        'supports'              => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ), // Enabled features
        'public'                => true,                                                             // MUST be TRUE to display on the front-end
        'show_ui'               => true,                                                             // Show in Admin Menu
        'menu_icon'             => 'dashicons-star-half',                                            // Icon for the admin menu
        'has_archive'           => true,                                                             // Enables the main archive page (/reviews/)
    );

    // 3. REGISTRATION: Combine labels and arguments
    register_post_type( 'review', $args );

}

// 4. THE HOOK: Tell WordPress to run this function on startup
add_action( 'init', 'register_review_cpt', 0 );

Conclusion

Congratulations! You’ve successfully created a Custom Post Type using raw code. You now have a faster site, full control over your content, and a professional-grade development skill.

Your next steps:

  1. Add Custom Fields: Learn how to add fields for a “Rating Score” or “Pros/Cons” list using the ACF (Advanced Custom Fields) plugin (which is much lighter than a full CPT builder plugin).
  2. Customize the Template: Dive into your new archive-review.php and single-review.php files to truly customize the HTML and CSS for a unique look!

Got Questions? Let us know in the comments below what CPT you are building, or what you struggled with!

Leave a Comment

Your email address will not be published. Required fields are marked *