Rolling a Custom Docs CMS with Slate and Django REST Framework

In 2019 we realized we had a big problem here at Stream. Our documentation for Feeds and Chat was outdated, hard to navigate, and difficult to update due to legacy systems that were in place. We made it a goal to set out and build the best documentation site available to the developers also to make it easy for internal teams to make updates to the documentation quickly and efficiently.

Nick P.
Luke S.
Nick P. & Luke S.
Published January 10, 2020 Updated January 13, 2020

The Rundown

In 2019 we realized we had a big problem here at Stream. Our documentation for Feeds and Chat was outdated, hard to navigate, and difficult to update due to legacy systems that were in place. We made it a goal to set out and build the best documentation site available to the developers also to make it easy for internal teams to make updates to the documentation quickly and efficiently.

At first, we figured it would be an easy task – find a CMS and tie it into our website so that updates are easy to make on the fly. Soon after our discovery period, we realized that this was not as easy as we had anticipated. At a bare minimum, our documentation needed to support the following:

  • Multi-language code support
  • An easy to navigate CMS for internal users
  • Full white-label support
  • Live on the Stream domain at https://getstream.io/docs/
  • Revision control
  • Autosave on change
  • Table support
  • Bullet points and numbered lists
  • Real-time collaboration support

Primarily, we were looking for a CMS / Google Docs hybrid for editing our documentation.

In this post, we’ll outline our process for not only choosing the best tools to build our in-house CMS but how we went about building it along with the tools we used to do so.

Discovery

With our documentation in what we would call shambles (primarily due to our high expectations as a team), our CEO and Co-Founder, Thierry Schellenbach, took charge of the project and spent hours on end researching and trying the best tools on the market for documenting an API. Thierry spent days looking into and prototyping various frameworks, some of which were (but not limited to):

Unfortunately, none of the above CMS products fit the bill. The closest that came in as a winner was Contentful; however, after a week of prototyping with Contentful, we realized that it didn’t have the features that we were looking for in a CMS. There was a lack of table and multi-language support, no real-time editing across multiple users, and more importantly, we did not have full control over the model of the data as Contentful uses a MongoDB style schema.

Finding Slate

Slate turned out to be the winner of our findings. It’s a fully customizable framework for building rich text editors with dozens of plugins to support what we need from a CMS standpoint. Best of all, Slate is built with React, allowing our front-end team to build on top of the framework with ease quickly.

By default, Slate provided everything we needed from a development standpoint to build a fully functional and robust CMS from the ground up (on top of Slate, of course). If you’re not sold yet, have a look at the live demo for Slate here.

Building with Slate

Slate has a relatively easy learning curve and, for the most part, mimics how the DOM itself works. Everything from simple markdown parsing and custom keyboard shortcuts to deeply nested tables and other custom nodes are a breeze to implement.

Slate allows you to create custom editor blocks and functionality through plugins. By encapsulating certain functionality into plugins, we can allow even non-technical team members to create relatively complex layouts within the editor on the fly.

Under the hood, Slate is relatively barebones and essentially provides a wrapper around the HTML5 contentEditable APIs but with additional constraints that help keep things smooth and predictable to reason with. This means that you must create all additional functionality above typing into the editor and callbacks like onPaste, etc. However, Slate’s ecosystem of plugins on npm is growing by the day, and to say they are easy to use, read, and create would be an understatement.

Some of the custom blocks we created for the CMS to meet our needs are as follows:

  • Code Tabs (Tabbed pre blocks with language selection and live syntax highlighting from PrismJS)
  • “Environment Variables” that automatically resolve to values (e.g., a users API key) in the front-end
  • Parameters list for displaying arguments and their types for our Feeds and Chat API calls
  • Custom table with an arbitrary amount of rows and columns
  • Language-specific blocks that can contain any number of nested blocks within them and that are conditionally shown based on the language selected by the user when viewing the documentation
  • Raw HTML block for adding HTML directly to the current page of the documentation to allow for custom elements that we don’t yet have dedicated blocks for, or don’t make sense as a block

All of the views and logic that go into creating these custom blocks are abstracted into plugins, which helped keep the codebase clean and provided total reusability across different projects.

On top of all of this, you can serialize and deserialize the content with custom functions – giving you total freedom of the input and output format of the editor.

One way we utilized both plugins and serialization was by creating a plugin that hijacks Slates onPaste handler to batch import code examples from the original documentation. Seeing as we had countless examples for multiple languages, it would have taken a lot longer to migrate our old content if we did it manually.

The plugin listens for cmd+v, checks the content that will be pasted in, and if it finds valid HTML for our code examples, it will automatically create a CodeTab block with a separate tab for each language, complete with syntax highlighting and indentation.

Overall, Slate has been a pleasure to work with from a developer-experience standpoint.

Pairing with Django REST Framework

There is nothing more productive than Django Rest Framework for spinning up a simple CRUD API. That’s why we chose to use it to roll out our API for supporting the backend of the Docs CMS.

We created models for Section, Page, and Feedback. Next, we enabled reversion on the Page. Reversion is this excellent little library that prevents you from losing changes.

The whole API took only a few hours to develop. Most of that time was spent writing tests to verify everything worked. Here at Stream, we do most of our development work in Go these days. However, for simple things like CRUD APIs, Python is a charm – it’s easy to implement and understand for everyone on the team.

Announcing the Stream Docs CMS

With Slate and Django REST Framework communicating, Stream built an entirely functional (and beautiful) CMS from the ground up. Without Slate, none of this would have been possible – thank you to the developers over there.

At the top, we have several controls that allow us to customize the content of the page. And on the right-hand side, we have a Google-like comments section where individuals within the team can leave @mentions – these are automatically emailed to the associated user when an email is found in the string.

Best of all, there’s real-time collaboration support, so we know when other users are working on the same doc. Real-time collaboration helps to prevent users from overwriting others’ work. Pretty cool, right?

Final Thoughts

As we continue to build out additional functionality on the Stream Docs CMS, we’ll continue to write about our findings. Again, we can’t thank the team and contributors over at Slate enough for the fantastic framework that they’ve put together. Without it, we wouldn’t have a custom CMS.

If you’re looking to build a custom CMS, we recommend you skip the frameworks found in our “discovery” section and jump right on over to Slate. You’ll be surprised by how easy it is to implement.

If you’d like to check out the Stream documentation, visit https://getstream.io/docs!

Until next time. Happy documenting! ✌️