A CMS In React & Markdown In 10 Steps (2023)

You probably know Wordpress, the content management system (CMS) that powers 43% of all websites. Well, what if you could build your own lightweight CMS with low-code?

You probably need a way to manage and publish digital content like text, images, and videos. A CMS is a software that provides a user-friendly interface for anyone to create, edit, and publish their own content without having to know how to code. But if you want a more efficient way to write content, you might want to consider a Markdown CMS.

Markdown is a lightweight markup language to write formatted text using a simple, easy-to-read syntax. A Markdown CMS stores content in Markdown format and converts it to HTML when it is displayed on the web. There are several benefits to using a Markdown CMS, such as its simplicity, speed, portability, and readability. Some popular Markdown CMS options include Ghost and Wordpress, but they aren't exactly lightweight and no-code friendly. In this tutorial, we'll see how to build a simple Markdown CMS with React and Rowy using Firebase as a flexible backend.

How To Build Your Own CMS With Low-Code

1. Add columns

Log in your Rowy account and create a new table. We will start small with only the following two columns:

  • title - a short text type field
  • content - a markdown type field

You'll obtain the following empty table:

picture of new columns in a Rowy table 0.jpg

You can already use Rowy as a Markdown CMS and add markdown content―it's useful when you want to perform quick fixes―but in this tutorial we want a React-powered CMS built from scratch. Let's see how to go about the UI.

2. Create a simple markdown text editor

First, we create a new React app via command line interface:

npx create-react-app react-md-cms

To keep things simple, we'll just use a plain textarea editor to write markdown content, a text input field for the article's title, and a save button to call our Rowy backend.

The main React component will look like this:

import React, { useState, useEffect } from 'react';

