Documenting Craft with Craft

Documentation is a beast with two heads. It's challenging enough to write and present everything in a way that makes sense to an audience with a variety of experience levels and needs, and then there's the part where you have to manage it all, ideally in a way that encourages you to continue revising and adding content as time goes on.

As a software developer, that second challenge has always interested me. In fact the technology behind our documentation has been one of the primary focal points every time we work on a new site for ourselves. (I wrote about my first two forays into documentation management back when this site first launched.)

Leading up to Craft’s public beta, we were faced with the decision once again. At the time, our EE add-on documentation was being written as raw HTML right within our add-ons’ repositories, and whenever we made a change, the change had to be copied into a text area within the EE installation powering It was a primitive system that I had devised with the (delusional) intent to improve upon post-launch.

I did like how the docs could get zipped up right with our add-ons for offline viewing, so for Craft I felt that much should stay the same. We just needed to address the part my OCD had no appreciation for – that they were effectively managed in two separate places.

With that in mind, we ended up deciding to go with Sphinx. With Sphinx, documentation is written in reStructuredText, and there’s a build script that converts it into a big, static collection of HTML files. We could set the build script’s output directory to our repository, and then push the changes live from there. (We never did get around to bundling the documentation with Craft itself.)

It sounded good, and we kept telling ourselves that it was good. In fact when we set out to redesign a few months later, we decided to use Sphinx for the EE add-on documentation there too.

The only problem: it really wasn’t good.

The cold, hard truth is that managing documentation with Sphinx is completely repulsive. The syntax for reStructuredText is as ugly as its name, and the effort that goes into previewing and publishing changes is so tedious it would be funny, if not for the fact that it actually discouraged us from making changes in the first place.

Time for a fresh start

Not long after Craft 1.0 had launched, some Craft users started voicing complaints about the documentation, and it quickly became clear that a complete reorganization was needed. The thought of doing that within Sphinx was unbearable, so we knew we had to take this as an opportunity to come up with something better.

It was a combination of our need to move away from Sphinx, and the dozens of people requesting hierarchical content structures in Craft, that led to our a-ha moment: If Craft supported hierarchical content, we could use that to manage our documentation!

Using Craft to manage documentation had other benefits as well. We had been planning a redesign of that brought the documentation back into the main website in a way that made it feel like it actually belonged there, and what better way to do that then have it use the exact same CMS and templates as the rest of the site? Not to mention it would be easier to relate that content to other content throughout the site, like the Help & Support section.

We came up with a plan that resulted in the big features behind Craft 1.2: Structure sections and Entry Types. Once we got things working on some level, we started working on our redesign, and with it, the new documentation.

Most of the work that went into the new docs was just porting the 85-or-so reStructuredText pages to Markdown, a process that ended up taking a lot longer than we expected. We could have saved ourselves some trouble if we had gone with a Rich Text field instead, where we could just copy Sphinx’s output HTML directly into the field, but we prefer Markdown (especially with the wonderful Scrawl plugin). After all, the whole point of this exercise was to come up with a setup where we actually enjoy writing documentation.

Gotcha’s and Voila’s

You never know what you’ve got until it’s gone, as they say, and as it turned out there were a couple features in Sphinx that were actually missed:

  • Easily creating links to other pages in the docs
  • Creating includes that could store commonly-used content
  • Headings are linkable via anchors that automatically get appended to them

Sure, Markdown has the ability to create hyperlinks, but in our case the actual URLs weren’t set in stone yet (the site URL would be changing when we moved from staging to production, and we weren’t sure about the actual documentation URL formats yet). Plus there are times where you want the actual link text to be set to the linked entry’s title, and you wouldn’t want to have to remember to update the link text if the title ever changes.

The solution we came up with? Reference tags. They are a new, generic way to pull data from other elements in the system (entries, assets, users, etc.), right into a text field.

Whenever we needed to link to another page, we would place a reference tag where the URL is supposed to go in Markdown’s hyperlink syntax:

Each Entry Type gets its own [field layout]({entry:docs/fields}),
and has control over what the “Title” field should be labelled.

For the times we wanted the link’s text to be set to the linked entry’s title, we added the new getLink() method to EntryModel, which allowed us to just type this:

See {entry:help/dev-mode:link} for more info.

Inline images proved to be another great application for reference tags. The only issue was that there wasn’t an obvious textual format for asset references as there had been with entries (“sectionHandle/entry-slug”), since assets don’t have slugs, and their filenames aren’t necessarily unique per asset source, when you factor in subfolders. Technically something like “sourceHandle/relative/path/to/file.ext” could work if asset sources had handles, but at that point there’s not a whole lot of benefit over hardcoding the full asset URL. Instead we added a new “Copy reference tag” option to assets’ context menus in the main Assets index, which brings up a Javascript prompt where you can copy the asset’s ID-based reference tag (e.g. “{asset:100}”) and paste it where it needs to go:


That syntax got old pretty quick though, so we added a new getImg() method to AssetFileModel in the same spirit as getLink():


Once we had a few reference tags in place, we put the system to the test using Craft’s new parseRefs filter:

{{ entry.body | parseRefs | markdown }}

It worked like a charm, and we were quite happy with it.

Then there was content includes. There were only a couple places where we had actually been using them in Sphinx, such as documenting the possible values supported by the ‘childOf’ and ‘parentOf’ params on craft.entries et al., so we were tempted to just ignore the problem and hard-code that content.

Then it hit us: reference tags could solve this problem too!

We created a new section called “Documentation Snippets”. The section’s entries don’t have URLs. Just a Body field (Markdown, of course). And in that section there are entries for each of our reusable snippets.

Then, whenever we needed to include one of those snippets, we just used a reference tag:

The supported values are:

We even coded our reference tag parsing function to work recursively on the values that replace the tags. So in that example, any additional reference tags inside our “Relation Param Values” entry’s body will also get parsed, just as they would have if we had typed the content directly in the main entry.

The last thing we missed from Sphinx was the linkable headings. In practice, it’s very nice to be able to send someone directly to the spot on the page that explains a concept that’s come up in support or on Google+.

That feature seemed beyond the scope of what Craft itself should do out-of-the-box however. So instead we wrote a new plugin for the job, called Anchors. It’s a very simple plugin, and it does its job quite nicely. With it installed, we just had to add one more filter to our Body field’s output tag in the template:

{{ entry.body | parseRefs | markdown | anchors }}

And presto! With those filters in place, the Body field’s Markdown-formatted content would A) get parsed for reference tags, B) get converted into HTML, and C) get anchor tags added to its headings.

In Conclusion

We set out to give ourselves a better tool for managing documentation, and I’m happy to say we succeeded.

The proof is in the pudding: we went from a system we despised so much that we actually avoided, even at the expense of our own customers’ understanding of our product, to a system that is so enjoyable to use, we’ve been looking for things to clear up or expand upon.

It’s a dramatic difference, and Craft has benefitted greatly from it, both as a product and through better documentation. We couldn’t be happier with the result, and we hope you feel the same way.

comments powered by Disqus