Template Examples
Audience: Site Builder.
Copy-paste starting points for wiring PageCraft entries into front-end templates. They progress from the simplest possible render to a full multi-page site with global chrome.
Before you start — two common causes of missing output:
- Use your real field short name. Every
field="…"below usesfield="page_builder". Replace it with your actual PageCraft field short name (Develop → Channels → Fields). A wrong name renders nothing and emits<!-- PageCraft: no data found … -->. - Keep the asset placeholders.
{pagecraft_css}and{pagecraft_styles}belong in<head>;{pagecraft_js}and{pagecraft_scripts}belong before</body>. Without them, plugin sections won't load their CSS or JavaScript. Put{pagecraft_styles}before your stylesheets so your CSS wins collisions. See Template Tags Reference.
If a page comes up blank, View Source and check for an HTML comment — see the diagnostics table in Template Tags Reference.
1. Out of the box — the minimal render
The smallest template that renders a PageCraft entry. No routing, no chrome. Good for confirming the pipeline works before adding anything else.
global/_wrapper (layout)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>My Site</title>
{pagecraft_css}
{pagecraft_styles}
</head>
<body>
{layout:contents}
{pagecraft_js}
{pagecraft_scripts}
</body>
</html>
site/page (the template that renders an entry)
{layout="global/_wrapper"}
{exp:channel:entries channel="default_page" limit="1" status="open"}
{exp:page_craft:render entry_id="{entry_id}" field="page_builder" runtime="yes"}
{/exp:channel:entries}
This grabs the most recent open entry and renders it. render supplies the inline CSS, the .is-wrapper div, and the runtime — so the layout only needs the four asset placeholders.
2. Out of the box — URL-routed pages
One template that serves any page by its url_title from the last URL segment. This is the typical "flat pages" setup.
site/page
{layout="global/_wrapper"}
{exp:channel:entries channel="default_page" url_title="{segment_1}" limit="1" require_entry="yes"}
{exp:page_craft:render entry_id="{entry_id}" field="page_builder" runtime="yes"}
{/exp:channel:entries}
url_title="{segment_1}"— matches/about→ entry with url_titleabout. Use{segment_2}if the template lives one group deep, or{last_segment}for nested/Structure URLs.require_entry="yes"— a clean miss instead of a half-rendered page when the URL matches nothing.
Home page note: an empty URL means
{segment_1}is blank, which matches no entry. Handle home with a dedicated condition — see example 4.
3. Mixed — out of the box render + custom EE markup around it
PageCraft renders the page body, but you hand-author the surrounding template: your own <h1>, breadcrumbs, sidebar, other channel fields, an entries loop of related posts, etc. Mix freely — render just drops resolved HTML into wherever you place it.
site/page
{layout="global/_wrapper"}
{exp:channel:entries channel="default_page" url_title="{segment_1}" limit="1" require_entry="yes"}
<nav class="breadcrumbs">
<a href="{path='home'}">Home</a> › <span>{title}</span>
</nav>
<article class="page-body">
{!-- PageCraft-built body --}
{exp:page_craft:render entry_id="{entry_id}" field="page_builder" runtime="yes"}
</article>
{!-- a normal custom field, rendered by hand --}
{if custom_summary}<aside class="summary">{custom_summary}</aside>{/if}
<section class="related">
<h2>Related pages</h2>
{related_entries:related_pages}
<a href="{title_permalink='site/page'}">{title}</a>
{/related_entries:related_pages}
</section>
{/exp:channel:entries}
The key idea: render is just one tag among many inside the entries loop. Everything outside it is ordinary EE templating.
3b. Mixed — split CSS / HTML / runtime manually
When you need the page CSS in <head> (cleaner than render's inline <style>) but the HTML lower down, use the granular tags instead of render. Use the field tag (fastest, you're already in the loop) or the equivalent module tags.
{layout="global/_wrapper"}
{exp:channel:entries channel="default_page" url_title="{segment_1}" limit="1" require_entry="yes"}
{!-- field-tag form: page_builder is YOUR field's short name --}
{if no_results}{redirect="404"}{/if}
{!-- main + section CSS pushed to the <head> via a layout variable --}
{layout:set name="page_styles"}
<style>{page_builder type="css"}</style>
<style>{page_builder type="sectioncss"}</style>
{/layout:set}
<h1>{title}</h1>
{page_builder type="html" runtime="yes"}
{/exp:channel:entries}
…and in the layout <head>, output {layout:contents}-set styles:
{if page_styles}{page_styles}{/if}
{pagecraft_css}
{pagecraft_styles}
type="html"defaults toruntime="no". Addruntime="yes"whenever the page uses plugin sections, or the sliders/menus won't initialise.
4. All custom — full site with global header & footer
A complete production layout: fixed global header, segment-based routing, a dedicated home case, and a global footer. This is the pattern to grow into once the basics render.
global/_wrapper (layout)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>{if segment_1}{segment_1}{if:else}Home{/if} · My Site</title>
<style>
.site-header{position:fixed;top:0;left:0;width:100%;z-index:9999}
.site-header-spacer{height:70px}
</style>
{pagecraft_css}
{pagecraft_styles}
</head>
<body>
{!-- Global header on every page. Keep wrapper="yes" (the default) so the
runtime always finds a .is-wrapper element and prepareContent() can't
crash. --}
<div class="site-header">{exp:page_craft:global_header}</div>
<div class="site-header-spacer"></div>
{layout:contents}
{exp:page_craft:global_footer}
{pagecraft_js}
{pagecraft_scripts}
</body>
</html>
site/page (router with a home case)
{layout="global/_wrapper"}
{if segment_1 == "" OR segment_1 == "home"}
{!-- Home: newest open entry --}
{exp:channel:entries channel="default_page" url_title="home" limit="1" require_entry="yes"}
{exp:page_craft:render entry_id="{entry_id}" field="page_builder" runtime="yes"}
{/exp:channel:entries}
{if:else}
{!-- Inner page: matched by URL --}
{exp:channel:entries channel="default_page" url_title="{segment_1}" limit="1" require_entry="yes"}
{exp:page_craft:render entry_id="{entry_id}" field="page_builder" runtime="yes"}
{/exp:channel:entries}
{/if}
Why global_header keeps its wrapper
global_header / global_footer default to wrapper="yes", emitting a <div class="is-wrapper">. Do not set wrapper="no" on chrome that contains plugin sections (a nav menu, social links, etc.). The runtime's prepareContent() runs document.querySelector(".is-wrapper").classList.add(...) with no null guard — if the header boots the runtime but has no .is-wrapper, init throws and the entire page fails to render, main content included. The fixed-position styling lives on the outer .site-header div, so the inner .is-wrapper doesn't affect layout.
5. All custom — multiple PageCraft fields on one entry
If a channel has more than one PageCraft field (e.g. a page_header builder and a page_body builder), target each by name. render is per-field, so call it once per field.
{layout="global/_wrapper"}
{exp:channel:entries channel="landing" url_title="{segment_1}" limit="1" require_entry="yes"}
<header class="lp-hero">
{exp:page_craft:render entry_id="{entry_id}" field="page_header" runtime="yes"}
</header>
<main class="lp-body">
{exp:page_craft:render entry_id="{entry_id}" field="page_body" runtime="yes"}
</main>
{/exp:channel:entries}
The runtime and CSS enqueue is de-duplicated across calls, so multiple render tags on one page won't double-load assets.
Quick reference
| You want… | Use |
|---|---|
| Whole page, one tag, simplest | {exp:page_craft:render entry_id="{entry_id}" field="…"} |
| HTML only, manage CSS yourself | {your_field type="html"} or {exp:page_craft:html} |
Page CSS in <head> |
{your_field type="css"} + {your_field type="sectioncss"} |
| Sitewide header / footer | {exp:page_craft:global_header} / {exp:page_craft:global_footer} |
See Template Tags Reference for every parameter.
For live HTML examples of section authoring, open the annotated developer reference.