# How Ghost Frontend Helpers Work: A Complete Guide to Creating Custom Handlebars Helpers

> Learn how Ghost frontend helpers work and build custom Handlebars helpers using ghost.helperService. This guide covers registering helpers for your Ghost site.

- Repository: [Ghost/Ghost](https://github.com/TryGhost/Ghost)
- Tags: deep-dive
- Published: 2026-05-18

---

**Ghost uses a centralized registry system to manage Handlebars helpers, exposing `ghost.helperService` methods that allow apps to register synchronous or asynchronous custom helpers individually or by directory.**

Ghost's theming engine relies on **Handlebars** to render dynamic content, providing a robust set of built-in helpers like `{{title}}` and `{{url}}`. The platform exposes a clean API through `ghost.helperService` that enables developers to extend functionality by creating custom Handlebars helpers. Understanding this architecture allows you to safely modify template behavior without hacking core files.

## Core Helper Architecture

The helper system is built around three specialized modules in `ghost/core/core/frontend/services/helpers/`:

- **[`registry.js`](https://github.com/TryGhost/Ghost/blob/main/registry.js)** – Maintains an in-memory map of helper names to functions and handles directory scanning logic.
- **[`handlebars.js`](https://github.com/TryGhost/Ghost/blob/main/handlebars.js)** – A thin wrapper that forwards registration calls to the global Handlebars instance used by Ghost.
- **[`index.js`](https://github.com/TryGhost/Ghost/blob/main/index.js)** – The public façade exposed as `ghost.helperService`, supplying `registerHelper`, `registerAlias`, `registerDir`, and an `init` routine.

According to the TryGhost/Ghost source code, this separation of concerns ensures that the registration API remains stable while the underlying Handlebars integration can evolve independently.

## How Ghost Loads Built-in Helpers

When the Ghost frontend starts, the helper initialization follows a strict sequence:

1. **`ghost.helperService.init()`** is called, which builds the absolute path to `core/frontend/helpers`.
2. **`registry.registerDir`** scans the directory using `globSync('!(index).js')` to exclude index files.
3. Each helper file is `require`d, and **`registerHelper`** stores the function in the internal registry object.
4. Registration detects async capabilities: if `helperFn.async === true`, Ghost calls `handlebars.registerAsyncThemeHelper`; otherwise it uses `handlebars.registerThemeHelper`.

The [`handlebars.js`](https://github.com/TryGhost/Ghost/blob/main/handlebars.js) wrapper delegates to the global instance with minimal overhead:

```javascript
module.exports.registerThemeHelper = function registerThemeHelper(name, fn) {
    hbs.registerHelper(name, fn);
};

module.exports.registerAsyncThemeHelper = function registerAsyncThemeHelper(name, fn) {
    asyncHelperWrapper(hbs, name, fn);
};

```

## Creating Custom Handlebars Helpers

You can add custom helpers through the `ghost.helperService` API using two distinct approaches.

### Register a Single Helper

Call **`ghost.helperService.registerHelper(name, fn)`** from any app's activation code to register individual functions:

```javascript
// app activation code
ghost.helperService.registerHelper('myHelper', function(options) {
    // `this` represents the template context
    const name = (this.author && this.author.name) || 'Guest';
    return `Welcome, ${name}!`;
});

```

### Register a Directory of Helpers

Place multiple helper files in a folder and call **`ghost.helperService.registerDir(path)`** to auto-load them:

```javascript
// my-app/index.js
const path = require('path');

module.exports = {
    activate(ghost) {
        ghost.helperService.registerDir(
            path.resolve(__dirname, './lib/helpers')
        );
    }
};

```

Each `.js` file in the directory should export a single function. Ghost automatically derives the helper name from the filename (e.g., [`my-helper.js`](https://github.com/TryGhost/Ghost/blob/main/my-helper.js) becomes `{{my-helper}}`).

### Creating Async Helpers

Ghost automatically detects async functions and wraps them for safe template usage. Simply export an async function or set `fn.async = true`:

```javascript
// Async helper that fetches data
ghost.helperService.registerHelper('fetchAuthor', async function(options) {
    // this refers to the post object
    const author = await this.author();
    return author.name;
});

```

Because the function is async, Ghost registers it via `registerAsyncThemeHelper`, allowing the template to render without blocking while the promise resolves.

## Built-in Helper Examples

Ghost ships with numerous built-in helpers located in `ghost/core/core/frontend/helpers/`. For example, **[`title.js`](https://github.com/TryGhost/Ghost/blob/main/title.js)** returns a safely escaped string:

```javascript
const {SafeString} = require('../services/handlebars');

module.exports = function title(options) {
    const title = this.title || '';
    return new SafeString(title);
};

```

The **private-blogging app** ([`ghost/core/apps/private-blogging/index.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/apps/private-blogging/index.js)) demonstrates real-world usage of `registerDir` to inject authentication-related helpers.

## Summary

- Ghost maintains a **registry-based architecture** that decouples helper storage from Handlebars registration.
- Built-in helpers auto-load from `core/frontend/helpers` via `globSync` pattern matching.
- Apps register custom helpers via **`registerHelper`** (single) or **`registerDir`** (bulk), accessible through `ghost.helperService`.
- **Async helpers** are automatically detected by the `async` property or Promise return type and wrapped for non-blocking template execution.
- Custom helpers become immediately available in themes as `{{helperName}}` once registered during app activation.

## Frequently Asked Questions

### How do I register a custom helper in Ghost?

Register a custom helper by calling `ghost.helperService.registerHelper(name, func)` inside your app's `activate` method. Alternatively, use `registerDir(path)` to load an entire folder of helper files automatically.

### What is the difference between sync and async helpers in Ghost?

Synchronous helpers execute immediately and return strings or `SafeString` objects. Async helpers return Promises and are wrapped by Ghost's `asyncHelperWrapper` to prevent blocking template rendering while data loads.

### Where should I place custom helper files in a Ghost app?

Store helper files in a subdirectory of your app (commonly `lib/helpers/`), ensuring each file exports a single function. Ghost derives the helper name from the filename, converting dashes to camelCase where appropriate.

### Can themes register custom helpers or only apps?

Only apps can register custom helpers using `ghost.helperService`. Themes cannot directly register helpers; they must rely on built-in helpers or helpers provided by active apps installed on the Ghost instance.