Library / SDK
avo-hq/marksmith avatar
avo-hq/marksmith

Marksmith: a GitHub-style markdown editor for Rails apps

GitHub-style markdown editor for Ruby and Rails

556 stars30 forksJavaScriptMIT

At a glance

What is it?
Marksmith is a Rails engine that puts a GitHub-style markdown editor into a form field, with a built-in preview renderer, Active Storage drag and drop uploads and a Stimulus controller you register in the application. It is MIT licensed and, by the maintainer's own warning, still short of a beta with an API that may change.
Who is it for?
Marksmith suits a Rails application that stores long-form content as plain text and wants a GitHub-style editing experience without adopting a hosted editor or building one, since the Stimulus controller, the preview renderer and the Active Storage upload path are all present.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 70 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the editor actually is

Marksmith is described as a GitHub-style markdown editor for Rails apps, and the README is unusually direct about its status. The warning block says Marksmith is at the initial stage of development, that it is nearing a beta release, that the API might change and that bugs are expected, and it asks people to continue using the library and report issues in the GitHub repo. That framing should shape your expectations: this is a component for a Rails app that stores markdown, not a product with a stable interface.

The feature list is short and specific. It supports Active Storage attachments, and it comes with a built-in markdown preview renderer. The use case is a form field where a user writes markdown and sees the rendered result, with the toolbar and the paste behaviour people expect from GitHub.

The repository is JavaScript and Ruby side by side, with a package.json for the front end and a gemspec for the Rails engine. The front end is a Stimulus controller, since the README says Marksmith integrates with Rails apps using Stimulus controllers and Turbo Streams. The build is Vite and Rollup, with Tailwind for the styles, and the npm package is published as @avo-hq/marksmith with the module built to app/assets/builds/marksmith.esm.js.

The homepage is avohq.io, and the README closes with a note about extra behaviour when Marksmith is used in Avo, the project behind that homepage.

Prerequisites that narrow who can use it

The installation section opens with prerequisites rather than with the install command, and that ordering is a warning. Marksmith integrates with Rails apps using Stimulus controllers and Turbo Streams, so the following must already be installed and configured: the @hotwired/stimulus package, the @hotwired/turbo package and the turbo-rails gem.

That is the sharpest boundary in the project. An app that has not already moved to Hotwire cannot adopt Marksmith without first taking on Stimulus and Turbo, and an app that has deliberately avoided them has a reason. A Rails app still on Rails UJS or on a classic server-rendered form flow with no Stimulus would find the editor's live preview behaviour hard to reproduce.

The JavaScript dependencies are bundled by default, which the README notes in a callout. The package ships with several dependencies already in it, and if you want to import those yourself and pull in only the controller, there is a /core path for it. The underlying pieces it bundles are @github/markdown-toolbar-element for the formatting toolbar, DirectUpload from @rails/activestorage for uploads, post from @rails/request.js for requests and subscribe from @github/paste-markdown for pasting markdown from a clipboard.

The main dependency list in package.json confirms this: @github/hotkey and vite-plugin-rails as runtime dependencies, with @github/markdown-toolbar-element, @github/paste-markdown, @hotwired/stimulus, @hotwired/turbo-rails, @rails/activestorage, @rails/request.js, tailwindcss and vite as dev dependencies used at build time.

Installing the gem and the npm package

The Ruby side is a normal Bundler add, and the command pulls in both the editor and a markdown parser at once:

bash
bundle add marksmith commonmarker

The parser is a separate gem on purpose. Marksmith renders markdown through a parser you choose, and commonmarker is the default, so the install command shows the intended pairing.

The JavaScript side is installed from npmjs.org with yarn:

bash
$ yarn add @avo-hq/marksmith

If the app uses importmap rather than a bundler, the package can be pinned instead:

bash
bin/importmap pin @avo-hq/marksmith

Then the controllers are imported and registered. The ListContinuationController is optional and only needed if you want continued lists in the markdown:

js
import { MarksmithController, ListContinuationController } from '@avo-hq/marksmith'

application.register('marksmith', MarksmithController)
application.register('list-continuation', ListContinuationController)

The core-only import path exists for the case where you want to bring the dependencies in yourself:

