Using Ghost Apps to Add Custom Handlebars Helpers To Your Theme
Ghost blogging platform theming support gives us plenty of built-in blog and post related Handlebars helpers. There are cases where built-in helpers are not enough and we need a way to apply custom helper. In this blog post, I'll show you how new Handlebars helpers can be applied using Ghost App. Keep in mind that the Apps functionality is in early stages and most likely there will changes.
The Ghost App we are creating
In this blog each blog post has an image that can be selected using Ghost's blog post editor. The chosen image is in the header of the blog post that you're reading. In addition to the header image, there is another image used in the blog post listing view. The URLs have a pattern:
If selected image has the following URL:
http://example.com/image.jpg
Then in blog posts listing the URL should be:
http://example.com/image_mini.jpg
The blog post listing URL cannot be created without logic (as it is in the middle of the image URL) and therefore we need Handlebars helper. To create a custom helper we need to create a Ghost App.
Note: Before 0.8.0 release of the Ghost, I was able to add Handlebars helpers with help of this blog post. Unfortunately, there has been some changes how Handlebars is used and the instructions described in the A Patch of Code blog post didn't work anymore.
Creating a bare minimum Ghost App
mini-images is the name of the Ghost App that we're going to create. Ghost Wiki has instructions on how to set-up a structure for the Ghost App. Minimum is two files: package.json and index.js.
These are the files I created for my Ghost App:
Package.json
{
"name":"mini-images",
"version": "0.0.1",
"dependencies": {
"ghost-app": "0.0.2"
}
}
index.js
var App = require('ghost-app'),
BlogPostMiniImage;
BlogPostMiniImage = App.extend({
install: function () {},
uninstall: function () {},
activate: function () {
this.ghost.helpers.register('blogPostMiniImage', this.blogPostMiniImageHelper);
},
deactivate: function () {},
blogPostMiniImageHelper: function(imageUrl) {
if(!imageUrl) {
return "";
}
var extension = imageUrl.slice(imageUrl.lastIndexOf('.'));
var fileNameWithoutExtension = imageUrl.slice(0, imageUrl.lastIndexOf('.'));
return fileNameWithoutExtension + "_mini" + extension;
}
});
module.exports = BlogPostMiniImage;
The idea is to take image URL and add "_mini" to the filename. The file extension should be same as in the original.
Permissions
If we tried to use the mini-images Ghost App now we would get an error:
ERROR: The App "mini-images" attempted to perform an action or access a resource (helpers.blogPostMiniImage) without permission.
The app will not be loaded
Check with the app creator, or read the app documentation for more details on app requirements
We need to modify the package.json file to have permission defined:
{
"name":"mini-images",
"version": "0.0.1",
"dependencies": {
"ghost-app": "0.0.2"
},
"ghost": {
"permissions": {
"helpers": ["blogPostMiniImage"]
}
}
}
Now we're ready to install and use our new Ghost App.
Usage of the Ghost App
As I am writing this, there isn't any user-interface to manage Ghost Apps. We need to modify blog settings without Ghost user-interface. In the Wiki article, there are two methods described: export JSON then import the edited JSON back to the Ghost or use GUI to edit data in the SQLite database. I am using PostgreSQL as my data store and I used a desktop application called pgAdmin III to edit the right database row in the Settings database table.
Everything should be now ready for testing.
Testing if Ghost App works
In my custom theme I can now try what happens if I add Handlebars helper into my HTML image src-attribute:
<img src="{{blogPostMiniImage image}}" alt="{{title}}">
The result should be image source with text "_mini" before filename extension.
Excellent!
Conclusion
I am eagerly awaiting for the Ghost Apps functionality as there are many extension points, Handlebars being the simplest one. Very likely there will be breaking changes on how Ghost Apps are developed, but I try to revisit this blog post and update the instructions.
If you found this useful, then send me a tweet and I know that people find this kind of content practical!