It started from one of my side projects.

I’ve been creating mobile app UI clone using Ionic Framework (omg I love it) and putting them on Ionic Market for sale. It’s been a lot of fun and joy working on something I like so much. But. There’s always a but. It was quite a struggle at the end of the process: I have to write some document before I can publish my product. The list of my products is getting longer over time and one day I released that I have 20+ items of them to manage. I have to publish the document to different places, make sure they are up-to-date in terms of pricing, bug fixes and improvements that I added (change log). Sometimes in a document, I need to refer to another of my products so that people can find the older or newer version of the same item.

I used to write the documents on Stackedit in markdown format. It’s very convenient: preview markdown with scroll-sync, sync files to Dropbox, Google Drive… However, it was still a manual job. If I need to make a small change to all the existing documents (announcement, promotion, markdown format issue,…), I’ll have to go through all of them and make the change, and make sure I don’t mess the content up. It was time-consuming and it almost made me hate what I’m doing.

I also built a site for my Ionic collection using Jekyll. That added up to the pile of the struggles I was having. More than ever, I need a single source of truth for all of my content.

Not long ago, I came up with the idea of having a system that helps me to manage all the items information, export the documents automatically. It was like a Now-Or-Never call.

So I started to think of Wordpress, imagined a platform that works as a CMS, but much more flexible and can expose a JSON API that I can re-use the content in any way I want. I’ve heard that Wordpress can also expose JSON API with the help of a plugin, but it’s 2019 and why not try something new? Something that goes with the trend?

The internet is so extremely creepy these days when it feels like it can read your mind. The next day, I saw some articles, or another way to say, some articles reached me, about a totally new and intriguing term: “headless CMS”. After doing some research, I find it really interesting and what I get about headless CMS is:

A traditional CMS without front-end and comes with Admin dashboard only

Content as API

CRUD system that then can expose an API

You can find a lot of definitions and explanations of headless CMS.

Bad news: there are so many of them. Let specify the goals before diving deeper and not getting lost:

What I need from a headless CMS

I want to manage all of my products in one place

I can do basic CRUD, edit information, images and so on

(Customizable so that I can) generate product document (in markdown)

Flexible enough to export and re-use my data in the future (just in case it can not survive until 2030)

Open source

Comes with Free plan or Dev plan

Should be written in some language or framework that I’m familiar with: Node JS, React JS or Vue.JS (I was going to learn)

Must work on my localhost. I plan to use it for myself only

It’d better to have a clean, modern, nice UI

I’ve got a long list on my hand: ButterCMS, Storyblok, Directus, Strappi, Sanity, … It took me almost a day to check out each of them. Thanks to my goals listed above, I knew I would choose Sanity.io. Or maybe just because the UI of Sanity Studio looks so nice, and my impression formed visually.

Pros

Meets most of my goals mentioned above (Yay!)

Cons

Cannot self-host the database and media server: although I can run Sanity Studio (Admin part) on my localhost, it always has to connect with Sanity Cloud service to interact with the database. On the bright side, you don’t need to care anything about managing your own database and media server, and you can export a snapshot of your dataset anyway.

Plot twist: it’s a realtime database.

As a headless CMS, Sanity explains its own way of doing “headless”.

Get started

First of all let’s install the main package @sanity/cli

> npm install -g @sanity/cli

Create a folder (for instant, /demo) for your project and go:

> cd demo

> sanity init

Next, just follow the instruction on your terminal and nothing can go wrong:

Sanity CLI is doing its job

Now go Sign up for a free plan on Sanity.io, get it up and running on your localhost

> sanity start

and Log in with the username and password you’ve just created then you’re good to go. This is called Sanity Studio aka.Admin dashboard.

A humble start with Sanity.io

I have to say that I was impressed with the clean, clear and simple UI of Sanity Studio. What’s more I was asking for?

You can find the full document here.

Your first schema

You very first lines of code with Sanity.io is probably to build your data structure. It’d take you not so long to master the schema syntax and structure. My notes when working with schema:

Every item is a document

A document has multiple fields

There are 2 main types of schema: built-in and object (custom). Learn more here

2 interesting types: Array and Reference. You’d use them more often than you think.

Extendable: besides some fancy types developed by Sanity.io team (for instant: Google maps input), you can write your own.

Schema in Sanity.io is pretty straightforward and you can easily figure it out by just looking at the sample dataset.

Sample schema

When you need a 1-N relation, for instant: a set of thumbnail images for a product item, use the type of “array”. When you need a dropdown, for instant: to select the document template that a product is using, use the type of “reference”. Very simple.

Type: “array”

Type: “reference”

Document for Schema on Sanity.io

And now the number of fields is adding up quickly and it probably needs to be organized a little bit. Let do something about it.

Your first custom object type

I started to think of grouping fields so that it’s easier to manage and maintain later. A product should have a group of external links (I’m putting my products on different payment gateways and using some tracking tools), a group of images (icon, logo, banner, and a set of thumbnail images).

Custom type. Notice the type: “object”

Now I’m going back to the Product schema product.js and add the new custom types I just created:

Works as expected. No sweat.

I think if you don’t need anything more than a basic CRUD system, you can settle now and start adding content. If you want more, roll up your sleeves higher and move on to the next section.

