Location: Custom Blocks → Structure → Post Type Overrides (Page 2 of the Structure admin) Option key stored in: wpoptions → wptrussposttypeoverrides System class: wpTrussGlobalStructure_Manager Version introduced: 1.3.0
What Is It?
The Global Structure system lets you assign Gutenberg block templates to eight slots (Header, Footer, Before Content, After Content, Sidebar, Archive Header, Search Header, 404 Page). Those slots are global, they apply to every page on the site.
Post Type Overrides adds a second layer on top. It lets you assign different templates to specific post types for three of those slots:
| Slot | What it does |
|---|---|
| Before Content | Injected before the post content on singular views |
| After Content | Injected after the post content on singular views |
| Sidebar | Replaces the theme sidebar widget area |
There is also a fourth column: Full Takeover – which is a more powerful mode described below.
Why Does It Exist?
Without overrides, a blog post, a WooCommerce product, and a custom developerdoc CPT would all get the same Before Content and After Content templates. That’s often wrong:
- A product page needs a pricing callout after the description, a blog post doesn’t.
- A documentation CPT needs a “Was this helpful?” block after content, a product doesn’t.
- You want a sidebar on docs pages but not on blog posts.
Post Type Overrides solve this by letting each CPT declare its own templates for these slots, without touching global settings.
Resolution Order (Priority Chain)
When wpTruss decides which template to render for beforecontent, aftercontent, or sidebar on a singular page, it walks this chain from highest to lowest priority:
1. Per-post meta override (_tp_page_header_id / _tp_page_footer_id)
↓ if not set
2. Post-type override table (wptruss_post_type_overrides option)
↓ if not set
3. Global slot (wptruss_global_before_content_id etc.)
↓ if not set
4. Nothing renders
This means:
- A post type override overrides the global slot for all posts of that type.
- An individual post’s per-post meta setting overrides even the post-type override.
- Leaving a slot empty in the override table means that post type inherits the global setting.
The Admin Table – Column by Column
Post Type (Column 1)
Lists every registered WordPress post type: WordPress core types (post, page), media (attachment), and all Custom Post Types registered by plugins or themes. Each row shows the post type label, its slug, and a badge (Core or CPT).
Before Content (Column 2)
A dropdown of all published Structure Templates. Selecting one means: for every singular view of this post type, inject this template above the post content (via the the_content filter). Leave it on — Inherit global — to fall through to the global Before Content slot.
After Content (Column 3)
Same logic as Before Content but injected below the post content.
Sidebar (Column 4)
Assigns a Structure Template to replace the theme’s sidebar widget area for this post type. Only fires if the theme calls getsidebar(). Individual posts can also opt out of the sidebar using the tphidesidebar post meta.
Full Takeover (Column 5: Enable checkbox)
This is the most powerful mode. When enabled:
- wpTruss intercepts the request at
template_redirect(priority 8, after the 404 check at priority 5). - The theme’s
single.php/ template hierarchy is completely bypassed. - wpTruss renders a full HTML page shell:
<!DOCTYPE html>,wphead(),wpbodyopen(), the assigned Before Content template,thecontent()(the actual post content), the assigned After Content template,wp_footer(). - The
<main>auto-wrapper is removed because the takeover template provides its own document structure. - The CSS class
wpt-pt-takeover wpt-pt-takeover--{post_type}is applied to<body>for styling hooks.
Use Full Takeover when you need complete control over the page structure for a CPT, for example, a documentation CPT where you want a two-column layout with a custom navigation sidebar, no theme chrome interference, and a completely custom article wrapper.
⚠️ Full Takeover requires the Before Content slot to have a template assigned for the post type. If Before Content is empty, takeover silently falls back and the theme renders normally.
When Full Takeover is active, the admin row is highlighted in yellow and a “Theme bypassed” badge appears under the post type name.
How to Configure an Override
- Go to Custom Blocks → Structure and click the Post Type Overrides tab (Page 2).
- Find the row for your CPT (e.g.,
developerdoc). - For each slot you want to customise, open the dropdown and select a published Structure Template. If you haven’t created the template yet, use the + Create new for this slot button, it opens the Gutenberg editor for a new Structure Template in a new tab.
- For Full Takeover, tick the Enable checkbox in the last column.
- Click Save Overrides.
Changes are live immediately. The system caches template content in transients (5 minutes), so you may need to wait briefly or clear the cache to see changes on the frontend.
Creating Templates for Post-Type Slots
Templates are wpt_structure CPT posts edited in Gutenberg. When creating one for a post-type slot:
- Go to Custom Blocks → All Templates → Add New.
- In the Template Slot Type metabox (right sidebar), tag it with the slot it’s designed for (
Before Content (Article Header),After Content (Article Footer), orSidebar). - Build your layout using any blocks.
- Publish it. (Draft templates appear in the admin dropdown with a “Draft” label but will not render on the frontend until published.)
- Return to the Post Type Overrides table and assign it.
Full Takeover vs. Regular Override: When to Use Which
| Scenario | Regular Override | Full Takeover |
|---|---|---|
| Add a pricing CTA above/below product content | ✅ Before/After Content | ❌ Not needed |
| Add a “Was this helpful?” block to docs | ✅ After Content | ❌ Not needed |
| Replace sidebar on a CPT | ✅ Sidebar override | ❌ Not needed |
| Build a completely custom doc page layout (2-column, custom nav, no theme header/footer interference) | ❌ Not enough control | ✅ Full Takeover |
| Override a CPT’s single.php entirely | ❌ Theme still controls structure | ✅ Full Takeover |
How It Works Internally
The override table is stored as a nested PHP array in wp_options:
[
'developerdoc' => [
'before_content' => 42, // post ID of the Structure Template
'after_content' => 0, // 0 = inherit global
'sidebar' => 0,
'takeover' => true,
],
'product' => [
'before_content' => 55,
'after_content' => 58,
'sidebar' => 0,
'takeover' => false,
],
]
On every singular request, injectcontentslots() (hooked to thecontent filter) calls getslotidforposttype() which checks this table for the current post type before falling back to the global slot. If Full Takeover is enabled, mayberenderpttakeover() fires at templateredirect priority 8 and renders the complete page, then calls exit to stop WordPress loading the theme template.
Template content is cached in transients using the key pattern wptcontentpt{posttype}{slot} (e.g., wptcontentptdeveloperdoc_before). The cache is busted automatically whenever:
- A Structure Template post is saved or deleted.
- The global slot assignment changes.
- The override table is saved.
Cache Keys Reference
| Scenario | Transient key |
|---|---|
| Global slot (e.g., global before_content) | wptcontentbefore_content |
| Post-type override (e.g., developerdoc before_content) | wptcontentptdeveloperdocbefore |
| Post-type override (e.g., developerdoc after_content) | wptcontentptdeveloperdocafter |
| Post-type override sidebar | wptcontentptdeveloperdocsidebar |
| Per-page template override | wptcontenttpl{postid} |
All transients have a 5-minute TTL (CACHE_TTL = 300) and are busted immediately on any relevant change.
Inheritance Summary
Slot resolution for before_content on a "developerdoc" singular page:
Is there per-post meta (_tp_page_header_id)?
→ Yes → use that template (or suppress if -1)
→ No ↓
Is there a post-type override for developerdoc → before_content?
→ Yes, ID > 0 → use that template
→ No / 0 ↓
Is there a global before_content slot assigned?
→ Yes → use global template
→ No → nothing renders
Empty slots always inherit the next level down. This means you can set a global default for most post types and only override the CPTs that need a different layout.
Frequently Asked Questions
Q: I assigned a template but it’s not showing up on the frontend. A: Check that the template is Published (not Draft). Drafts will never render. Also confirm you clicked Save Overrides after making changes.
Q: I enabled Full Takeover but the theme header is still showing. A: Full Takeover only fires on singular pages of that post type (individual posts, not archives). It also requires a Before Content template to be assigned, if that slot is empty, takeover silently aborts.
Q: Can I use Full Takeover to replace the archive page for a CPT? A: No. Full Takeover only intercepts is_singular() requests. Archives, category pages, and post type archives are not affected. Use the Archive Header slot on the global Structure page for those.
Q: The sidebar override is not working. A: The sidebar slot only fires if your theme calls get_sidebar(). Block themes (FSE) typically don’t call this function. In that case, add the sidebar content directly into a Before Content or After Content template instead.
Q: Can multiple post types share the same template? A: Yes. Assign the same Structure Template ID to as many post types as you like. The admin UI shows an “also used by” label on shared templates for transparency. Caching uses per-post-type cache keys so each type can still be invalidated independently.
Q: Does this affect REST API or block editor previews? A: No. The slot injection hooks check issingular(), intheloop(), and ismain_query() before rendering, which excludes REST requests and block editor iframes.