[email protected]
HANDLERBARS
JAVASCRIPT
SEPT 14,2024

Extending Capability in Ghost

Custom Templating for Dynamic Content

If you’ve ever used Ghost, you’re probably familiar with how amazing the framework is. However, despite its strengths, it lacks some key quality-of-life (QOL) features. One notable missing feature is the ability to customize posts individually. Ghost does not support custom settings or attributes that are specific to each post, configurable from the admin panel, and accessible within the theme context

Ghost Post Writeup
Ghost Post Writeup Screen

This limitation can be frustrating for users who need more flexibility when tailoring content on a per-post basis

Current Workarounds and Their Drawbacks

Searching across forums reveals one thing: the Ghost team is still looking into how to implement this feature and extend functionality. In the meantime, users are left with two options: either keep the customization within a limited scope or use JavaScript injection to make modifications

Ghost JS Injection
Ghost JS Injection Code Snippet

However, this approach has significant drawbacks:

  1. Loss of Server-Side Rendering– Users will notice flashes of the original content before the JavaScript is executed.
  2. Not User-Friendly– This method isn’t ideal for non-developers, and it offers limited extensibility for future enhancements.

A New Approach: Custom Templates Inside Ghost

With no viable solution in sight, I decided to find my own way to implement it. I embarked on creating a custom template system inside Ghost.

Yes, you heard that right—a template language within a template engine, where Ghost uses our custom logic to parse content.

But the question remains: how can this be done?

Custom Helpers in Ghost

Ghost uses Handlebars for templating to render content. Handlebars has a concept called Helper Functions—JavaScript functions that allow you to create custom templates with dynamic content. Ghost itself utilizes many of these, as outlined in their official documentation

So, what if we could write our own helper that parses the document and makes it available to the theme? That’s exactly what we’re going to do.

Implementing Custom Templating

To streamline the process and save you from hours of debugging, I’ve created a custom templating engine code. Follow these steps to integrate it into Ghost:

  1. Navigate to Your Ghost Installation Folder Go to current/core/frontend/helpers
  2. Replace raw.js Replace the contents of the raw.js file with the custom code provided below.
  3. Restart Ghost : After replacing the file, restart Ghost to apply the changes
const _ = require('lodash');

