Ghost Theme Basics — default.hbs, post.hbs, page.hbs and Partials
In this tutorial, you'll learn the basics of Ghost theme development — understanding the template hierarchy, creating default.hbs, post.hbs, and page.hbs templates, using partials for reusable components, and how themes render different content types.
What You'll Learn
- The Ghost theme template hierarchy and how Ghost chooses templates
- Creating the required default.hbs file
- post.hbs: The template for individual post pages
- page.hbs: The template for static pages
- Partials: reusable template components (header, footer, navigation)
- The theme directory structure and required files
- Template contexts and available data
- How Ghost renders different routes with different templates
Why It Matters
Themes control every aspect of how your content looks. Even if you use a pre-built theme, understanding the template hierarchy helps you customize it. When you want to change how your post page looks, you edit post.hbs. When you want to customize the homepage, you edit index.hbs. Knowing which file controls which part of your site turns theming from guesswork into engineering.
Real-World Use
A blogger wants to add an author bio section at the bottom of every post. Instead of editing the full post template, she opens post.hbs in her theme, adds a Handlebars partial for the author card, and the bio appears on every post. Later she wants a different layout for her "About" page — she creates a custom page template (page-about.hbs) with a full-width layout.
Learning Path
flowchart LR A["Image & Media"] --> B["Theme Basics
You are here"]:::current B --> C["Handlebars Templates"] C --> D["Theme Assets"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Theme Directory Structure
A Ghost theme is a folder containing Handlebars templates, assets, and a package.json file.
my-theme/
├── assets/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── index.js
├── partials/
│ ├── header.hbs
│ └── footer.hbs
├── default.hbs
├── index.hbs
├── post.hbs
├── page.hbs
├── tag.hbs
├── author.hbs
├── error.hbs
└── package.json
Some files are required, others are optional.
Required Files
| File | Purpose |
|---|---|
| default.hbs | The base template that all other templates inherit from |
| index.hbs | The homepage and post listing |
| post.hbs | Individual post pages |
| page.hbs | Individual page views |
| package.json | Theme metadata and configuration |
Optional Files
| File | Purpose |
|---|---|
| tag.hbs | Tag page (falls back to index.hbs if missing) |
| author.hbs | Author page (falls back to index.hbs if missing) |
| error.hbs | 404 and error pages (English default if missing) |
| custom-*.hbs | Custom templates for specific routes |
| partials/*.hbs | Reusable template components |
The Template Hierarchy
When Ghost renders a page, it selects the template based on the route:
flowchart TD
A["Request URL"] --> B{"Route type?"}
B -->|"/"| C["index.hbs"]
B -->|"/post-slug/"| D["post.hbs"]
B -->|"/page-slug/"| E["page.hbs"]
B -->|"/tag/tag-slug/"| F["tag.hbs"]
B -->|"/author/author-slug/"| G["author.hbs"]
B -->|"404"| H["error.hbs"]
B -->|"/custom/"| I["custom-{route}.hbs"]
C --> J["default.hbs (wraps everything)"]
D --> J
E --> J
F --> J
G --> J
H --> J
I --> J
style J fill:#38bdf8,color:#0f172a
All templates are wrapped in default.hbs, which provides the HTML document structure.
default.hbs
default.hbs is the base template that contains the HTML structure shared by every page. It includes the <!DOCTYPE html>, <head>, and the Ghost {{body}} helper.
<!DOCTYPE html>
<html lang="{{@site.locale}}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{meta_title}}</title>
{{ghost_head}}
<link rel="stylesheet" href="{{asset "css/style.css"}}">
</head>
<body>
{{> header}}
<main>
{{{body}}}
</main>
{{> footer}}
{{ghost_foot}}
</body>
</html>
Key elements:
{{ghost_head}}— Ghost injects SEO metadata, JSON-LD structured data, and other head elements here.{{ghost_foot}}— Ghost injects scripts and code injection here.{{{body}}}— Three curly braces tell Handlebars not to escape HTML. This is where child templates render their content.{{> header}}— Includes the header partial.{{asset "css/style.css"}}— Generates the correct URL for the asset file.
index.hbs
index.hbs renders the homepage and paginated post listings. It is the default template for the root route and any route that shows a list of posts.
{{!< default}}
<section class="post-feed">
{{#foreach posts}}
<article class="post-card">
{{#if feature_image}}
<a href="{{url}}">
<img src="{{img_url feature_image size="s"}}" alt="{{title}}">
</a>
{{/if}}
<h2><a href="{{url}}">{{title}}</a></h2>
<p>{{excerpt words="30"}}</p>
<div class="post-meta">
<time datetime="{{date format="YYYY-MM-DD"}}">{{date}}</time>
{{#foreach tags}}
<a href="{{url}}">{{name}}</a>
{{/foreach}}
</div>
</article>
{{/foreach}}
</section>
{{pagination}}
Key elements:
{{!< default}}— Tells Ghost to wrap this template inside default.hbs. The content replaces{{{body}}}in the parent template.{{#foreach posts}}— Loops through all posts on the current page.{{pagination}}— Renders pagination links (Previous/Next).
post.hbs
post.hbs renders individual post pages. It has access to the full post context.
{{!< default}}
<article class="post-full">
{{#post}}
{{#if feature_image}}
<figure class="post-image">
<img src="{{img_url feature_image size="xl"}}" alt="{{feature_image_alt}}">
{{#if feature_image_caption}}
<figcaption>{{feature_image_caption}}</figcaption>
{{/if}}
</figure>
{{/if}}
<h1 class="post-title">{{title}}</h1>
<div class="post-meta">
<time datetime="{{date format="YYYY-MM-DD"}}">{{date}}</time>
{{#foreach authors}}
<a href="{{url}}">{{name}}</a>
{{/foreach}}
</div>
<section class="post-content">
{{content}}
</section>
<div class="post-tags">
{{#foreach tags}}
<a href="{{url}}">{{name}}</a>
{{/foreach}}
</div>
{{/post}}
</article>
The {{#post}} block sets the context to the current post. Inside it, you can access all post properties: title, content, feature_image, date, tags, authors, excerpt, reading_time, and more.
page.hbs
page.hbs renders static pages. It is identical in structure to post.hbs but used for pages instead of posts.
{{!< default}}
<article class="page-full">
{{#post}}
<h1>{{title}}</h1>
<section class="page-content">
{{content}}
</section>
{{/post}}
</article>
Some themes use the same template for posts and pages by not differentiating. However, separating them gives you more control — you might want a different header style, no author byline, or a different layout for pages.
Partials
Partials are reusable template snippets stored in the partials/ folder. They are included in other templates using the {{>}} syntax.
header.hbs
<header class="site-header">
<div class="header-content">
<h1 class="site-title">
<a href="{{@site.url}}">{{@site.title}}</a>
</h1>
<nav class="site-nav">
{{navigation}}
</nav>
<button class="menu-toggle">Menu</button>
</div>
</header>
footer.hbs
<footer class="site-footer">
<div class="footer-content">
<p>© {{date format="YYYY"}} <a href="{{@site.url}}">{{@site.title}}</a></p>
<nav class="footer-nav">
{{navigation type="secondary"}}
</nav>
</div>
</footer>
Subdirectory Partials
You can organize partials in subdirectories:
partials/
├── components/
│ ├── post-card.hbs
│ └── author-card.hbs
└── layout/
├── header.hbs
└── footer.hbs
Include them with the subdirectory path:
{{> "components/post-card"}}
{{> "components/author-card"}}
Custom Templates
You can create custom page templates by naming them with the page slug. For example, if you have a page with slug "about," create page-about.hbs. Ghost automatically uses it for that page.
Custom templates follow this pattern:
page-{slug}.hbs— for pagespost-{slug}.hbs— for poststag-{slug}.hbs— for tagsauthor-{slug}.hbs— for authors
Or you can define custom routes in routes.yaml (Lesson 35).
Common Mistakes
Forgetting
{{!< default}}at the top of templates: Every template that is not default.hbs must start with{{!< default}}to inherit the base layout. Without it, the template renders without the HTML structure, head tags, or theme assets.Using two curly braces for body content: The
{{{body}}}and{{content}}helpers use three curly braces ({{{ }}}). Two curly braces ({{ }}) escape HTML, which would render post content as unformatted text with visible HTML tags.Editing the default.hbs of a third-party theme without backing up: If you modify default.hbs and something breaks, you need to restore the original. Always back up your theme before editing.
Missing the
{{ghost_head}}helper: Without{{ghost_head}}, Ghost does not inject SEO metadata, canonical URLs, or JSON-LD structured data. Your pages will have missing SEO elements.Hardcoding URLs instead of using helpers: Using
https://mysite.com/aboutinstead of{{@site.url}}/aboutmeans you must update URLs if your site changes domain. Always use Ghost helpers for site URLs.
Practice Questions
What are the three required files in a Ghost theme? Answer: default.hbs (base template), index.hbs (homepage/post listing), and post.hbs (individual post view). page.hbs is also typically considered a core file. package.json is required for theme metadata.
What is the purpose of the
{{!< default}}syntax in Ghost templates? Answer: It tells Ghost to render the current template's content inside the default.hbs layout. The content replaces the{{{body}}}helper in default.hbs. Without it, the template renders standalone without site chrome.How do you include a partial in a Ghost template? Answer: Use the
{{> partial-name}}syntax. Partials are stored in thepartials/directory. For subdirectory partials, use the path:{{> "components/post-card"}}.Challenge: Create a minimal Ghost theme from scratch with default.hbs, index.hbs, post.hbs, page.hbs, and two partials (header and footer). Install the theme on a local Ghost site and verify it renders posts, pages, and the homepage correctly.
FAQ
Mini Project
Your task: Create a minimal Ghost theme and test it locally.
- Create a new folder called
minimal-themeincontent/themes/. - Create default.hbs with HTML structure, ghost_head, ghost_foot, header partial, footer partial, and {{{body}}}.
- Create index.hbs that loops through posts and displays title, excerpt, and date.
- Create post.hbs that shows the full post with feature image, content, tags, and author.
- Create page.hbs similar to post.hbs but without author/tags.
- Create partials/header.hbs with site title and navigation.
- Create partials/footer.hbs with copyright.
- Create package.json with theme name and version.
- Select the theme in Ghost Admin > Settings > Design.
- Verify the theme works on all page types.
This exercise gives you a working theme skeleton you can extend for any project.
What's Next
Now that you understand the template structure, dive deeper into Handlebars:
Continue to Lesson 16: Handlebars Templates — foreach, if, helpers, contexts, and advanced templating.
Related lessons:
- Theme Assets — Manage CSS, JS, and assets
- Custom Themes — Upload and validate themes
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro