Create Ghost Theme from scratch

If you know HTML, CSS and JavaScript, you can create beautiful themes for your Ghost blog. In this post, we will see how to create a Ghost theme from scratch.

Create Ghost Theme from scratch

If you know HTML, CSS and JavaScript, you can create beautiful themes for your Ghost blog. In this post, we will see how to create a Ghost theme from scratch.

This tutorial is divided into two parts.

  • Part 1 - Create a simple Ghost theme.
  • Part 2 - Add navigation bar and custom templates.


Before continuing, please make sure you have installed NodeJs and any text editors such as Visual Studio Code, Atom or Sublime Text installed on your computer. In this tutorial, I'll be using Visual Studio Code.

To develop a theme for ghost, one should have basic knowledge in:

  • HTML
  • CSS
  • JavaScript
  • Handlebars

Install Ghost

To develop and test the theme, you should install Ghost on your computer.

On Windows, open Command Prompt or PowerShell and type node --version and verify that you have installed a supported version of node.

  • Now, install Ghost-CLI
npm install -g ghost-cli
  • Before installing Ghost, Navigate to Desktop and create a folder named ghost
mkdir -p Desktop/ghost
  • And install Ghost in the folder.
cd ghost
ghost install local
# Or run npm install ghost

This will Ghost on your local machine.


Ghost uses Handlebars template engine to bring dynamic content in theme. This helps to create a clear separation between the design (HTML, CSS) and JavaScript logic.

Ghost uses Handlebars template engine to manage dynamic content and Js logic in the themes.

Install GScan

GScan is a tool to validate Ghost themes. All themes uploaded to Ghost will be automatically scanned with GScan to avoid fatal errors.

Install GScan by running:

# Install the npm package
npm install -g gscan

# Use gscan <file path> anywhere to run gscan against a folder
gscan /path/to/ghost/content/themes/casper

# Run gscan on a zip file
gscan -z /path/to/download/

Theme structure

It is recommended to follow this structure while developing a Ghost theme.

├── /assets
    ├── /css
        ├── screen.css
    ├── /fonts
    ├── /images
    ├── /js
├── /partials [optional]
    ├── list-post.hbs
├── default.hbs
├── index.hbs [required]
└── post.hbs [required]
└── package.json [required]
  • assets - Place all the assets ( CSS, JS, Images, Fonts etc ) of your theme in this folder.
  • partials - This is where you place different reusable parts of your template.
A Ghost theme should have index.hbs and post.hbs.

