Library / SDK
pillarjs/hbs avatar
pillarjs/hbs

hbs for Express: Handlebars rendering, partials, and runtime controls

Express view engine wrapper for Handlebars

1,684 stars203 forksJavaScriptMIT

At a glance

What is it?
hbs connects Handlebars templates to Express through a small wrapper that registers the view engine, loads partials, and exposes compile and runtime options. It fits applications that already use Express and need `.hbs` rendering, but its permissive prototype defaults require attention before inherited properties are used.
Who is it for?
Adopt hbs when an Express application needs Handlebars views, filesystem partials, or separate Handlebars instances. Do not choose it as a standalone template compiler or as a boundary for untrusted templates or context data.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why hbs belongs in an Express application

hbs solves a narrow integration problem: Express expects an application-level view engine, while Handlebars supplies template compilation and rendering behavior. hbs exposes the adapter through `__express`, allowing Express to render Handlebars templates through its normal `res.render` path. The target audience is Node.js developers who already have an Express application and want `.hbs` files without building the integration themselves.

The package is intentionally described as a thin wrapper. Its dependencies are `handlebars` and `walk`; the package metadata identifies `lib/hbs.js` as the main entry point. That narrow scope is useful. It keeps the project focused on registration, partial loading, locals exposure, and option handling rather than introducing a separate template language or application framework.

The limitation follows from that design. hbs does not define an application architecture, provide a visual interface, or replace Express. It also does not make untrusted templates safe. The README explicitly says to treat both templates and template context as trusted input. Applications that need a full rendering platform, a non-Express integration, or a security boundary for user-controlled templates should look elsewhere or add a separate design.

The Express rendering path in hbs

The smallest configuration registers the engine with Express. Once `app.set('view engine', 'hbs')` is set, calls to `res.render` use `.hbs` files according to Express view resolution. For a different file extension, the application registers `require('hbs').__express` under the chosen extension.

The wrapper also exposes selected Handlebars functionality. `registerHelper` and `registerPartial` register helpers and partials through Handlebars. `registerPartials` walks a directory and assigns partial names from filenames. Spaces and hyphens become underscores, so `template.html` becomes `template` and `template-file.html` becomes `template_file`. A `rename` function can replace other non-word characters with underscores.

Another feature bridges Express locals and template data. After `hbs.localsAsTemplateData(app)` receives the application instance, application and request locals can be referenced with `@property` syntax. In partials and templates, local data can also be accessed without the `@` prefix. This is a convenience for exposing framework-level values throughout views, but it also makes it important to review which locals are placed on the Express application or request.

Installing hbs and registering the first view engine

The README directs users to install the package through npm. The package metadata lists version 4.3.1, Node.js compatibility of `>= 0.8`, and npm compatibility of `1.2.8000 || >= 1.4.16`. The installation command is:

bash
$ npm install hbs

In an Express application, register hbs as the default view engine during application setup:

javascript
app.set('view engine', 'hbs');

After this configuration, a view file with the `.hbs` extension can be selected through Express's normal view resolution and rendered with `res.render`. The README does not provide a complete application file, a template, or a rendering response example, so the exact directory and route names depend on the surrounding Express project.

If the application uses `.html` files instead, register the hbs adapter manually:

javascript
app.set('view engine', 'html');
app.engine('html', require('hbs').__express);

This second configuration is useful when the project already uses HTML as its view extension but wants Handlebars compilation. It does not change how Express resolves views or how application locals are supplied.

Loading partials and exposing application locals

A directory of partials can be registered asynchronously. The callback receives an error argument, while the directory traversal runs without blocking application startup. The following example loads partials from `views/partials`:

javascript
var hbs = require('hbs');

hbs.registerPartials(__dirname + '/views/partials', function (err) {});

The filename mapping is deterministic but opinionated. A file named `login view.hbs` is registered as `login_view`, and `template 2.html` as `template_2`. When that mapping does not fit a project, `rename` receives the relative path without the extension. For example, the following function replaces all non-word characters with underscores:

javascript
var hbs = require('hbs')

hbs.registerPartials(path.join(__dirname, '/views/partials'), {
  rename: function (name) {
    return name.replace(/\W/g, '_')
  }
})

For application-wide values, `localsAsTemplateData` connects Express locals to template context:

javascript
var hbs = require('hbs');
var express = require('express');

var app = express();
hbs.localsAsTemplateData(app);

app.locals.foo = "bar";

Templates can then use `{{@foo}}`. The README notes that partials and templates can access local data without the `@` prefix. This feature reduces repeated context plumbing, although applications should avoid placing sensitive request values into locals that every template can reach.

Compile options and isolated hbs instances

