In recent years, Markdown has become the lingua franca of plain-text files on the web. If you’re a developer, you have read — and maybe even written — hundreds of Markdown documents over the course of your career.
GitHub repositories use README files written in Markdown. Stack Overflow and Reddit use it to format posts. Technical documentation, blog posts, and entire books are written in Markdown. And it’s not just for humans either! AI tools such as Claude Code and Cursor use Markdown documents to improve the effectiveness of AI agents. This article you are reading is — you guessed it — written in Markdown!
Ruby on Rails, of course, has its own tooling around Markdown, and in this post, we’ll build a Markdown editor using Rails.
Markdown in Ruby on Rails
Rails 8.1 brings …
In recent years, Markdown has become the lingua franca of plain-text files on the web. If you’re a developer, you have read — and maybe even written — hundreds of Markdown documents over the course of your career.
GitHub repositories use README files written in Markdown. Stack Overflow and Reddit use it to format posts. Technical documentation, blog posts, and entire books are written in Markdown. And it’s not just for humans either! AI tools such as Claude Code and Cursor use Markdown documents to improve the effectiveness of AI agents. This article you are reading is — you guessed it — written in Markdown!
Ruby on Rails, of course, has its own tooling around Markdown, and in this post, we’ll build a Markdown editor using Rails.
Markdown in Ruby on Rails
Rails 8.1 brings Markdown as content type, as well as a new rich text editor with Markdown support. And if you can’t or don’t want to use Rails defaults, there are always gems, such as Marksmith.
But what does it actually take to build a GitHub-like Markdown editor? Something simple that supports editing Markdown text and previewing the result. Something like this:


Let’s find out!
Markdown and Markdown Flavors
Before we start with the implementation, let’s talk about the Markdown language itself. First, it’s essential to understand that there is no single, definitive Markdown language. When Markdown was conceived in 2004, its original description left quite some room for interpretation. As a result, a plethora of Markdown dialects — or flavors — sprang into existence, each of them supporting different features and slightly different syntaxes.
The flavor you might be most familiar with is GitHub Flavored Markdown, or GFM in short. It is based on CommonMark, an attempt to create a formal, unified specification for Markdown, which was created in 2014. The CommonMark specification has been widely adopted, but even so, several different Markdown flavors remain common today.
Now, what makes Markdown so interesting, and why was it so widely adopted? The beautiful thing about Markdown is that it strikes a perfect balance between being structured and unstructured. It is not as verbose as other, more structured languages (such as XML, HTML, or JSON), and that makes it easy to read and write. But it provides just enough structure to be processed by computers. And that allows us to do interesting things with it.
One of those interesting things is converting Markdown to other formats, such as HTML. As a matter of fact, that was exactly what its designers had in mind!
Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid HTML. Source: Markdown, John Gruber’s website
Using Ruby, we have several fine options for converting Markdown into HTML: redcarpet, commonmarker, and Kramdown, to name a few. What they all have in common is that they convert Markdown to HTML. For example:
Markdown: Markdown supports *italic* and **bold** text.
HTML: <p>Markdown supports <em>italic</em> and <strong>bold</strong> text.</p>
As I mentioned earlier, different flavors of Markdown work differently. We’ll be using GitHub Flavored Markdown (GFM) for the rest of this article. If you’re curious about how GFM differs from other Markdown flavors, take a look at the specification. Note the sections tagged as extension — these are features that are unique to GFM and not usually found in other Markdown dialects.
Now that we have covered the theory, let’s get building.
Creating a Markdown Editor with Ruby on Rails
Our Markdown editor will require three things. First, a way for users to write — and save — plain text. This is easily accomplished using off-the-shelf Rails features. Second, we’ll utilize a third-party gem to render Markdown to HTML. And finally, a tiny bit of JavaScript to tie these things together and create a nice user experience.
While the quickest way to add a Markdown editor to your application is simply by using a gem — such as the excellent Marksmith — it’s always worthwhile to learn what goes on under the hood. You can follow along with the code used in this post by viewing or cloning this repository.
Let’s start a new Rails app with a simple post model that will hold our Markdown text. We use Tailwind CSS for styling and also enable ActiveStorage. Why do we need ActiveStorage, you ask?
It’s a surprise: you’ll see later.
The post scaffold creates a form with a text area, which is all we need for users to write and save Markdown documents. Now we need to render those documents to HTML. We’ll use Commonmarker. It supports GitHub Flavored Markdown and is easily customizable, which makes it the logical choice.
Before we tackle the editor itself, let’s update our app so it displays rendered Markdown when showing a post. We only need to add a handful of lines to both our posts controller and the post partial.
Note our use of html_safe. Otherwise, the HTML created by Commonmarker will be sanitized by Rails. We don’t want sanitized HTML — we want our HTML to be shown to the user as rendered.
To try it out, let’s create a new post with the following Markdown content.
If we view the post, things are clearly working. We can see bold and italic text, links, and even syntax highlighting. But we are still far from a GitHub-like editor.
We don’t want to create a post to see what the resulting HTML looks like. What we need is a live preview.
Preview with Turbo Streams
Rather than showing rendered HTML only after a post is created, we want to display it while in the writing process. We’ll easily be able to do so using Turbo Streams and StimulusJS. Let’s add a Turbo stream action to our posts controller and the corresponding route to our routes file.
Our Turbo stream targets the preview element. Accordingly, we create a div with the preview ID in the post form. We also add a button that will trigger the Turbo stream action using a Stimulus controller — which we’ll create next.
Our Stimulus controller is nothing special. It takes the content of our editor, which we refer to as the editorContentTarget. It then sends a request to the post preview action. The rest is done automagically by Turbo. On a side note, we are using rails/request.js here, which makes it a lot simpler to send Turbo Stream requests — or any other requests for that matter — to your Rails application from JavaScript.
In our post form, we can now preview the rendered Markdown at the click of a button. Great success!
Improving the User Experience
Now, there are two more things to address. For one, if you’ve been playing along, you’ve probably noticed our HTML doesn’t really look right. We are rendering tags such as h1 or table correctly, but the styling is off. Since we use Tailwind, there is an easy way to fix this — the TailwindCSS typography plugin.
What does it do, you ask?
The official Tailwind CSS Typography plugin provides a set of prose classes you can use to add beautiful typographic defaults to any vanilla HTML you don’t control, like HTML rendered from Markdown, or pulled from a CMS. Source: Tailwind CSS Typography GitHub README
Sounds perfect for our purpose. To make our HTML look pretty, we just need to add the prose class to our preview pane.