function extractAndMergeQuoteTags(content) {
    const regex = /{{GHOST-(\w+)\s*([\s\S]*?)}}\s*([\s\S]*?)\s*{{\/GHOST-\1}}/g;
    const results = {};

    let match;
    while ((match = regex.exec(content)) !== null) {
        const [ , tag, attributesString, innerContent ] = match;
        const attributes = {};
        const attrRegex = /(\w+)\s*=\s*({[\s\S]*}|\[[\s\S]*\]|"[^"]*")/g;
        let attrMatch;
        while ((attrMatch = attrRegex.exec(attributesString)) !== null) {
            const [, key, value] = attrMatch;
            try {
                attributes[key] = JSON.parse(value);
            } catch (e) {
                attributes[key] = value.replace(/\"/g, '"'); 
            }
        }
        if (!results[tag]) {
            results[tag] = {
                attributes: {},
            };
        }
        _.assign(results[tag].attributes, attributes);
    }
    return results;
}

module.exports = function raw() {
    let self = this;
    const serialize = extractAndMergeQuoteTags(self.html);
    return serialize;
}
Restart Ghost
Restart Ghost

Let’s Test the Templating

After integrating the custom code, it’s time to test whether the templating is working as expected

Ghost Post
Create a new Post with following Custom Content
Ghost Post Preview
Previewing Ghost Post with Custom Content

We still don’t see the changes, it’s because the template hasn’t been updated to use the custom styling yet. To fix this:

  1. Download the Theme Go to Settings > Appearance and download the theme as a ZIP file
    Download Theme
    Download Theme
  2. Extract the ZIP File: Open the extracted theme folder in your favorite editor
    Extract Theme
    Extract and Open Theme Folder in Editor of Your Choice
  3. Go to Post.hbs file and insert the following line of code Content section :
    <section class="gh-content gh-canvas is-body{{#if @custom.enable_drop_caps_on_posts}} drop-cap{{/if}}">
        {{#with (raw)}}
        <h1>
          {{custom.attributes.Title}}
        </h1>
        <div>
            {{#each custom.attributes.data.items}}
            <span
             style="background-color: #e0e0e0; color: #333; padding: 5px 10px; border-radius: 20px; font-size: 14px; display: inline-block;">
          {{this}}
            </span>
            {{/each}}
        </div>
        {{/with}}
        {{content}}
    </section>
  4. Upload the theme - Zip the folder and upload it to Ghost by going to Settings > Design > Upload a theme and restart page
    Ghost New Templating
    We can now see the custom content rendered in the post

Updating Content Rendering

We can now render data dynamically from the content, but the old templating is still visible. To complete the setup, we need to remove the old templating and update the content rendering

  1. Navigate to current/core/frontend/helpers: Locate the Content.js file in this directory
  2. Replace Content.js : Replace the content of Content.js with the following code:
    
    // # Content Helper
    // Usage: `{{content}}`, `{{content words="20"}}`, `{{content characters="256"}}`
    //
    // Turns content html into a safestring so that the user doesn't have to
    // escape it or tell handlebars to leave it alone with a triple-brace.
    //
    // Shows default or custom CTA when trying to see content without access
    //
    // Enables tag-safe truncation of content by characters or words.
    //
    // Dev flag feature: In case of restricted content access for member-only posts, shows CTA box
    
    const {templates, hbs, SafeString} = require('../services/handlebars');
    
    function removeTemplate(content) {
        const regex = /{{GHOST-[\w-]*[\s\S]*?}}[\s\S]*?{{\/GHOST-[\w-]*}}/g;
        content = content.replace(regex, '');
        return content;
    }
    
    const downsize = require('downsize');
    const _ = require('lodash');
    const createFrame = hbs.handlebars.createFrame;
    
    function restrictedCta(options) {
        options = options || {};
        options.data = options.data || {};
    
        _.merge(this, {
            // @deprecated in Ghost 5.16.1 - not documented & removed from core templates
            accentColor: (options.data.site && options.data.site.accent_color)
        });
    
        const data = createFrame(options.data);
        return templates.execute('content-cta', this, {data});
    }
    
    module.exports = function content(options = {}) {
        let self = this;
        let args = arguments;
    
        const hash = options.hash || {};
        const truncateOptions = {};
        let runTruncate = false;
    
        for (const key of ['words', 'characters']) {
            if (Object.prototype.hasOwnProperty.call(hash, key)) {
                runTruncate = true;
                truncateOptions[key] = parseInt(hash[key], 10);
            }
        }
    
        if (this.html === null) {
            this.html = '';
        }
    
        if (!_.isUndefined(this.access) && !this.access) {
            return restrictedCta.apply(self, args);
        }
    
        if (runTruncate) {
            return new SafeString(
                downsize(this.html, truncateOptions)
            );
        }
        return new SafeString(removeTemplate(this.html));
    };
    
Preview Link
Previewing Ghost Template with our own Custom templating

and there we have it , we can now save it as a Template and use as a custom template for our Ghost Posts in the future

Creating Template
Creating Template and saving it as Template
Template Preview
Template Preview
Complete Preview
Viewing Preview of Custom Template

With these updates, we now have a reusable component that handlesdynamic data and offers full access to styling. This approach allows you to integrate custom components into Ghost seamlessly, enhancing the flexibility and functionality of your site. By following these steps, you’ve not only added dynamic content capabilities but also customized your Ghost theme to fit your needs. Enjoy the new level of customization!


Were My Blogs Beneficial to You ?
Subscribe to My Newsletter , Get Notified Whenever I post new Blogs
Loading...