Create a Ghost Theme

A guide to setting up a dev environment for creating a custom Ghost theme with my ghost-theme-starter. Easily and quickly compile and minify CSS and JS assets, start a live reload server, and zip up your theme files.

by Ryan Feigenbaum

Create a Ghost Theme

Share this post

Create a Ghost Theme

Create a Ghost Theme

In this guide, I'll show you how I set up my dev environment for creating a custom Ghost theme. The key part of this environment is Rollup, which I use to compile, bundle, and minify my JavaScript and CSS files.

My Rollup config–in addition to some other goodies like a script to start a LiveReload server and one to zip theme files–are available as a template on GitHub. The template includes the basic Handlebars files needed for a Ghost theme, Rollup, PostCSS, basic JS and CSS, along with some explanatory comments. The template builds off of the official Ghost theme Starter.

Below, I walk through the template and show how to use it.

GitHub - royalfig/ghost-theme-starter
Contribute to royalfig/ghost-theme-starter development by creating an account on GitHub.
My Ghost theme starter is a work in progress. I'll be updating and adding to it, so check back often. Contributions are also welcome!


This guide assumes you have a local version of Ghost installed. If you don't, the official documentation should help you get set up. You'll need some basic familiarity with the command line and a code editor like VS Code.

If you've never built a Ghost theme before, you'll want to read through the docs to understand how the template layer works. You can also look at my custom themes, Smart and Mel, or the default Ghost theme, Casper, to see real-world examples.

If you're brand new to building Ghost themes and would like more articles walking you through the basics–let me know.

Get the Template

Go to my ghost-theme-starter and click Use the template. (You can also just clone the repo directly.) This will create a copy of the template in your GitHub account. You can then clone the repo to your local machine.

Go to the directory where you installed the theme starter and install dependencies with npm install. Then, create a symlink from the theme starter to your Ghost's themes folder ( content/themes). The symlink allows you to work on your theme in a folder that's independent of your Ghost install, yet still links to it directly. This way you can reinstall Ghost without having to worry about your theme files or upload a production version of the theme to your local install without worrying about overwriting your local development copy.

# symlink your theme to your local Ghost install
ln -s path-to-ghost-theme-starter ghost-install/content/themes
Create a symlink between your theme and the Ghost themes folder

Run ghost restart so Ghost recognizes your new theme. Activate your theme and you're off to the races.

Make It Pretty

Styles live in src/css, and files are organized into a 7-1 architecture. While the 7-1 architecture was originally made popular with SASS, it's used here with several plain CSS files split across seven categories with one main app.css file. I find that CSS has evolved enough with custom properties and PostCSS that SASS is now unnecessary and sometimes more of a burden than a convenience.

Folder Description Example
abstracts/ Variables used across the project variables.css
base/ Base styles for the project reset.css
layout/ Elements found on every page navbar.css
pages/ Styling for particular templates like post.hbs or home.hbs post.css
components/ Components used frequently like buttons or forms buttons.css
vendor/ Styling for 3rd party elements ghost.css
themes/ Multiple color or typography schemes Not used

Use the command npm run dev to watch for changes in src/css, compile your CSS, and provide a sourcemap, so when you inspect your styles in the dev console, you'll see the exact file and line of code responsible for breaking your design.

In production, initiated with npm run zip, stylelint will lint your CSS, automatically ordering properties according to a recess logic. CSS will be autoprefixed and polyfilled with postcss-preset-env (like a Babel for CSS). One example of this polyfill is automatically writing fallbacks for custom properties. Finally, the CSS is minified and optimized with cssnano. It's just up to you to ship those styles 🛳️.

PostCSS - a tool for transforming CSS with JavaScript
Transform CSS with the power of JavaScript. Auto-prefixing, future CSS syntaxes, modules, linting and more are possible with hundreds of PostCSS plugins.

Put It to Work

Every KB counts

For JS, the story's the same. Rollup compiles your JS files in src/js. What's different here is that there are two different output bundles: app.js and post.js. App.js is loaded globally on every page, whereas post.js is only loaded on posts and pages. The thinking here is that you likely have scripts that are only needed on posts and pages (like responsive embeds) that aren't needed on index pages, so you can reduce your homepage's bundle size by excluding this code from the app's main JS file.

Babel is also active to help compatibility across browsers along with a handful of other Rollup plugins to make it easier to work with next-gen JS and third-party libraries.

Reload It Like It's Hot

Rollup has a built-in ability to watch your files and compile them when you save edits. The problem is those changes won't show up in your browser without a hard refresh.

The theme starter template uses a custom implementation of LiveReload to automatically refresh your browser whenever you make changes to your CSS or JS. This makes it easy to see your changes in real-time, without having to lift a finger.

This reloading is limited to JS and CSS changes and not to Handlebars files. If you make a change to a template file, you're stuck having to reload the browser. (It may be possible to tell LiveReload to watch for changes to .hbs files, too, but I haven't got it to work yet.) You also need to restart your Ghost instance if you add a new template file altogether.

If your styles or scripts aren't reloading, ensure that you've disabled the cache in the browser. Open your dev tools, go to the network tab, and toggle the Disable cache option

Zip, Zip, and Away

The final piece of this puzzle is a custom script for zipping your files up into a file that is suitable to upload to Ghost. If you add new template files, you'll need to adjust zip.js to include them.

It's also possible, instead of zipping your files and uploading yourself, to just use Ghost's Github action, which will automatically deploy your theme whenever you push a change to your repo's main remote branch.

The ghost-theme-starter will set you up with a great working environment to build your next Ghost theme. Try it out and let me know what you think.