Build on Rails

Build a CMS on Rails

Give your editors a real content management system built on the models you already have. Avo renders the posts, pages, rich text, and media library on top of your Rails app, so publishing stays in your codebase.

250+ paying companies · 2.7M downloads of the avo gem

lifecycle · post.status editor to live

draft

in review

scheduled

published_at in the future, and the editor can see exactly which posts are queued.

published

archived

The status enum is the whole publishing workflow. Avo renders it as a badge on Index and a select on the form, no state machine gem required.

01

01 · the hand-built version

What building a CMS by hand actually costs

Most Rails CMS options ask you to adopt their schema and their conventions. When your content does not fit their shape, you spend your time fighting an engine instead of shipping.

  • 01

    The engine owns your content model

    Drop in a full CMS engine and your posts now live in its tables, with its assumptions about slugs, taxonomies, and templates. The day your content needs a field the engine did not anticipate, you are patching someone else's migrations.

  • 02

    Rich text is where the hours go

    Editors expect to paste, format, and embed images inline. Wiring a decent editor, handling uploads, and sanitizing the output is a real project, and it is the same project every time you hand-roll a CMS.

  • 03

    Draft, scheduled, published, archived

    A blog post has a lifecycle, and editors need to see and change it safely. Modeling that state and keeping unpublished work off the public site is easy to get subtly wrong when the admin is hand-built.

  • 04

    Editors are not developers

    The people writing content should never touch the console or a raw form. They need a clean screen with the right fields, sensible validation, and a media library, which is exactly the polish a rushed internal admin skips.

02

02 · the Avo version

Your models are already the hard part. Avo is the rest.

Point Avo at the content models you already have, Post, Page, Author, and it renders the writing and publishing screens on top of your existing tables. The rich editor your writers want is the trix field, one declaration, with Action Text as an option when you want to reuse the rich text your public views already render.

Your content stays in your schema

You keep ownership of the Post and Page models and their columns. Avo puts an editorial interface on top with the field DSL, so no engine dictates how your content is shaped. See https://docs.avohq.io/4.0/resources.html.

Rich text that just works

field :body, as: :trix renders a Trix WYSIWYG editor with inline formatting. Add attachment_key: for inline image uploads through Active Storage, or back the attribute with Action Text to reuse the rich text you already render. The heaviest part of any CMS becomes a field declaration plus a line of config.

A media library out of the box

The file and files fields turn a cover image or a gallery into drag-and-drop uploads against your existing attachments. Editors manage media in the same screen where they write.

Publish state editors can trust

A status select mapped to your enum makes draft, scheduled, published, and archived first-class, and a scope keeps unpublished work out of the public feed. The lifecycle lives in your model, not in the admin's head.

Authors, tags, and SEO fields

A belongs_to field ties each post to its author, a tags field handles categorization, and plain text fields cover the slug and meta description. Everything an editor needs to publish sits on one screen.

Reviewers and writers, separated

Pundit-style policies let writers draft while only editors publish, so the publish button is not a trust exercise. See https://docs.avohq.io/4.0/authorization.html.

A Post resource with rich text, the whole file:

class Avo::Resources::Post < Avo::BaseResource
  self.title = :title
  self.includes = [:author]

  def fields
    field :id, as: :id
    field :title, as: :text, required: true
    field :slug, as: :text, help: "Used in the public URL"
    field :body, as: :trix
    field :cover_image, as: :file
    field :author, as: :belongs_to
    field :status, as: :select, enum: ::Post.statuses
    field :published_at, as: :date_time
    field :tags, as: :tags
    field :meta_description, as: :textarea, hide_on: :index
  end
end
03

03 · the timeline

From first resource to the team running on it

Hour one

Install Avo and point it at Post, Page, and Author. Your existing articles show up in an editor screen you can actually write in.

The first afternoon

Turn the body into a trix field, add a cover image, and map the status select to your publish enum. Writing and scheduling a post starts to feel like a real CMS.

Day one

Put it behind your existing auth with a policy that lets writers draft and editors publish, then ship it so the content team works in production.

By the end of the week

Your editors run publishing from Avo. The old rich-text hack and the read-only admin are gone, and the CMS is just resources in your Rails app you can extend anytime.

Prefer to prompt?

Hand this to Claude Code, Cursor, or whatever agent you build with, and review what it ships like any other pull request.