Much better. If you are not using Tailwind or the typography plugin, you can still make your HTML look nice, but you’ll need to create the necessary CSS by hand.
Now, finally, let’s touch up our editor. We want to switch between our editor (the text area where we write our Markdown) and our preview at the click of a button. For that, we’ll update our Stimulus controller so that it toggles between these two panes by hiding one and displaying the other. We’ll introduce the necessary targets, along with new targets for the buttons (so we can change the style of the active one).
We also need to make some changes to our form. We can remove our previous preview button since we no longer need it.
And that’s all we need to create a magnificent Markdown editor just like the one GitHub has! Well, that’s not entirely true. GitHub’s editor has a lot of subtle features that make our writing experience more pleasant — we won’t add all of them here.
There’s one thing we will add, though. What I adore about the GitHub editor is that it lets you add images to your Markdown simply by pasting them into the editor. Let’s wrap up this article by adding this advanced feature.
Adding Image Uploads to our Markdown Editor in Ruby on Rails
Remember how we enabled ActiveStorage when we set up our demo application? Well, this is what it’s for!
To upload files directly from our editor, we’ll be using Rails Direct Uploads. Let’s add the necessary JavaScript to our application.
Next, we update our trusty preview controller one final time.
The upload function will be called whenever a user pastes something in our editor. If the pasted data contains any files, we use the DirectUpload class supplied by ActiveStorage to upload them to our server. Once the upload has finished, we take the resulting blob attributes and convert them into a markdown image link that Commonmarker can convert to HTML. Finally, we use selectionStart and selectionEnd to determine at which position to insert this link.
We now only need to update our HTML to provide the direct upload URL and wire up the paste event to our Stimulus controller. Simple.
Now, we did leave some things out. For one, there is no error handling — your image upload may fail, and you should handle that. Also, for a true GitHub-like experience, you’d want to support uploading files via drag-and-drop, as well as allowing users to select files using a file picker. These features work similarly to upload-on-paste — we just left them out in this post for brevity.
Wrap Up
Markdown is arguably the language of the web. Except it isn’t a single language, but is comprised of various dialects, the most popular of which are CommonMark and GitHub Flavored Markdown.
In the Ruby ecosystem, Commonmarker is an excellent choice for converting GFM to HTML.
By leveraging the power of Turbo Streams, StimulusJS, and TailwindCSS, we have created a nice-looking Markdown editor that supports live previews and image uploads in no time.
Happy coding!