function App() {

    const [title, setTitle] = useState("");
    const [content, setContent] = useState("");

    return (
        <div className="App">
            <h1>My React Markdown CMS</h1>

            <input type="text" placeholder="Title" 
              onChange={(e) => setTitle(e.target.value)}

              onChange={(e) => setContent(e.target.value)}

            <button onClick={save}>Save</button>

export default App;

react text editor with markdown content 1.jpg

We have our basic user interface, but we need to interact with the backend to store and retrieve the article data. Rowy is a Firebase CMS, so we can use Firebase's API to interact with our database out of the box:

import { initializeApp } from "firebase/app";
import { 
} from "firebase/firestore"; 

const firebaseConfig = YOUR_FIREBASE_CONFIG

const app = initializeApp(firebaseConfig)

const db = getFirestore(app)

const collection_name = "react-cms"

function App() {

    const [article, setArticle] = useState(false);


export default App;

The save function will create a new document in the database if it doesn't exist, or update the already existing one with the new content:

async function save() {

  if (article) {
    await setDoc(doc(db, collection_name, article.id), {
  } else {
      const newArticle = await addDoc(collection(db, collection_name), {

Now, everytime we click on Save, our database will be updated accordingly. Note that Rowy automatically displays Markdown content as rich HTML in the view, but you can update the markdown by clicking on each individual row:

saved row 2.jpg

When we refresh our React app, we'll need to fetch the content from the database. To keep the example simple, we'll use localStorage to store the id of the document we just saved. We'll use this id to fetch the document from the database when the components mounts:

useEffect(async () => {
    const d = await getDoc(doc(getDb(), collection_name, localStorage.getItem("id"))) 

}, []);

A more elaborate solution would be to enable Firebase authentication and store the identity of the author in an additional Rowy column. This way, we could query all the articles in the database related to this specific author and pick which article we want to read and update. This is a bit too complex for the scope of this article, so we'll keep it for another one.

3. Convert Markdown To HTML

Most publishing CMS require HTML, not markdown, to publish articles, so we'll use Rowy to automatically convert Markdown to HTML. Rowy provides a nifty way to do this: the derivative column type. This type allows you to create a new column based on the value of another column. In our case, we want to create a new column that will contain the HTML version of the markdown content:

derivative column modal 3.jpg

A derivative column takes custom code in the form of a single cloud function. This function takes the current row as a parameter and returns the value of the new column. In our case, we'll use the showdown library to convert the markdown to HTML:

const showdown = require('showdown')
const converter = new showdown.Converter();
return converter.makeHtml(row.content);

Then, you just need to paste this code in your derivative column's settings:

derivative column settings 4.jpg

We can do the same for generating a slug column and a description by extracting the first 100 characters from the content column, for example. This way, repetitive manual tasks can be automated.

4. Upload images

Uploading images is one of the most important features of a CMS. Rowy leverages Google Storage in a simple spreadsheet interface to make it easy to upload and manage images. To upload an image, just create a new image column and upload or drag & drop them into the spreadsheet:

upload from Rowy column 5.jpg

Once the upload is completed, you can click on the image, copy its permanent Google Storage link, and paste it into the markdown content to have it appear in your article:

![my cool image alt text](GOOGLE STORAGE LINK)

4. Publish your content with Webflow

Last but not least, we'll use Rowy to publish our final article with Webflow. Webflow is another CMS to build beautiful websites without coding skills, so it's a great tool to combine with Rowy and your React CMS when you don't want to handle the publishing side.

For this part, you'll need a Webflow CMS Collection id that matches the Rowy table and a Webflow API key. You can get the collection id directly from Webflow's CMS collection editor. To get a Webflow API key, just go to your Webflow Project settings, then Integrations, and browse to the API access section.

To connect Webflow to Rowy using Webflow's API, we create a new Action column. An action column works similarly to a derivative column, except it runs when you press a button instead of automatically when a row is updated. Create a new column and pick the Action type:

action modal 6.jpg

An action column display a button you can use to run any custom script you wish. It's as simple as copy/pasting the following code in the Action column settings:

const action: Action = async ({ row, ref, db, storage, auth, actionParams, user }) => {

  const baseWebflowURL = "https://api.webflow.com"
  const webflowSecret = <YOUR WEBFLOW SECRET>
  const headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + webflowSecret,
    "accept-version": "1.0.0"
 const collectionId = <YOUR CMS COLLECTION ID>

  const itemId =  row.webflowItemId

  const body = JSON.stringify({
    "fields": {
      "name": row.title,
      "content": row.richContent

  try {

    if (itemId && itemId.trim().length > 0) {

      // update existing item
      var completeURL = baseWebflowURL + `/collections/${collectionId}/items/${itemId}`;
      if (row.live){
        completeURL = completeURL+ `?live=true`;
      await fetch(completeURL, {
        method: "PUT", headers, body
      }).then(async r => {
        const data = await r.json()
        console.log("Item updated 🌎🌎🌎🌎🌎🌎🌎")

    } else {
      // create new item
      var completeURL = baseWebflowURL + `/collections/${collectionId}/items`;
      if (row.live){
        completeURL = completeURL+ `?live=true`;
      await fetch(completeURL, {
        method: "POST", headers, body
      }).then(async r => {
        const respData = await r.json()
        console.log("Item created 🌎🌎🌎🌎🌎🌎🌎")

        await ref.update({ webflowItemId: respData._id })

    return {
      success: true,
      message: `Successfully published: ${row.name}`
  } catch (e) {
    return {
      success: false,
      message: `Error in publishing:` + e


All we do is import our data from Rowy to Webflow using an API request. Your action section in the column settings will look like this:

action column settings 7.jpg

Finally, you can publish the article from Rowy by clicking the Action button and check that the Webflow CMS collection is updated accordingly:

publish button 8.jpg

This is how we publish our blog content at Rowy: it's a great way to keep your content in sync between your CMS and your website without needing extra code and servers to maintain:

Rowy's CMS 9.jpg

Check Out More Demos

We hope you enjoyed this tutorial and that you'll be able to use it to build your own React CMS. If you want to see more examples, check out our Rowy demo templates.

Get started with Rowy in minutes

Continue reading

Browse all