Ghost.io comes with a great default template called Casper for people who want to hit the ground running. You could easily start there and customize the theme to your liking. However, if you'd rather start from zero and build upward to your finished theme this might be a good read for you.
TL;DR;
Clone the repository into your your Ghost's /content/themes
directory and start building.
Goals
In this article we'll build a barebones starting point for a ghost.io theme. You'll have a theme that passes gscan testing and is ready for you to completely customize. It will be the very opposite of opinionated.
Assumptions
For you to comfortably follow along with this tutorial I'll assume you're familiar with the command line, git, and installing npm packages. You should also probably know a little something about HTML, CSS, and have Ghost installed somewhere.
Help
If you get lost at any point take a look at the source of the completed tutorial.
Let's get started
First make a new directory wherever you'd like and cd into it.
mkdir minnie
cd minnie
There are a few files that Ghost requires you to have. Let's create those now.
touch index.hbs post.hbs package.json
It is also highly recommended to create a default.hbs page to act as the default layout for your theme. We'll do that now.
touch default.hbs
Next we'll create the asset folder structure that Ghost recommends.
mkdir assets
cd assets
mkdir scss js images fonts
We used scss because later on we'll write some scripts that will transform sass to css. For the sake of this tutorial it's best to just stick with scss. It's a minor change if you'd rather use css as the directory name, but I'll leave that up to you.
Now let's create some empty files in the scss and js directories.
touch scss/main.scss js/main.js
Whew! Break time
Now that we have our basic theme structure we'll add a few things to the pages so they have content when you finally install this theme.
Let's build a simple default.hbs file. It includes the html wrapper element and many more of the tags used to make a functioning webpage. It also has some of the required Ghost helpers. In order for our theme to be valid it must make use of the following built in helpers {{asset}}
, {{body_class}}
, {{post_class}}
, {{ghost_head}}
, and {{ghost_foot}}
.
Paste this into your default.hbs file.
<!DOCTYPE html>
<html lang="{{lang}}">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>{{meta_title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="{{asset "built/main.css"}}" />
{{ghost_head}}
</head>
<body class="{{body_class}}">
{{{body}}}
{{ghost_foot}}
<script type="text/javascript" src="{{asset "built/main.min.js"}}"></script>
</body>
</html>
Nice! We've knocked out 4 of the 5 required helpers!
Now we can move onto the first required file of our theme index.hbs. We're going to display a listing of our posts.
Add the following code to your index.hbs file.
{{!< default}}
{{#foreach posts}}
<article class="{{post_class}}">
<h2 class="post-title">
<a href="{{url}}">{{{title}}}</a>
</h2>
{{excerpt}}
</article>
{{/foreach}}
We use the {{#foreach 'posts'}}{{/foreach}}
helper to loop through the available posts and display the title and excerpt. The {{!< default}}
tag tells Ghost to insert everything from this file into the {body}
tag of the default.hbs template.
Finally let's build the simplest post.hbs page that we can. We'll display the title and the content of the post.
{{!< default}}
{{#post}}
<h1>{{title}}</h1>
{{content}}
{{/post}}
Alright! That's it for pages. Now it's time to configure our package.json file and begin building our theme.
Package.json
I'll break the config down into small sections. There is a lot of information that can go into the package.json file. We'll start with just a little more than what's needed. If you're planning on submitting your theme to the Ghost Marketplace be sure to follow their standards and check out the Casper theme for a more complex example.
We'll add the name of our template, a description, version number, and a license.
{
"name": "minnie",
"description": "Just what you need. Nothing more.",
"version": "0.0.2",
"license": "MIT"
}
Then let's add some information about the theme's author.
{
...
"author": {
"name": "Mark Garrigan",
"email": "mark@markgarrigan.com",
"url": "https://markgarrigan.com"
}
}
We'll include an engines field to indicate which version of Ghost your theme is compatible with.
{
...
"engines": {
"node": ">=8.0.0",
"ghost": ">=2.0.0",
"ghost-api": "v2"
}
}
We could leave scripts out of the file, but to make development a little easier we'll add a few that will help us.
{
...
"scripts": {
"css": "node-sass ./assets/scss -o ./assets/built",
"css:watch": "node-sass ./assets/scss -wro ./assets/built",
"postcss": "postcss ./assets/built/*.css -m --use autoprefixer -d ./assets/built/",
"js": "uglifyjs ./assets/js/* -c --source-map --output ./assets/built/main.min.js",
"js:watch": "onchange './assets/js/*.js' -v -- npm run js",
"predev": "npm run css & npm run js",
"dev": "npm run css:watch & npm run js:watch",
"build": "npm run predev",
"prezip": "npm run build",
"zip": "mkdir -p ./dist & git archive -o ./dist/minnie.zip HEAD",
"test": "gscan .",
"sweepmac": "echo \"Removing DS_Store files\" & find . -name '.DS_Store' -type f -delete"
}
}
The scripts are simple by design. Let's break them down and see how they can help you start developing right away.
- css, css:watch, postcss compiles sass then uses postcss to compress it and adds vendor prefixes. css:watch adds the ability to watch files for changes.
- js, js:watch combines and minifies your javascript files. js:watch watches javascript files for changes.
- zip creates an archive of your theme so that you can upload it to your Ghost installation.
- test will run gscan to check for errors and feature support.
- dev, build will both compile your code but dev also sets up file watching.
- sweepmac removes .DS_Store files from your project. I develop on a Mac and I'm one of those crazy people who use the Finder from time to time. That means that .DS_Store files will end up in my directories. If you're not on a mac just remove this script.
The scripts are meant to be expanded, changed, or removed. For example, if you don't use sass you can just completely remove the css scripts. If you do this, make sure to change your main.scss file to main.css and you'll probably want to rename the asset directory to css instead of scss.
We'll add one more bit to our package.json file. It's a little configuration for Ghost.
{
...
"config": {
"posts_per_page": 25,
"image_sizes": {}
}
"keywords": [
"ghost",
"theme",
"ghost-theme"
]
}
The config field is fairly self explanatory. image_sizes is used to help "the performance of your site by outputting images at different sizes depending on where they appear." The keywords field is another Ghost required field and should contain, at the minimum, ghost-theme.
devDependencies
In order to make those scripts work we'll need to install some packages. How you install your npm packages is up to you and out of the scope of this article. I'll show you how I do it, but feel free to do what you do.
pnpm i autoprefixer gscan node-sass onchange postcss-cli uglify-js --save-dev
The first p in pnpm is not a typo. Read about how pnpm gives you faster installations and saves you disk space.
Your completed package.json file should look something like this.
{
"name": "minnie",
"description": "Just what you need. Nothing more.",
"version": "0.0.2",
"license": "MIT",
"author": {
"name": "Mark Garrigan",
"email": "mark@markgarrigan.com",
"url": "https://markgarrigan.com"
},
"engines": {
"node": ">=8.0.0",
"ghost": ">=2.0.0",
"ghost-api": "v2"
},
"devDependencies": {
"autoprefixer": "^9.4.7",
"gscan": "^2.0.0",
"node-sass": "^4.11.0",
"onchange": "^5.2.0",
"postcss-cli": "^6.1.1",
"uglify-js": "^3.4.9"
},
"scripts": {
"css": "node-sass ./assets/scss -o ./assets/built",
"css:watch": "node-sass ./assets/scss -wro ./assets/built",
"postcss": "postcss ./assets/built/*.css -m --use autoprefixer -d ./assets/built/",
"js": "uglifyjs ./assets/js/* -c --source-map --output ./assets/built/main.min.js",
"js:watch": "onchange './assets/js/*.js' -v -- npm run js",
"predev": "npm run css & npm run js",
"dev": "npm run css:watch & npm run js:watch",
"build": "npm run predev",
"prezip": "npm run build",
"zip": "mkdir -p ./dist & git archive -o ./dist/minnie.zip HEAD",
"test": "gscan .",
"sweepmac": "echo \"Removing DS_Store files\" & find . -name '.DS_Store' -type f -delete"
},
"config": {
"posts_per_page": 25,
"image_sizes": {}
},
"keywords": [
"ghost",
"theme",
"ghost-theme"
]
}
Time to Build
You'll need to initialize a git repo inside your theme directory. You don't need to upload the repo to Github or Bitbucket, but the zip script uses git to produce the archive. As an added bonus you'll be using source control for your development!
git init
npm run dev
Add your styles, markup, and extra files. Along the way you should npm run test
to make sure your theme is not producing any errors. When the theme is ready to be zipped make sure you stage and commit your changes to git.
git add . -A
git commit -m 'commit message here'
Now you can archive your theme.
npm run zip
Activate Your Theme
Login to your Ghost admin, go to the Design settings, upload your theme's zip file, and activate it.
Summary
We've built an overly simple Ghost theme with a few tools to help you in development. markgarrigan.com started out as this barebones theme and evolved into the super complex site you see now. ;)