Theme templates

  • default.hbs - This is the base template that contains the HTML markup (Outline) that exists on every page such as <html>, <head> and <body> tags.
  • index.hbs - This is the most common template. It is used to display the list of posts. This template will be used if your theme does not have a tag.hbs, author.hbs or index.hbs page template. Usually, the index.hbs template inherits the header and footer section from default.hbs. Every Ghost themes should have this template.
  • home.hbs - home.hbs will be used if you want a separate home page for your blog. In other words, we can say that home.hbs act as the home page of a blog. It's an optional template.
  • post.hbs - The required template for a single post which extends default.hbs and uses the {{#post}} helper to output the post details. Custom templates for individual posts can be created using post-:slug.hbs.
  • page.hbs - An optional template for static pages. If this is not specified then post.hbs will be used. Custom templates for individual pages can be created using page-:slug.hbs.
  • author.hbs - An optional template for author archive pages. If not specified the index.hbs template is used. Custom templates for individual authors can be created using author{{slug}}.
  • private.hbs - An optional template for the password form page on password-protected publications.
  • error.hbs - This theme template is used to display 404 or 500 errors that are not handled by error- or class-specific templates. If one is not specified Ghost will use the default.
  • tag.hbs - An optional template for tag archive pages. If not specified the index.hbs template is used. Custom templates for individual tags can be created using tag-:slug.
  • amp.hbs - An optional theme template for AMP (Accelerated Mobile Pages). If your theme doesn't provide an amp.hbs file, Ghost will use its default.
  • robots.txt - Themes can include a robots.txt which overrides the default robots.txt provided by Ghost.


An optional custom templates that can be selected in the admin interface on a per-post basis. They can be used for both posts and pages.


An optional theme template for errors belonging to a specific class (e.g. error-4xx.hbs for 400-level errors). A matching error class template is prioritized over both error.hbs and the Ghost default template for rendering the error.


An optional theme template for status code-specific errors (e.g. error-404.hbs). A matching error code template is prioritized over all other error templates for rendering the error.

Create a Ghost theme

The first step in developing a Ghost theme is to add Package.json to set meta data of the theme.

Open the Ghost installation folder as a project in your favourite text editor. In this tutorial, I'll be using Visual Studio code.

Create a new directory named mytheme under content -> themes, create a file named package.json and add the following content to it.

    "name": "mytheme",
    "description": "A custom ghost theme",
    "version": "0.0.1",
    "engines": {
        "ghost-api": "v2"
    "license": "MIT",
    "author": {
        "email": ""
    "config": {
        "posts_per_page": 10,
        "image_sizes": {}

Then add the following files to the theme folder (mytheme).

  • default.hbs
  • index.hbs
  • post.hbs

Next, we're going to set out theme as the new theme of our ghost blog. For that, navigate to https://localhost:2368/ghost and login as ghost admin. Under Settings -> Design, you will see the default Casper theme listed there. But our theme is not displayed. Well, this is because we have not restarted our Ghost installation after adding our new theme files.

Restart Ghost with the following command.

ghost restart

Reload the page and you will see out theme listed there.

Ghost themes

Click on Activate to activate the theme. Ghost may list a couple of errors and warnings.

Error and warnings

Click OK to activate the theme.

You will get a blank page if you visit the blog now because we left our theme files blank. So, let's develop our theme now.

Open default.hbs and add the following content to it.

<!doctype html>
<html lang="en">

    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href=""
        integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">


<body class="{{body_class}}">
    <nav class="navbar navbar-expand navbar-light bg-light">
        <div class="nav navbar-nav">
            <a class="nav-item nav-link active" href="#">Home</a>

    {{!-- All the main content gets inserted here, index.hbs, post.hbs, etc --}}

    <div class="card">
        <div class="card-footer text-muted">
            &copy; {{date format="YYYY"}} {{@site.title}}


As we've discussed earlier, this file serves as the parent or root file. It holds the basic HTML tags such as <head>, <title> etc that are common in all pages.

Next, open index.hbs and add the following code. This page will serve as the template to list your posts.

{{!< default}}
{{!-- The tag above means: insert everything in this file
into the {body} of the default.hbs template --}}

{{!-- The main content area --}}
<main id="site-main">
    <div class="container">

            {{#foreach posts}}

            <article class="{{post_class}} card" style="margin-bottom: 20px;">
                <div class="card-body">

                    {{#if primary_tag}}
                        <span class="badge badge-success">{{}}</span>
                    <a style="text-decoration-line: none;" href="{{url}}">
                    <p>{{excerpt words="33"}}</p>


                        <span class="reading-time">{{reading_time}}</span>
                        <span> - Published: {{date published_at timeago="true"}}</span>



After adding the content, reload the home page of the blog. You will get a page similar to the one shown below.

Custom template home page

Next, we should create a template to display individual posts. For that, add the following code to post.hbs.

{{!< default}}

{{!-- Everything inside the #post tags pulls data from the post --}}

<main id="site-main" class="container">
    <div class="inner">

        <article class="post-full {{post_class}} {{#unless feature_image}}no-image{{/unless}}">

            <h1 class="text-center">{{title}}</h1>

            {{#if feature_image}}
            <figure class="post-full-image">
                {{!-- This is a responsive image, it loads different sizes depending on device --}}
                <img srcset="{{img_url feature_image size="s"}} 300w,
                            {{img_url feature_image size="m"}} 600w,
                            {{img_url feature_image size="l"}} 1000w,
                            {{img_url feature_image size="xl"}} 2000w" sizes="(max-width: 800px) 400px,
                            (max-width: 1170px) 700px,
                            1400px" src="{{img_url feature_image size="xl"}}" alt="{{title}}" />


                    <time datetime="{{date format="YYYY-MM-DD"}}">{{date format="D MMMM YYYY"}}</time>
                        <span>/</span> <a href="{{url}}">{{name}}</a>

                <div class="post-content">




Click on any post to see this template in action. Here's my output.

Custom template home page

In this post, we've learned to create a simple bootstrap theme for a Ghost blog. The next section of this post outlines how to add a navigation bar and pagination to the theme.

Share Tweet Send