Advanced: create a plugin for Sanity Studio

Did I mentioned that Sanity Studio is actually a React app? You can create new components on top of it.

Sanity.io explains that everything you see in the Sanity Content Studio is plug-ins.

Back to my problem first. Let’s see what I need but Sanity.io does not have out of the box:

Generate a markdown document based on a product information. This leads to…

Templating engine: with a set of data and a given document template, I want to output a final document (in markdown), and this leads to…

Preview content in markdown format

I was first a bit worried because I could only see the use of Schema in Sanity and was wondering if I made a bad decision. Adding or removing fields, modifying schema types are good enough but what if I want to add additional UI elements? Go through their document a bit more and I found Extending section with plugins, custom input widgets, input resolver. Sounds perfect. I also got to know the term: inputComponent which could finally solve my problem.

The inputComponent property can be set on all types and fields and will override the default input widget.

My favorite part is here. I started to make a wireframe of what it would look like.

Hand sketch of the upcoming component

I was going to create a 3-column view in a popup modal.

1st column will contains some properties as input fields that can be used directly to create the document

2nd column will be the markdown document generated from the selected template

3rd column will be the preview of the document.

When I change a field under Properties column, it should be reflected immediately in the other 2 columns, as well as updated in the Document content so that I can save after that.

At this point, I did not even know how I would integrate this component with Sanity Studio, how it would go with Sanity’s data flow. Thanks to the component-based mindset, I can first focus on building the isolated component regardless of its surrounding environment.

And now let’s dive into inputComponent . My notes:

Just a normal React component

Take an additional step to modify and sync with data from Sanity Studio https://www.sanity.io/docs/extending/custom-input-widgets#it-must

You can pass the entire document content into the component using the built-in HOC withDocument (unfortunately, it’s undocumented)



...

export default withDocument(class PreviewModal extends PureComponent {

...

}) import { withDocument } from 'part: @sanity/form-builder ';...export default withDocument(class PreviewModal extends PureComponent {...})

Even when you can get the entire document content in your component, you won’t get the complete object for properties with the type of “reference”. It will only be an object with _ref and _type property. You will need to query Sanity database to get the object in full. I was struggling for days before I found the solution, by myself. I actually asked on StackOverflow then was able to answer my own question.

View from Sanity Document Inspector

This can be done by using Sanity client service



...

client.fetch(`*[_type == "category" && _id == "<that _ref string>"]{

title,

body

}`).then(response => {}) import client from 'part: @sanity/base /client';...client.fetch(``).then(response => {})

Hang in there. Did you see what’s I’m passing into that client.fetch() method? That is called GROQ (GRaph Oriented Query language) a query language created by Sanity.io. It’s very close to the way we write GraphQL query if you’re familiar (tbh I’m not LoL). You will work with this query language more when you develop a client side app which consumes Sanity API. You can definitely try GROQ right on Sanity Studio by installing a plugin called Vision.

Lodash template is still killing it. I couldn’t think of any simpler template engine.

import template from 'lodash/template';

...

const compiled = template(templateAsString);

const markdownInput = compiled({

data: {

...documentProperties

}

}); this.setState({

markdownInput: markdownInput

})

There are still something we need to do to actually connect this component to Sanity Studio:

Create a plugin call DocumentGenerator: create a folder for my new plugin plugins/document-generator and a config file plugins/document-generator/sanity.json to tell Sanity that I’m creating a custom schema type

Update the list of plugins that I can use across the Studio in the root sanity.json

Show and connect the input fields inside the component with the document content so that when we modify the values here, it will be synced up with the document object. I’ve learnt by looking at the code of BarcodeInput in Sanity’s Github repository. The FormBuilderInput is the key here.

Spend more time to connect all the dots then here is the final result:

Final UI of the component

The document for my product Ionic Tinder 4 is live now.

From now on, I can create different document templates for different purposes: one for my Jekyll site, the other for Market Ionic, another one if I want to change the content structure or the tone of the language… and the product information remains consistent. No manual copy-paste no more!

Different templates for different purposes

Bonus: Storybook in Sanity

Heard about Storybook? The living style guide and UI gallery for your current (ReactJS) project.

Great news: I figured out that Sanity repository kept this hidden gem in it which I couldn’t find it mentioned in their document. Anyway, I found it, that means you’d know it.

Before this point, I got a hard time looking into the code of the components folder on Sanity’s Github repository and making a lot of guesses to find the right UI components I need. Storybook saved my day. I’ll show you how to have it on your localhost.



> cd sanity/packages/storybook/

> npm install

> npm start

... > git clone git@github.com :sanity-io/sanity.git sanity> cd sanity/packages/storybook/> npm install> npm start... Content Studio successfully compiled! Go to http://localhost:9002

Open http://localhost:9002 on your browser:

See built-in components in action! What could be better?

Conclusion

Sanity.io’s learning curve is small: you can start to modify the schema to suit your needs and manage your content right away.

It takes a little bit extra effort to start your first plugin (as a React component).

So far so good. I’m happy with it.

If you have any questions on usage and development of Sanity Studio, I hope I can help to answer. I’ve been there, done that, looking around the internet, StackOverflow to find the solution for my problems.