Build a fresh Ruby on Rails content management system from nothing, with Avo 4 (https://avohq.io, docs at https://docs.avohq.io/4.0/) as the editorial UI. There is no app and no schema yet, so scaffold everything.

1. Generate the application with rails new using the standard defaults and a real database. Set up sign-in with Rails' built-in authentication generator so there is a User to hang editor accounts on. Add the avo gem, bundle, run bin/rails generate avo:install, and mount Avo at /avo so only a signed-in user reaches it.
2. Design the content model from first principles before you write a single migration. A CMS revolves around the writing your public site renders, the people who produce it, and the workflow that moves a piece from idea to live page. Decide which of those deserve their own tables, how they relate (an author has many posts, a post carries tags, standalone pages may stand apart from the blog feed), and where publish state belongs. Model that state as an enum on the record when the transitions are simple, or as its own table when you need history or scheduling. Treat slugs, SEO metadata, cover images, and scheduled-at timestamps as columns you add on purpose, not as an afterthought. Sketch the whole schema, then generate the migrations.
3. Generate an Avo resource per model with bin/rails generate avo:resource and make the API concrete. Set self.title to the human label editors recognize (a post's headline, an author's name), list the columns worth searching in self.search, and eager-load associations through self.includes so Index views stay fast. Match each field to its column: field as: :trix with attachment_key: for the long-form body so writers get a WYSIWYG editor with inline Active Storage uploads, a file field for cover images and a media library, a select mapped to your status enum, a date_time for the publish timestamp, a belongs_to for the author, a tags field for categorization, and textarea or text fields for slug and meta description, hidden from Index where they only add noise. See https://docs.avohq.io/4.0/resources.html.
4. Turn the editorial verbs into Avo Actions rather than console work: publish, unpublish, schedule, archive. Put the real behavior in the model or a small service object (a publish! method that stamps published_at and flips the enum, a scheduler that queues future posts) and have each action call it, so the workflow logic lives in Ruby you can test, not in the admin.
5. Give editors the overview a CMS needs. Add a model scope for published content and surface it as an Avo scope so drafts and scheduled work stay out of the live view, add filters by status and author, and build a dashboard with metric cards for posts published this week, drafts awaiting review, and posts per author (https://docs.avohq.io/4.0/dashboards.html).
6. Authorize with Pundit so roles are enforced, not assumed. Writers create and edit their own drafts, editors publish and manage everyone's work, and the publish action is gated to the editor role so the button is a permission rather than a trust exercise (https://docs.avohq.io/4.0/authorization.html).
7. Seed the database with believable sample content: a handful of authors, a spread of posts across every lifecycle state including a scheduled one, tags, a couple of standalone pages, and cover images, so every screen, scope, filter, and dashboard card has something real to render.
8. Boot the app, sign in, open /avo, write a draft with an inline image, run the publish action, and confirm the post moves to published while the drafts and scheduled pieces stay off the public feed.

04 · start today

Stand up a CMS this week

Avo is per app with unlimited users, so the whole team works from it without the bill scaling with headcount. When you hit an edge Avo doesn't cover, you drop to plain Rails instead of filing a feature request.

More things teams run from their Rails app

Avo by industry

Avo for your team

Frequently asked questions

Can I build a CMS with Ruby on Rails?
Yes. A content management system is posts, pages, media, and a publish workflow, all of which are ordinary Rails models. The effort is in the editing interface: rich text, uploads, and a clear publish state. Avo generates that interface from your models, so you build a CMS without adopting a heavy engine or leaving Rails.
What is the fastest way to build a CMS in Rails?
Keep your content as normal Active Record models and let Avo resources render the editorial screens. Rich text comes from the trix field, media from the file field, and publish state from a status select. You get a usable CMS on the first day instead of integrating and customizing a full-stack engine.
Does Avo support rich text and Action Text?
Yes. Declaring field :body, as: :trix gives editors a Trix rich text editor, with inline image uploads available through Active Storage via the attachment_key option. If your public views already use Action Text, back the field with it so the admin and the site share the same rich text.
How is Avo different from Refinery, Alchemy, or ComfortableMexicanSofa?
Those are CMS engines that bring their own content schema and page model, which is great until your content does not fit their shape. Avo is a framework that puts an editorial interface on the models you define, so you own the schema. When you need something custom, you write plain Rails instead of overriding an engine.
Can editors publish without touching code or the console?
Yes. Avo gives writers a clean screen with the right fields, validation, and a media library, so they never see a raw form or a Rails console. Policies can let writers draft while only editors publish, keeping the workflow safe for a non-technical content team.
Can I add SEO fields and custom metadata to content?
Yes. Since the content model is yours, adding a meta description, an Open Graph image, or a canonical URL is a column plus one line in the resource. There is no vendor cap on what a post can store, so your CMS grows with whatever your content and marketing team need.