Tips and tricks for Ghost, self-hosted, using CSS, javascript and handlebars

Self-hosted Ghost tips and tricks

Containers May 28, 2022

Ghost is the platform this website is created on. Frankly, it's amazing. It works right out the box, and a little tinkering will get you a theme you're happy with. But sometimes we want different functionality in our pages, or we want to make the theme a little more 'me'.

Below are some of the things I've added to this site, and I'll update this page as and when I can.

Knowing where to put the code

This can be daunting to start with, so I'm going to show you the main locations you may need to paste or create some script or code snippets.

Code Injector

The settings page
Under the hood of the GUI

From the settings page, you want to find and click on Code Injection.

The Site Header
The Code Injection page

This page has two code blocks you can input code. The first is the Site Header, while the second (predictably) is the Site Footer. These are not really interchangeable, so make sure you pay attention to where the instructions tell you to put your code.

Also notice that the header and footer show that the code is injected into the {{ghost_head}} or {{ghost_foot}} respectively. This is good to know if you decide to inject code into the default.hbs file instead, and I'll show you where that is now.


This file is located inside your theme's main folder (Casper, Editorial, whatever it may be). It is what tells your web pages to look the way they do. The top of the document would look something like this:

inside your default.hbs

Remember the {{ghost_head}} I mentioned above? There it is. Everything above that in the file is the header, and where the Code Injector puts anything you've written in it. This means that you also can use this file instead of the code injector, and if for any reason you put some code in but get duplication errors or anything else, you now know where to look to see what's what.

Similarly, if you scroll down to the bottom of the default.hbs you'll see something like this:

the foot of your default.hbs

As it says, the {{ghost_foot}} should be the last thing before the closing </body> tag, so you put the footer code just above it.

Other theme files

You may want some style choices to only appear on a particular type of page, like a post or an index. In those cases you'd add the code directly to those, whether it's the post.hbs or home.hbs etc.

Javascript and CSS files

Sometimes you will need to copy paste a javascript file into your theme so it can be referenced in a header or footer script. You may also want to know where your main css file is to make very specific, detailed tweaks to how something looks or reacts. These can both be found inside your /assets/main folder in your theme:

A word on updating these files

Just saving any changes to these files won't automatically update what you see in your browser. The ghost service (whether it's a docker container or otherwise) needs to be restarted for these changes to take effect.

And with that out of the way, let's check out what we can do!

Clicking a link inside your site, by default, will navigate you away from the page you're on and into the next site. This can be undesirable, so the following code will recognise whether a link is part of your domain or not, and depending on that will open external links in another tab.

Checking our content wrapper

Inside your theme folder you should have a file called post.hbs. Open that, and find a handlebars {{ }} line that says {{content}}:

the class specifier for your post's contents

Take note of what the class is called. In my case, it's called content, but for you it may be something like post-content or something else entirely.

The Code

Locate your default.hbs file inside your theme, and copy paste the following to just above {{ghost_foot}}:

    {{!-- Open external links in a new tab --}}
    var links = document.querySelectorAll('.content a');  
    for (var i = 0, length = links.length; i < length; i++) 
        if (links[i].hostname != window.location.hostname) 
            links[i].target = '_blank';
            links[i].rel = 'noopener';
Make sure the ('.content a') is correct for you

Make sure that the .querySelectorAll class identifier matches the class we found above. This means if your class was post-content then your snippet above would look like var links = document.querySelectorAll('.post-content a');.

Safe the file, now when you reload your container, any external link should open in a new tab while keeping all internal links and anchors as they were.

Lightboxes for your images

This allows you to zoom into the images you put in your posts without leaving the page. As an example, click the image below:

Nifty. The way we do this is as follows:

  1. Click this link and click the 'Download' button
  2. Extract the zip once it's downloaded
  3. Locate the fslightbox.js file, and copy paste it into the assets/main/js folder of your Ghost theme's folder structure
  4. Locate your default.hbs file, and copy paste the following so it sits just above {{ghost_foot}} :
    {{!-- Lightbox --}}
    <script async src="{{asset "main/js/fslightbox.js"}}"></script>
    const images = document.querySelectorAll('.kg-image-card img, .kg-gallery-card img');

    // Lightbox function
    images.forEach(function (image) {
    var wrapper = document.createElement('a');
    wrapper.setAttribute('data-no-swup', '');
    wrapper.setAttribute('data-fslightbox', '');
    wrapper.setAttribute('href', image.src);
    wrapper.setAttribute('aria-label', 'Click for Lightbox');
    image.parentNode.insertBefore(wrapper, image.parentNode.firstChild);

Pay attention to the asset location in the first script
note that the location of the fslightbox.js file must be accurate. If you have simply put the file into the /assets/main folder, then you would change the handlebars above to be "{{asset "main/fslightbox.js"}}"

Now you can save your default.hbs and restart the container.

Some notes:

If it doesn't work, right click inside your browser and click inspect to see if there are any errors that come up.

For instance, I already had images defined (where it says const images = and images.forEach) in a different script. I got around this by simply defining them as something else, in my case imageslb (L.B. so I remembered that it was specifically for LightBox, but it can be anything you want).

You may also experience an error saying Uncaught ReferenceError: refreshFsLightbox is not defined at (index):[numbers]. I'm not totally sure why, but it doesn't seem to impede the functionality of the lightbox.

If there's anything you've been trying to do on your site that you'd like me to put here, let me know in the comments below.


With very limited knowledge, PTS fell down the selfhosted rabbit hole after buying his first NAS in October 2020. You can find him on the Synology discord server (click the icon below)

Have some feedback or something to add? Comments are welcome!

Please note comments should be respectful, and may be moderated