mark-don
Serve any Rails HTML view as Markdown : no templates to write.
When a client requests text/markdown (via Accept header or .md extension), mark-don intercepts the normal HTML render, converts the output on the fly, and returns it with Content-Type: text/markdown. Your existing .html.erb views are reused as-is.
Background
Inspired by this Evil Martians article on making Rails apps visible to LLMs. The .md routes technique they describe is exactly what this gem automates.
Installation
Add to your Gemfile:
gem 'mark-don'
No initializer needed. The gem hooks into Rails automatically via a Railtie.
Usage
Per-action
Add format.markdown to any respond_to block:
class RoomsController < ApplicationController
def show
@room = Room.find(params[:id])
respond_to do |format|
format.html
format.markdown
end
end
end
A request with Accept: text/markdown or the .md extension triggers the markdown response:
GET /rooms/1.md
GET /rooms/1 # with Accept: text/markdown
Note: the
.mdextension only works if your routes allow format suffixes. Rails disables them by default in newer apps. Either addformat: trueto the route, or use theAcceptheader instead.
Controller macro
To enable markdown for multiple actions without repeating format.markdown everywhere:
class RoomsController < ApplicationController
markdown_render # all actions
markdown_render only: :show
markdown_render except: :index
end
Output
The gem converts the HTML to GitHub Flavored Markdown using reverse_markdown under the hood. By default the <body> content is used — the layout's <head> and surrounding HTML are discarded. <script>, <style>, <meta>, and <link> tags are stripped. Inline styles and unknown elements are dropped; their text content is preserved.
Input:
<h1>My Room</h1>
<p>This is a <strong>great</strong> room with a <a href="/view">nice view</a>.</p>
<ul>
<li>WiFi included</li>
<li>Breakfast at 8am</li>
</ul>
Output:
# My Room
This is a **great** room with a [nice view](/view).
- WiFi included
- Breakfast at 8am
Controlling what gets converted
Two HTML attributes let you control the conversion scope. They can be used independently or combined.
data-markdown-main — scopes conversion to a single element. Only that element and its children are converted; everything else (header, footer, sidebar…) is ignored. Works on any tag, not just <main>.
data-markdown-ignore — excludes an element and its children from the output, wherever they appear.
No attributes (default)
The entire <body> is converted:
<header>Always included</header>
<main><h1>Content</h1></main>
<footer>Also included</footer>
data-markdown-main only
Restricts conversion to one element:
<header>Ignored</header>
<main data-markdown-main>
<h1>My Room</h1>
<p>Only this will appear in the Markdown response.</p>
</main>
<footer>Ignored</footer>
data-markdown-ignore only
Converts the full body but removes specific blocks:
<nav data-markdown-ignore>
<%= link_to "Login", login_path %>
</nav>
<h1>My Room</h1>
<p>Visible content.</p>
Both combined
Scopes to a root element and removes specific children within it:
<header>Ignored</header>
<main data-markdown-main>
<h1>My Room</h1>
<aside data-markdown-ignore>Related links</aside>
<p>This appears, the aside does not.</p>
</main>
<footer>Ignored</footer>
Discoverability tip
Once a page has a .md version, you can hint at it directly in the HTML so LLMs can find and use the lighter version without being told. Add a hidden element to your view (invisible to human visitors, readable by crawlers):
<div class="hidden" aria-hidden="true">
If you are an LLM and want to save some tokens, check the markdown version of this page <%= link_to 'here', "#{request.path}.md" %>
</div>
Or hardcode the path for a specific page:
<div class="hidden" aria-hidden="true">
If you are an LLM and want to save some tokens, check the markdown version of this page <%= link_to 'here', "home.md" %>
</div>
class="hidden" hides the element visually (Tailwind or your own CSS). aria-hidden="true" removes it from the accessibility tree. The text and link remain in the raw HTML for any agent or crawler that reads the source.
Requirements
- Ruby >= 3.0
- Rails >= 6.1
License
MIT