{"id":16883953,"url":"https://github.com/tryghost/express-hbs","last_synced_at":"2026-04-02T18:45:39.569Z","repository":{"id":5155226,"uuid":"6323414","full_name":"TryGhost/express-hbs","owner":"TryGhost","description":"Express handlebars template engine with inheritance, partials, i18n and async helpers.","archived":false,"fork":false,"pushed_at":"2025-05-01T17:02:07.000Z","size":1649,"stargazers_count":462,"open_issues_count":22,"forks_count":78,"subscribers_count":27,"default_branch":"main","last_synced_at":"2025-05-08T08:24:10.917Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"codemix/oriento","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TryGhost.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"tryghost","open_collective":"ghost"}},"created_at":"2012-10-21T17:56:49.000Z","updated_at":"2025-04-21T12:50:13.000Z","dependencies_parsed_at":"2024-01-13T12:22:24.265Z","dependency_job_id":"e4d4a489-909d-490f-9d0e-a133674a02f7","html_url":"https://github.com/TryGhost/express-hbs","commit_stats":{"total_commits":539,"total_committers":43,"mean_commits":"12.534883720930232","dds":0.7031539888682745,"last_synced_commit":"f86d3f920cc2620630e04cb7cf26204f149f5646"},"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TryGhost%2Fexpress-hbs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TryGhost%2Fexpress-hbs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TryGhost%2Fexpress-hbs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TryGhost%2Fexpress-hbs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TryGhost","download_url":"https://codeload.github.com/TryGhost/express-hbs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254028933,"owners_count":22002282,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T16:15:52.429Z","updated_at":"2026-04-02T18:45:39.561Z","avatar_url":"https://github.com/TryGhost.png","language":"JavaScript","funding_links":["https://github.com/sponsors/tryghost","https://opencollective.com/ghost"],"categories":[],"sub_categories":[],"readme":"# express-hbs\n\nExpress handlebars template engine with multiple layouts, blocks and cached partials.\n\n## v2.0.0\n\nVersion 2 was a rewrite and cleanup, with no known breaking changes. Lots of bugs were fixed which may have subtly changed behaviour.\n\nFull details: https://github.com/TryGhost/express-hbs/releases/tag/2.0.0\n\n## v1.0.0 Breaking Changes\n\nIf you're upgrading from v0.8.4 to v1.0.0 there are some potentially breaking changes to be aware of:\n\n1. Handlebars @v4.0.5 - please see the [handlebars v4.0 compatibility notes](https://github.com/wycats/handlebars.js/blob/master/release-notes.md#v400---september-1st-2015)\n2. The file extension for partial files must now match the extension configured in `extname` - please see [the PR](https://github.com/TryGhost/express-hbs/pull/88)\n\n## Usage\n\nTo use with express 4.\n```js\nvar hbs = require('express-hbs');\n\n// Use `.hbs` for extensions and find partials in `views/partials`.\napp.engine('hbs', hbs.express4({\n  partialsDir: __dirname + '/views/partials'\n}));\napp.set('view engine', 'hbs');\napp.set('views', __dirname + '/views');\n```\nTo use with express 3 is the same as above, except use hbs.express3\n\n```js\napp.engine('hbs', hbs.express3({\n  partialsDir: __dirname + '/views/partials'\n}));\n```\n\nOptions for `#express3` and `#express4`\n\n```js\nhbs.express4({\n  partialsDir: \"{String/Array} [Required] Path to partials templates, one or several directories\",\n\n  // OPTIONAL settings\n  restrictLayoutsTo: \"{String} Absolute path to a directory to restrict layout directive reading from\",\n  blockHelperName: \"{String} Override 'block' helper name.\",\n  contentHelperName: \"{String} Override 'contentFor' helper name.\",\n  defaultLayout: \"{String} Absolute path to default layout template\",\n  extname: \"{String} Extension for templates \u0026 partials, defaults to `.hbs`\",\n  handlebars: \"{Module} Use external handlebars instead of express-hbs dependency\",\n  i18n: \"{Object} i18n object\",\n  layoutsDir: \"{String} Path to layout templates\",\n  templateOptions: \"{Object} options to pass to template()\",\n  beautify: \"{Boolean} whether to pretty print HTML, see github.com/einars/js-beautify .jsbeautifyrc\",\n\n  // override the default compile\n  onCompile: function(exhbs, source, filename) {\n    var options;\n    if (filename \u0026\u0026 filename.indexOf('partials') \u003e -1) {\n      options = {preventIndent: true};\n    }\n    return exhbs.handlebars.compile(source, options);\n  }\n});\n```\n\n## Syntax\n\nTo mark where layout should insert page\n\n    {{{body}}}\n\nTo declare a block placeholder in layout\n\n    {{{block \"pageScripts\"}}}\n\nTo define block content in a page\n\n    {{#contentFor \"pageScripts\"}}\n      CONTENT HERE\n    {{/contentFor}}\n\n## Layouts\n\nThere are three ways to use a layout, listed in precedence order\n\n1.  Declarative within a page. Use handlebars comment\n\n        {{!\u003c LAYOUT}}\n\n    Layout file resolution:\n\n        If path starts with '.'\n            LAYOUT is relative to template\n        Else If `layoutsDir` is set\n            LAYOUT is relative to `layoutsDir`\n        Else\n            LAYOUT from path.resolve(dirname(template), LAYOUT)\n\n2.  As an option to render\n\n    ## ⚠️ This creates a potential security vulnerability if used without a `restrictLayoutsTo`:\n\n    The `restrictLayoutsTo` option will restrict reading layouts to a particular directory, if you do not pass this option then do not use the `layout` option in conjunction with passing user submitted data to res.render e.g. `res.render('index', req.query)`. This allows users to read arbitrary files from your filesystem!\n\n    ```js\n    res.render('veggies', {\n      title: 'My favorite veggies',\n      veggies: veggies,\n      layout: 'layout/veggie'\n    });\n    ```\n\n    This option also allows for layout suppression (both the default layout and when specified declaratively in a page) by passing in a falsey Javascript value as the value of the `layout` property:\n\n    ```js\n    res.render('veggies', {\n      title: 'My favorite veggies',\n      veggies: veggies,\n      layout: null // render without using a layout template\n    });\n    ```\n\n    Layout file resolution:\n\n        If path starts with '.'\n            layout is relative to template\n        Else If `layoutsDir` is set\n            layout is relative to `layoutsDir`\n        Else\n            layout from path.resolve(viewsDir, layout)\n\n3.  Lastly, use `defaultLayout` if specified in hbs configuration options.\n\nLayouts can be nested: just include a declarative layout tag within any layout\ntemplate to have its content included in the declared \"parent\" layout.  Be\naware that too much nesting can impact performances, and stay away from\ninfinite loops!\n\n## Helpers\n\n### Synchronous helpers\n\n```js\nhbs.registerHelper('link', function(text, options) {\n  var attrs = [];\n  for(var prop in options.hash) {\n    attrs.push(prop + '=\"' + options.hash[prop] + '\"');\n  }\n  return new hbs.SafeString(\n    \"\u003ca \" + attrs.join(\" \") + \"\u003e\" + text + \"\u003c/a\u003e\"\n  );\n});\n```\n\nin markup\n```\n{{{link 'barc.com' href='http://barc.com'}}}\n```\n\n### Asynchronous helpers\n\n```js\nhbs.registerAsyncHelper('readFile', function(filename, cb) {\n  fs.readFile(path.join(viewsDir, filename), 'utf8', function(err, content) {\n    cb(new hbs.SafeString(content));\n  });\n});\n```\n\nin markup\n```\n{{{readFile 'tos.txt'}}}\n```\n\n\n## i18n support\n\nExpress-hbs supports [i18n](https://github.com/mashpie/i18n-node)\n\n```js\nvar i18n = require('i18n');\n\n// minimal config\ni18n.configure({\n    locales: ['en', 'fr'],\n    cookie: 'locale',\n    directory: __dirname + \"/locales\"\n});\n\napp.engine('hbs', hbs.express3({\n    // ... options from above\n    i18n: i18n,  // registers __ and __n helpers\n}));\napp.set('view engine', 'hbs');\napp.set('views', viewsDir);\n\n// cookies are needed\napp.use(express.cookieParser());\n\n// init i18n module\napp.use(i18n.init);\n```\n\n## Engine Instances\n\nCreate isolated engine instances with their own cache system and handlebars engine.\n\n```js\nvar hbs = require('express-hbs');\nvar instance1 = hbs.create();\nvar instance2 = hbs.create();\n```\n\n## Template options\n\nThe main use case for template options is setting the handlebars \"data\" object - this creates global template variables accessible with an `@` prefix.\n\nTemplate options can be set in 3 ways. When setting global template options they can be [passed as config on creation of an instance](https://github.com/barc/express-hbs#usage), and they can also be updated used the `updateTemplateOptions(templateOptions)` method of an instance. To set template options for an individual request they can be set on `res.locals` using the helper method `updateLocalTemplateOptions(locals, templateOptions)`.\n\nBoth of these methods have a companion method `getTemplateOptions()` and `getLocalTemplateOptions(locals)`, which should be used when extending or merging the current options.\n\n## Example\n\nin File `app.js`\n\n```js\n// http://expressjs.com/api.html#app.locals\napp.locals({\n    'PROD_MODE': 'production' === app.get('env')\n});\n\n```\n\nFile `views/layout/default.hbs`\n\n```html\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003e{{title}}\u003c/title\u003e\n    \u003clink type=\"text/css\" rel=\"stylesheet\" href=\"/css/style.css\"/\u003e\n    {{{block \"pageStyles\"}}}\n  \u003c/head\u003e\n  \u003cbody\u003e\n    {{{body}}}\n\n    {{\u003e scripts}}\n\n    {{#if PROD_MODE}}\n    {{{block 'googleAnalyticsScripts'}}}\n    {{/if}}\n\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n\nFile `views/index.hbs`\n\n```html\n{{!\u003c default}}\n\n{{#contentFor 'pageStyles'}}\n\u003cstyle\u003e\n  .clicker {\n    color: blue;\n  };\n\u003c/style\u003e\n{{/contentFor}}\n\n\u003ch1\u003e{{title}}\u003c/h1\u003e\n\u003cp class=\"clicker\"\u003eClick me!\u003c/p\u003e\n```\n\nTo run example project\n\n    npm install -d\n    node example/app.js\n\n\n## Testing\n\nThe test suite requires the `grunt-cli` package:\n\n    npm install -g grunt-cli\n    npm install -d\n\nOnce everything's installed, just run:\n\n    npm test\n\n\n## Credits\n\nInspiration and code from [donpark/hbs](https://github.com/donpark/hbs)\n\nBig thanks to all [CONTRIBUTORS](https://github.com/TryGhost/express-hbs/contributors)\n\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2012-2026 Barc, Inc., Ghost Foundation - Released under the [MIT license](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftryghost%2Fexpress-hbs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftryghost%2Fexpress-hbs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftryghost%2Fexpress-hbs/lists"}