Ghost Theme Development: Custom Widgets

Get started customizing your themes with simple widgets

Here at H+S headquarters, we're pretty into the Ghost blogging platform. It's a lot like Wordpress, except without everything that sucks about Wordpress. It's a Node app, isn't bloated with widgets, is more secure, smarter, prettier, and so on. If you're a dev looking to get a quick clean CMS running, Ghost is pretty much a no-brainer.

While the platform has been around for a while, the community is still in its infancy as humanity lags behind the curve, with 80% of all sites victim of hacking being Wordpress-based. As such, we consider it our duty to share knowledge where possible to expedite the growth of independent blogging.

This is no means a "getting started with Ghost" post - their own documentation covers that quite well. Instead, we'd like to share the source for some of the widgets we've developed over the years to help your theme along.

The Basics

We'll keep this short: Ghost is built on the following core stack:

  • NodeJS
  • ExpressJS
  • Handlebars
  • Grunt

As long as you're familiar with Express and Handlebars, you'll be good to go.

Everything we're doing today happens at the theme level, which is your presentation layer that can be swapped at any given time. Running on your own installation, the path should look something like:

/var/www/myGhost/content/themes/myTheme/

Widgets are handled as partials in your theme.

The Widgets

Recent Posts

This is a fairly common widget which displays X number of posts ranked by most recent.