Handlebars compilation options can be applied per hbs instance. The documented `create()` function creates an isolated instance whose options apply to every view and layout compiled by that instance. This is useful when different parts of an Express application need different Handlebars behavior.

The README gives `strict` as the example:

javascript
var hbs = require('hbs');

var instance = hbs.create(null, {
  compileOptions: { strict: true }
});

app.engine('hbs', instance.__express);

Compile options include `strict`, `noEscape`, and `preventIndent`. The `noEscape` setting disables HTML escaping for every `{{expression}}` rendered by the instance. That setting should remain limited to trusted data because it removes an important output protection.

The options are also available as a mutable property:

javascript
hbs.compileOptions = { noEscape: true };

The README states that changing this property does not affect templates already in the cache. That cache behavior creates a deployment consideration: changing options during startup is different from changing them after views have already been compiled.

The `hbs.handlebars` property exposes the Handlebars module. The README specifically directs code using methods such as `SafeString` to access that property, while warning against registering helpers or partials through it.

Prototype access is the main security decision

hbs uses Handlebars, so Handlebars prototype-access controls apply. The package documentation identifies a compatibility difference that deserves attention. In hbs 4.x, `allowProtoMethodsByDefault` and `allowProtoPropertiesByDefault` are set to `true`, while Handlebars defaults them to `false`. As a result, inherited methods and properties may be readable by default in templates rendered by hbs.

If templates do not require inherited members, the README shows how to restore the stricter defaults:

javascript
var hbs = require('hbs');

var instance = hbs.create(null, {
  runtimeOptions: {
    allowProtoMethodsByDefault: false,
    allowProtoPropertiesByDefault: false
  }
});

app.engine('hbs', instance.__express);

For a smaller allowance, Handlebars allowlists can name known inherited members. This preserves the disabled defaults while permitting specific fields or methods. The README also documents changing `hbs.runtimeOptions` later, with the same cache-related distinction described for compile options.

This is not permission to render arbitrary context. The documentation says that templates and template context should be treated as trusted input regardless of these settings. Enabling `noEscape` has a separate risk because it disables HTML escaping for every expression compiled by the instance. An application that handles user-generated HTML should keep escaping enabled and define a reviewed path for intentionally safe markup.

Maintenance, packaging, and the boundary with alternatives

The repository is not archived, and the last push was on 2026-10-15. Recent releases listed for the project include v4.3.1 on 2026-09-11, v4.3.0 on 2026-08-25, and v4.2.1 on 2026-04-01. The package metadata declares an MIT license, so its use is governed by the repository's MIT terms, subject to the license text and applicable obligations.

The maintenance cost appears low for an application that uses only the documented wrapper behavior. The public API is compact, the package publishes `lib/`, `HISTORY.md`, `LICENSE`, and `README.md`, and the package pins Handlebars 4.7.10 plus `walk` 2.3.15. The project is primary JavaScript and includes lint, test, coverage, and CI scripts, although this article does not claim that those scripts were executed.

A real alternative is to invoke Handlebars directly in a custom Express adapter or use a different Express view engine. Direct Handlebars gives the developer control over compilation and rendering, but it also requires implementing Express integration and any caching, partial, or error-handling behavior that the application needs. hbs reduces that setup work while retaining compatibility-sensitive prototype defaults. The trade-off is clear: hbs saves integration code, but adopters inherit its option policy and need to verify it against their own template and context model.

Editorial conclusion

Adopt hbs when an Express application needs Handlebars views, filesystem partials, or separate Handlebars instances. Do not choose it as a standalone template compiler or as a boundary for untrusted templates or context data. Verify the installed Handlebars version, prototype-access policy, template locations, escaping needs, and expected maintenance of the application before adoption. The last push was on 2026-10-15.

Frequently asked questions

How do I install and configure hbs in an Express application?

Install it with `npm install hbs`, then set `app.set('view engine', 'hbs')` in the application setup. For a different extension, register `require('hbs').__express` with `app.engine`.

How does hbs load partials from a directory?

Call `registerPartials` with a directory and callback. The directory is walked asynchronously, and filenames become partial names with spaces and hyphens replaced by underscores; a `rename` function can customize the mapping.

Does hbs use Handlebars' default prototype-access settings?

No. For backward compatibility, hbs 4.x sets `allowProtoMethodsByDefault` and `allowProtoPropertiesByDefault` to `true`. The README documents setting both to `false` unless templates need inherited members.

Official sources

  1. Issues
  2. License: MIT
  3. pillarjs/hbs on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pillarjs-hbs.svg)](https://hysenlabs.com/projects/pillarjs-hbs)

Was this useful?