js
// Manually import Marksmith's dependencies
import '@github/markdown-toolbar-element'
import { DirectUpload } from '@rails/activestorage'
import { post } from '@rails/request.js'
import { subscribe } from '@github/paste-markdown'

// Import just the controller
import { MarksmithController } from '@avo-hq/marksmith/core'

application.register('marksmith', MarksmithController)

Finally the stylesheet tag goes into the application layout:

erb
<%= stylesheet_link_tag "marksmith" %>

The asset name matches what the build script produces, since the CSS is compiled from app/frontend/entrypoints/application.css to app/assets/stylesheets/marksmith.css by the Tailwind CLI.

Using the field, and what ends up in the database

The field is used through a form helper tag or attached to a form builder:

erb
<%= marksmith_tag :body %>
erb
<%= marksmith_tag :body, value: "### This is important" %>
or
<%= form.marksmith :body %>

The last line of the README's usage section is the one that matters for a data model: the value is stored in the database as plain text. No HTML is written at save time, so the markdown remains the source of truth and you can still change renderer later, re-render old records, or run a different parser over existing rows. That is a better choice for a long-term content store than persisting rendered HTML.

The field supports the regular options you would expect on a Rails form field, including disabled, placeholder, autofocus, style, class, data and value, plus three Marksmith-specific ones. extra_preview_params sends extra params to the preview renderer, which is how you pass something like a current user or a locale into the preview render. enable_file_uploads turns uploads on or off. And upload_url sets the URL used for file uploads, falling back to the rails_direct_uploads_url helper when it is not provided.

erb
<%= marksmith_tag :body,
  disabled: true,
  placeholder: "Write your best markdown here.",
  extra_preview_params: { foo: "bar" },
  enable_file_uploads: true,
  upload_url: nil
  %>

Active Storage uploads work through drag and drop and pasting files into the field, which is the behaviour people carry over from GitHub and miss most in a plain textarea.

Rendering the stored markdown, and the sanitisation warning

To display the stored content, the marksmithed helper runs the body through the renderer:

erb
<%== marksmithed post.body %>

The double equals is deliberate and the README attaches a warning to it. Using the <%== tag will output raw HTML, so you must ensure you sanitise the content to avoid XSS attacks. The README links to how the project does it, in a rendered body partial, with this as the sample sanitisation:

ruby
> # sample sanitization
> sanitize(body, tags: %w(table th tr td span) + ActionView::Helpers::SanitizeHelper.sanitizer_vendor.safe_list_sanitizer.allowed_tags.to_a)

This is the single most important thing to get right when adopting Marksmith, and the risk is not hypothetical. The editor is a rich text entry field where users paste arbitrary content, the stored value is raw markdown, and the render step turns it into HTML. If your app's user base includes untrusted authors, the sanitisation is the boundary that stops a stored XSS.

The sample shows the shape of the fix: start from the sanitiser's own allowed tag list, then add the table tags the renderer produces, so that a rendered markdown table survives sanitisation without opening the filter to the rest of HTML.

Choosing a parser, and swapping the renderer entirely

The preview renderer is powered by Commonmarker by default, but it can be changed to Redcarpet or kramdown in the configuration, or replaced outright by customising the Marksmith::Renderer model. The config.parser setting takes the name of the parser:

ruby
# config/initializers/marksmith.rb
Marksmith.configure do |config|
  config.parser = "redcarpet"
end

If you use redcarpet you can override the parser flags through configuration, and the defaults match the built-in behaviour. The whole options hash can be set:

ruby
# config/initializers/marksmith.rb
Marksmith.configure do |config|
  config.parser = "redcarpet"
  config.redcarpet_options = {
    underline: false,
    highlight: false
  }
end

or the individual flags can be overridden directly:

ruby
# config/initializers/marksmith.rb
Marksmith.configure do |config|
  config.parser = "redcarpet"
  config.redcarpet_options.underline = false
  config.redcarpet_options.highlight = false
end

Going further, the renderer can be replaced by overriding the model:

ruby
# app/models/marksmith/renderer.rb
module Marksmith
  class Renderer
    def initialize(body:)
      @body = body
    end

    def render
      # Your custom renderer logic here
    end
  end