<!-- start widget -->
{{#get "posts" limit="3" filter="primary_tag:-#hidden"}}
<div class="widget">
 <h4 class="title">Recent</h4>
 <div class="content recent-post">
 {{#foreach posts}}
   <div class="recent-single-post">
     <a href="{{url}}" class="post-title">{{title}}</a>
       <div class="date">{{date format="MMMM DD, YYYY"}}</div>
   </div>
    {{/foreach}}
 </div>
</div>
{{/get}}
<!-- end widget -->

{{#get}} will fetch posts, tags, or users within the given specifications. The tag does nothing on it's own; it simply allows us to work within the context of getting these items, such as how we use {{#foreach posts}} afterwards.

The filter is actually quite powerful, and perhaps a bit under-documented. In this case we're only fetching posts who have a visible main tag: you might want to do something like this if you sometimes use 'posts' to make announcements.

{{#foreach posts}} loops through our 3 posts and will create the result DOM structure the number of times it loops.

Similar to the above, but only returns posts which share the same main tag:

<!-- start widget -->
<div class="widget">
 <h4 class="title">Related</h4>
 <div class="content recent-post">
   {{#get "posts" limit="3" filter="id:-{{id}}+tag:{{primary_tag.slug}}"}}
   {{#foreach posts}}
     <div class="recent-single-post">
       <a href="{{url}}" class="post-title">{{title}}</a>
         <div class="date">{{date format="MMMM DD"}}</div>
	 </div>
	{{/foreach}}
   {{/get}}
 </div>
</div>
<!-- end widget -->

3. Authors

A surprisingly uncommon widget, we've actually yet to see this on another blog yet. This will list all contributors to your blog with their avatar, and link back to their author page:

<!-- start widget -->
<div class="widget contributors">
	<h3 class="title">Contributors</h3>
	<div class="recent-post">
		{{#get "users"}}
			{{#foreach users}}

				<div class="single-author {{slug}}">
					{{#unless profile_image}}
						<a href="{{url}}"><i class="fas fa-user" style="width:18px; height:18px; display:inline-block; margin-right:10px;"></i></a>
					{{/unless}}
					{{#if profile_image}}
        		<a href="{{url}}"><img src="{{img_url profile_image}}" alt="Author image" class="avi"></a>
					{{/if}}
					<div class="info">
						<a href="{{url}}" class="single-author-name">{{name}}</a>
						<span class="role"></span>
					</div>
				</div>
				{{/foreach}}
			{{/get}}
			</div>
</div>
<!-- end widget -->

4. About the Current Author

This widget only exists within the context of pages/posts which have an explicit author. Also supports the use case of multiple authors.

<!-- start about the author -->
{{#foreach authors}}
  <div class="about-author clearfix widget">
    <h4 class="title">Author</h4>
    {{#if profile_image}}
      <a href="{{url}}"><img src="{{profile_image}}" alt="Author image" class="avatar pull-left"></a>
    {{else}}
      <a href="{{url}}"><img src="{{asset "images/default-user-image.jpg"}}" alt="Author image" class="avatar pull-left"></a>
    {{/if}}
    <div class="details">
      <div class="author">
        {{!--{{t "About"}}--}}<a href="{{url}}">{{name}}</a>
      </div>
      <div class="meta-info">
        {{!--<span class="post-count"><i class="fal fa-pencil"></i><a href="{{url}}">{{plural count.posts empty=(t "0 Post") singular=(t "% Post") plural=(t "% Posts")}}</a></span>--}}
        {{#if location}}
          <span class="location"><i class="fal fa-home"></i>{{location}}</span>
        {{/if}}
        {{#if website}}
          <span class="website"><a href="{{website}}" targer="_BLANK"><i class="fal fa-globe"></i>{{t "Website"}}</a></span>
        {{/if}}
        {{#if twitter}}
          <span class="twitter"><a href="{{twitter_url}}"><i class="fab fa-twitter"></i>{{twitter}}</a></span>
        {{/if}}
        {{#if facebook}}
          <span class="facebook"><a href="{{facebook_url}}"><i class="fab fa-facebook"></i></a></span>
        {{/if}}
      </div>
    </div>
    {{#if bio}}
      <div class="bio">
        {{{bio}}}
        </div>
    {{/if}}
  </div>
{{/foreach}}
<!-- end about the author -->

5. About all the Authors

A combination of the above two, this widget displays a blurb and information about all authors who contribute to your publication.

{{#get "users" limit="all" include="count.posts" order="count.posts desc" }}
 	{{#foreach users}}
 		<div class="about-author clearfix" style="background: url({{cover_img}}) center center; background-color: rgba(255, 255, 255, 0.9); background-blend-mode: overlay;">
 		{{#if profile_image}}
 			<a href="{{url}}"><img src="{{profile_image}}" alt="Author image" class="avatar pull-left"></a>
 		{{else}}
 			<a href="{{url}}"><img src="{{asset "images/default-user-image.jpg"}}" alt="Author image" class="avatar pull-left"></a>
 		{{/if}}
 		<div class="details">
 			<div class="author">
 				<a href="{{url}}">{{name}}</a>
 			</div>
 		<div class="meta-info">
            <span class="post-count"><i class="fal fa-pencil"></i><a href="{{url}}">{{plural count.posts empty=(t "0 Post") singular=(t "% Post") plural=(t "% Posts")}}</a></span>
            {{#if location}}
                <span class="location"><i class="fal fa-home"></i>{{location}}</span>
            {{/if}}
            {{#if website}}
                <span class="website"><i class="fal fa-globe"></i><a href="{{website}}" targer="_BLANK">{{website}}</a></span>
            {{/if}}
            {{#if twitter}}
                <span class="twitter"><i class="fab fa-twitter"></i><a href="{{twitter_url}}">{{twitter}}</a></span>
            {{/if}}
            {{#if facebook}}
                <span class="facebook"><a href="{{facebook_url}}"><i class="fab fa-facebook"></i></a></span>
            {{/if}}
            </div>
        </div>
        {{#if bio}}
            <p class="bio">
                {{{bio}}}
            </p>
        {{/if}}
     </div>
{{/foreach}}
{{/get}}

Obviously you can customize your widgets as you see fit to include or exclude the information you're looking for. Hopefully these snippets serve as a useful reference for some common use cases to help your blog be as baller as possible.

Author image
New York City Website
Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.

Product manager turned engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.