A custom post type template is a PHP template file in a WordPress theme that controls how a specific custom post type’s content is displayed on the frontend. Just as WordPress uses single.php for blog post single views and archive.php for blog listing pages, it looks for CPT-specific template files — single-{post-type}.php and archive-{post-type}.php — before falling back to these generic defaults. Creating purpose-built templates for each CPT ensures the content is presented the way it was designed to be, not with generic blog formatting.
Without a custom template, a portfolio item uses the same template as a blog post, a team member page looks like an article, and an event listing inherits a chronological archive layout that doesn’t fit. Template files give developers full control over the HTML structure, the layout, which custom fields are displayed, and in what format — delivering an experience tailored to that content type rather than a generic fallback.
WordPress Template Hierarchy for CPTs
WordPress follows a specific lookup order when determining which template to use. For a CPT named portfolio, WordPress looks for templates in this order:
For single (individual) views:
1. single-portfolio.php
2. single.php
3. singular.php
4. index.php
For archive (listing) views:
1. archive-portfolio.php
2. archive.php
3. index.php
WordPress uses the first file it finds. Creating single-portfolio.php in your theme gives you complete control over how individual portfolio items display. Creating archive-portfolio.php controls the listing page at /portfolio/.
[Image: Template hierarchy diagram showing WordPress lookup order from specific CPT template to generic fallbacks]
Purpose & Benefits
1. Full Control Over CPT Display Logic
A custom template can display any combination of post content, custom fields, taxonomies, related content, and custom markup specific to that content type. A job listing template might display salary range and application deadline prominently; a team member template might show a headshot, title, and social links in a card layout; a recipe template might display ingredients as a structured list with quantities. None of these fit a generic blog post layout.
2. Clean Separation of Template Code
Keeping CPT display logic in dedicated template files makes the codebase easier to maintain. When a designer wants to change how events display, the edit is in single-event.php — not buried in a conditionally loaded section of single.php. This separation is consistent with WordPress’s template hierarchy design philosophy and makes debugging and updates more predictable.
3. Enables Advanced Layouts Without Conflicts
With a dedicated page template, a CPT can use a completely different layout from the rest of the site — no sidebar, a full-width hero section, a two-column structure for content and metadata — without affecting other content types. This flexibility is especially valuable for CPTs with distinct visual requirements. Our WordPress development services build these templates as standard practice.
Examples
1. Single Portfolio Item Template
A portfolio CPT template that pulls in the post title, featured image, description, and ACF custom fields (client name, project URL, services performed):
<?php
// single-portfolio.php — Template for individual portfolio items
get_header();
?>
<article id="post-<?php the_ID(); ?>" <?php post_class( 'portfolio-single' ); ?>>
<?php if ( has_post_thumbnail() ) : ?>
<div class="portfolio-hero">
<?php the_post_thumbnail( 'full' ); ?>
</div>
<?php endif; ?>
<div class="portfolio-content">
<h1 class="portfolio-title"><?php the_title(); ?></h1>
<?php if ( function_exists( 'get_field' ) ) : ?>
<div class="portfolio-meta">
<span class="client"><?php echo esc_html( get_field( 'client_name' ) ); ?></span>
<?php if ( $url = get_field( 'project_url' ) ) : ?>
<a href="<?php echo esc_url( $url ); ?>" target="_blank" rel="noopener">View Project</a>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="portfolio-description">
<?php the_content(); ?>
</div>
</div>
</article>
<?php get_footer(); ?>
2. Archive Template with Custom Query
An archive-portfolio.php that overrides the default archive with a custom grid layout and optional taxonomy filtering:
<?php
// archive-portfolio.php — Portfolio listing page
get_header();
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$portfolio_query = new WP_Query( array(
'post_type' => 'portfolio',
'posts_per_page' => 12,
'paged' => $paged,
'orderby' => 'date',
'order' => 'DESC',
) );
?>
<section class="portfolio-archive">
<h1>Our Work</h1>
<div class="portfolio-grid">
<?php while ( $portfolio_query->have_posts() ) : $portfolio_query->the_post(); ?>
<div class="portfolio-card">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail( 'medium' ); ?>
<h2><?php the_title(); ?></h2>
</a>
</div>
<?php endwhile; wp_reset_postdata(); ?>
</div>
</section>
<?php get_footer(); ?>
3. Block Theme Approach with Template Parts
In a block WordPress theme using Full Site Editing, CPT templates are JSON-based block templates stored in the theme’s /templates/ directory. For a block theme, the single portfolio template lives at /templates/single-portfolio.html and contains block markup:
<!-- wp:template-part {"slug":"header"} /-->
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:post-title {"level":1} /-->
<!-- wp:post-featured-image /-->
<!-- wp:post-content /-->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer"} /-->
Block templates can be edited visually in the Site Editor, making them accessible to non-developers for layout changes.
Common Mistakes to Avoid
- Registering CPT templates in functions.php via filter — While it’s possible to assign a template file via the
template_includefilter, it’s more complex and harder to maintain. The naming convention (single-{post-type}.php) is the cleanest approach and requires no extra code. - Not calling
wp_reset_postdata()after custom queries — If a CPT archive template runs a customWP_Query, always callwp_reset_postdata()after the loop. Forgetting this corrupts the global$postobject and causes unexpected behavior in template tags and plugins later on the page. - Hardcoding styles into template files — Markup belongs in templates; styling belongs in CSS. Inline styles in template files make design changes harder. Keep templates clean HTML/PHP and handle all visual presentation in the theme’s stylesheet.
- Assuming template changes survive theme updates — Modifications to a parent theme’s template files are overwritten on update. Put custom CPT templates in a child theme — not the parent — to keep customizations update-safe.
Best Practices
1. Follow the Template Hierarchy Naming Convention
Use the standard WordPress naming convention: single-{post-type}.php and archive-{post-type}.php. This requires no additional configuration — WordPress finds these files automatically through the template hierarchy. It also makes the codebase self-documenting: any developer looking at the theme knows exactly which files control which content types.
2. Keep Templates Lean — Move Logic to Functions
Avoid putting complex business logic directly in template files. Functions that query related posts, format metadata, or build complex output belong in the theme’s functions.php or a dedicated include file. Templates should be primarily markup with simple function calls. This separation makes templates easier to read and logic easier to reuse across multiple templates.
3. Use Template Parts for Reusable Components
If multiple CPT templates share common elements — a header section, a sidebar, a related posts widget — extract them into template parts using get_template_part(). This reduces duplication and ensures that changes to shared components only need to be made in one place. The same principle applies to block themes using <!-- wp:template-part --> blocks.
Frequently Asked Questions
Do I need a separate template for every custom post type?
Not necessarily. If a CPT’s content can be displayed adequately by the generic single.php or archive.php, creating a CPT-specific template isn’t required. In practice, most CPTs benefit from their own templates — the content structure is usually different enough from blog posts that a custom layout is worth the effort.
Where do CPT templates live in the theme?
For classic (non-block) themes, CPT template files go in the root of the theme directory: /wp-content/themes/your-theme/single-portfolio.php. For child themes, place them in the child theme’s root directory — they take precedence over the parent theme. For block themes, templates go in the /templates/ directory as .html files.
Can I use page builders to create CPT templates?
Yes. Most page builders (Elementor, Beaver Builder, Bricks, Breakdance) have a “Theme Builder” or “Template Builder” feature that lets you create CPT templates visually and assign them to specific post types. These generate the template files or override the template hierarchy internally. The result is the same — a custom display for that CPT — just built through the builder’s interface rather than hand-coded PHP.
Does Full Site Editing change how CPT templates work?
Yes. Block themes using Full Site Editing store CPT templates in the /templates/ folder as block markup files (.html). These can be edited visually in the Site Editor at Appearance > Editor > Templates. The template hierarchy logic is the same, but the template format changes from PHP to block markup. Classic themes are unaffected.
Related Glossary Terms
How CyberOptik Can Help
As a WordPress-focused agency, we build custom post type templates on every project that needs them. Whether you need a single archive page, a complex CPT with multiple display contexts, or a block-theme-compatible template setup, our developers can build it properly. Get in touch to discuss your project or explore our WordPress development services.


