Introduction
Markdown has become the lingua franca of AI. Large language models output markdown by default, documentation lives in markdown files, and developers think in markdown.
Why AI “Speaks” Markdown
Markdown’s rise in the age of AI comes down to a few key factors:
-
Simplicity and Structure: Its minimalist, plain text syntax (
#for headings,*for lists) creates a clear, predictable structure that AI models are trained on and can easily interpret. -
Enhanced Comprehension: AI struggles with complex formats like PDFs or Word documents due to visual clutter and metadata. Markdown strips this away, letting AI focus on meaningful content.
-
Improved Prompting and Output: Markdown in prompts provides a clear roadmap for AI, reducing ambiguity. AI systems generate responses in Markdown to ensure clear presentation with lists, code blocks, and tables.
-
Efficiency: Markdown’s conciseness saves tokens, allowing more information within an AI’s context window and reducing processing overhead.
-
Interoperability: As plain text, Markdown integrates easily across platforms—from GitHub to Notion to Obsidian—making it a universal format for digital content.
Rails 8.1 embraces this reality by adding native markdown rendering support. Controllers can now respond to markdown requests directly, just like HTML, JSON, or XML.
Before
Before Rails 8.1, serving markdown content required workarounds.
class PagesController < ApplicationController
def show
@page = Page.find(params[:id])
respond_to do |format|
format.html
format.text { render plain: @page.body }
end
end
endIf we wanted to serve markdown specifically, we had to register a custom MIME type and handle the rendering manually:
# config/initializers/mime_types.rb
Mime::Type.register "text/markdown", :md
# In the controller
format.md { render plain: @page.body, content_type: "text/markdown" }This worked but felt clunky. There was no convention for markdown responses, and objects couldn’t define their own markdown representation.
After
Rails 8.1 adds first class markdown support.
The format.md option is now available in respond_to blocks,
and objects can implement to_markdown for automatic rendering.
class Page < ApplicationRecord
def to_markdown
body
end
end
class PagesController < ApplicationController
def show
@page = Page.find(params[:id])
respond_to do |format|
format.html
format.md { render markdown: @page }
end
end
endWhen a client requests Accept: text/markdown
or appends .md to the URL,
Rails calls to_markdown on the object
and returns the content with the correct MIME type.
The to_markdown Convention
Similar to to_json and to_xml,
Rails 8.1 introduces to_markdown as a convention
for objects to define their markdown representation.
class Article < ApplicationRecord
def to_markdown
<<~MARKDOWN
# #{title}
*Published on #{published_at.strftime("%B %d, %Y")}*
#{body}
---
Tags: #{tags.pluck(:name).join(", ")}
MARKDOWN
end
endThis keeps the markdown generation logic encapsulated within the model, following Rails conventions.
Rendering Options
The render markdown: method accepts several options:
class DocumentsController < ApplicationController
def show
@document = Document.find(params[:id])
respond_to do |format|
format.md { render markdown: @document }
end
end
def raw
@document = Document.find(params[:id])
respond_to do |format|
format.md { render markdown: @document.raw_content }
end
end
endWe can pass an object that responds to to_markdown
or a string directly.
Use Cases
AI Generated Content
With AI assistants becoming common in applications, markdown rendering is essential. AI responses are typically formatted in markdown:
class ChatController < ApplicationController
def response
@message = AIService.generate_response(params[:prompt])
respond_to do |format|
format.html { render :response }
format.md { render markdown: @message.content }
format.json { render json: @message }
end
end
endClients can request markdown directly and render it on their end, or request HTML for server side rendering.
Documentation APIs
For applications that serve documentation, markdown is the natural format:
class DocsController < ApplicationController
def show
@doc = Documentation.find_by!(slug: params[:slug])
respond_to do |format|
format.html { render :show }
format.md { render markdown: @doc }
end
end
endDocumentation tools and static site generators can fetch content in markdown format and process it as needed.
Export Functionality
Users often want to export content in markdown for use in other tools:
class NotesController < ApplicationController
def export
@note = current_user.notes.find(params[:id])
respond_to do |format|
format.md do
send_data @note.to_markdown,
filename: "#{@note.title.parameterize}.md",
type: "text/markdown"
end
end
end
endContent Negotiation
Rails handles content negotiation automatically. Clients can request markdown in several ways:
Using Accept Header
curl -H "Accept: text/markdown" https://example.com/pages/1Using Format Extension
curl https://example.com/pages/1.mdUsing Format Parameter
curl https://example.com/pages/1?format=mdAll three approaches work out of the box
with the standard respond_to block.
Combining with Views
We can also use markdown templates directly.
Create a view with the .md.erb extension:
<%# app/views/articles/show.md.erb %>
# <%= @article.title %>
*By <%= @article.author.name %>*
<%= @article.body %>
## Comments
<% @article.comments.each do |comment| %>
- **<%= comment.author %>**: <%= comment.body %>
<% end %>Rails will render the ERB first, then serve the result as markdown.
Testing
Testing markdown responses follows the same patterns as other formats:
class PagesControllerTest < ActionDispatch::IntegrationTest
test "returns markdown format" do
page = pages(:welcome)
get page_path(page), headers: { "Accept" => "text/markdown" }
assert_response :success
assert_equal "text/markdown", response.media_type
assert_includes response.body, "# #{page.title}"
end
test "returns markdown via extension" do
page = pages(:welcome)
get page_path(page, format: :md)
assert_response :success
assert_equal "text/markdown", response.media_type
end
endConclusion
Native markdown rendering in Rails 8.1 reflects how developers work today. With AI tools generating markdown content and documentation living in markdown files, first class support makes sense.
The implementation follows Rails conventions:
to_markdown mirrors to_json,
format.md works like any other format,
and content negotiation handles the rest.
For applications serving AI generated content, documentation, or exportable notes, this feature removes boilerplate and provides a clean, conventional approach.