end

That override point is the escape hatch that makes the parser choice reversible. Because the database holds plain text, changing the parser or the renderer changes how existing rows display without a migration.

The engine, its mount path, and where this stops being the right tool

The Rails side is an engine mounted by default at /marksmith, and the mount path is configurable through Marksmith.configuration.mount_path in an initializer that you can generate:

bash
bin/rails generate marksmith:install

which creates a config/initializers/marksmith.rb file. The initializer sets the path:

ruby
# config/initializers/marksmith.rb
Marksmith.configure do |config|
  config.mount_path = "/markdown"
end

The engine can also be mounted manually by setting Marksmith.configuration.automatically_mount_engine to false and adding the mount in routes.rb:

ruby
# config/routes.rb
Rails.application.routes.draw do
  mount Marksmith::Engine => Marksmith.configuration.mount_path
end

The manual mount exists for a reason worth guessing at carefully: an application that already defines a /marksmith route, or one that wants the engine mounted inside a namespaced area, needs to place it itself.

The limits are these. The Stimulus and Turbo prerequisite rules out a large part of the Rails installed base. The raw HTML output of the marksmithed helper puts sanitisation on the application, and forgetting it is an XSS. And the README's own warning that the API might change means an application that builds a lot on Marksmith's internals is taking an upgrade risk that the maintainer has explicitly flagged rather than hidden.

On versions, there is a gap worth noting. The newest listed gem releases are v0.4.7 from 2025-08-07, v0.4.4 from 2025-04-05 and v0.4.2 from 2025-04-05, while the npm package.json records version 0.6.0, so the front end and the Ruby gem are versioned independently and the npm version number is not a mirror of the gem's. The last push to the repository was on 2026-08-14, and the licence is MIT, with an MIT-LICENSE file at the root.

Editorial conclusion

Marksmith suits a Rails application that stores long-form content as plain text and wants a GitHub-style editing experience without adopting a hosted editor or building one, since the Stimulus controller, the preview renderer and the Active Storage upload path are all present. It is the wrong tool if your app is not on Stimulus and Turbo, because those are stated prerequisites rather than optional extras, and it is the wrong tool if you need a stable API today, since the README warns that the API might change. Verify first which markdown parser your app will use, since the renderer defaults to commonmarker and the gem is added with bundle add marksmith commonmarker, and note that the npm package is at version 0.6.0 while the newest gem release listed is v0.4.7 from 2025-08-07.

Frequently asked questions

How do I install Marksmith in a Rails app?

Run bundle add marksmith commonmarker, then install the front end with yarn add @avo-hq/marksmith or bin/importmap pin @avo-hq/marksmith, import the MarksmithController and register it with your Stimulus application, and add stylesheet_link_tag "marksmith" to your application layout.

What are the prerequisites for Marksmith?

The README says Marksmith integrates with Rails apps using Stimulus controllers and Turbo Streams, so the @hotwired/stimulus package, the @hotwired/turbo package and the turbo-rails gem must already be installed and configured.

How is the markdown stored, and how is it rendered?

The value is stored in the database as plain text, and you render it with the marksmithed helper, for example <%== marksmithed post.body %>. The double equals outputs raw HTML, so the README warns you must sanitise the content to avoid XSS attacks.

Can I use a different markdown parser with Marksmith?

Yes. The renderer uses Commonmarker by default, but you can set config.parser to "redcarpet" or "kramdown" in the initializer, adjust the redcarpet_options flags, or replace the renderer entirely by overriding the Marksmith::Renderer model.

Does Marksmith support file uploads?

Yes, through Active Storage, using drag and drop and pasting files into the field. Uploads can be disabled with enable_file_uploads and the endpoint set with upload_url, which falls back to the rails_direct_uploads_url helper.

How do I change where the Marksmith engine is mounted?

Run bin/rails generate marksmith:install to create config/initializers/marksmith.rb and set config.mount_path, or set Marksmith.configuration.automatically_mount_engine to false and mount Marksmith::Engine yourself in routes.rb.

Official sources

  1. avo-hq/marksmith on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/avo-hq-marksmith.svg)](https://hysenlabs.com/projects/avo-hq-marksmith)

Was this useful?