Build on Rails
Build a back office on Rails
The screens support and ops live in, orders, refunds, customer lookups, manual overrides, built on the Rails models you already have, with every change attributed to the person who made it.
250+ paying companies · 2.7M downloads of the avo gem
01 · the hand-built version
What building a back office by hand actually costs
A back office is the second app behind your product: the place your team runs orders, refunds, and customer fixes from. Hand-built, it tends to fail in the same few ways.
-
01
The console is your back office
Support pings an engineer to refund an order or fix a shipping address because the only tool that can do it is rails console. Every routine fix becomes a developer interruption and an unlogged change to production data.
-
02
No record of who changed what
Someone issued a refund or flipped a customer to a different plan, and there is no trail of who, when, or why. When finance or the customer asks, you are grepping logs and guessing.
-
03
It grows one patchy screen at a time
An orders page here, a rake task for refunds there, a quick view bolted on for support. Each piece has its own half-considered auth check, or none, and nothing shares a shape.
-
04
Everyone gets the same keys
The tool that looks up a customer can also delete one, and the whole team has identical access because per-person permissions were never worth building for an internal screen.
02 · the Avo version
Your models are already the hard part. Avo is the rest.
Point Avo at the models you already have, Order, Customer, Refund, and you get real back-office screens without leaving your Rails app: list and search records, open a detail view, and run the fixes your team needs as actual actions. Support and ops work from the same data your product runs on, and every action is your code, gated by your Pundit policies and carried out as the signed-in admin.
Every model becomes a working screen
Generate a resource for Order, Customer, and Refund and each one is immediately searchable and filterable, so support finds the record they need instead of asking you to run a query.
Refunds and overrides become real actions
Wrap a refund, a plan change, or an address fix in an Avo Action so support runs it from a button behind a confirmation, instead of you translating the request into a console command.
Find any record in one search box
self.search with a ransack query lets an agent look up an order by number or a customer by email while they are on the phone, not after they have escalated to engineering.
Changes are attributable by default
Actions run as current_user, so the admin who issued a refund is known. Pair it with paper_trail or your own audit table and you have the trail finance keeps asking for.
Support and finance see different things
Pundit policies decide who can view customers, who can issue refunds, and who can delete. Add the whole team without watching a per-seat bill climb, since Avo is priced per app.
Queues for the work support actually does
Filters and scopes surface the records that need a human now, refunds requested, orders on hold, accounts flagged, so the team works a list instead of hunting.
One resource turns the orders table into a searchable, actionable back-office screen:
class Avo::Resources::Order < Avo::BaseResource
self.title = :number
self.includes = [:customer, :refunds]
self.default_sort_column = :placed_at
self.search = {
query: -> { query.ransack(number_cont: q, customer_email_cont: q, m: "or").result(distinct: false) }
}
def fields
field :id, as: :id
field :number, as: :text, readonly: true
field :status, as: :badge, options: {
info: %w[pending],
success: %w[paid fulfilled],
warning: %w[on_hold],
danger: %w[refunded canceled]
}
field :total, as: :number
field :placed_at, as: :date_time, sortable: true
field :customer, as: :belongs_to
field :refunds, as: :has_many
end
def actions
action Avo::Actions::RefundOrder
action Avo::Actions::PlaceOnHold
end
end
03 · the timeline
From first resource to the team running on it
Install Avo and generate resources for Order, Customer, and Refund. You are clicking through real production records in your own app, searching and filtering the same data support has been asking you about all week.
Put it behind your existing auth and Pundit policies, so support can look up customers and orders while only finance can issue a refund. Ship it to production.
The refund, the hold, and the address fix that used to be console commands are actions your team runs themselves, attributed to whoever ran them, and the console stops being your back office.
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.
Scaffold a fresh Ruby on Rails application whose back office runs on Avo 4 (https://avohq.io, docs at https://docs.avohq.io/4.0/). Start from an empty directory and create both.
1. Generate the project with bin/rails new, keeping the default database and asset setup. Create staff login with the built-in authentication generator (bin/rails generate authentication), add the avo gem, run bundle, then bin/rails generate avo:install, and mount Avo at /avo so only a signed-in staff member reaches it.
2. Design the data before writing a single migration. Reason from what running this business actually requires: the transactions or purchases money flows through, the people or accounts behind them, and the records that capture money moving back out such as refunds or credits. For each entity decide its lifecycle, an order that is pending, paid, on hold, or refunded, and model that as an enum when it is a simple status or its own table when a stage carries its own data, then work out how the entities associate. Write the migrations from that plan and run them.
3. Run bin/rails generate avo:resource for every model. On each one, set self.title to the identifier staff quote out loud, add self.search with a ransack query over the columns they look records up by like a reference number or an email, and list the rendered associations in self.includes so the index and detail views do not fire a query per row. Match field types to the columns you created: as: :badge for the status enums, sortable as: :date_time for timestamps, and as: :belongs_to and as: :has_many for the relationships.
4. Turn the operational verbs, issuing a refund, placing an order on hold, correcting an address, changing a plan, into Avo Actions. Since the app is new, put the real logic in model methods or small service objects and have each action call into them behind a confirmation, running as the signed-in admin so who did what is recorded.
5. Add filters and scopes that surface the queues staff work, refunds awaiting approval, orders on hold, accounts flagged for review, and build a dashboard (https://docs.avohq.io/4.0/dashboards.html) with the counts and lists that show the day's operational load at a glance.
6. Gate everything with Pundit policies (https://docs.avohq.io/4.0/authorization.html): support can look up customers and orders, ops can act on them, and only finance can run the money-moving actions. Pair the actions with paper_trail or a small audit table so finance gets the durable trail of who changed what and when.
7. Seed the database with believable sample data, several customers, their orders across every status, and a few refunds, so every screen, filter, and dashboard has something real to show on first boot.
8. Boot the app, sign in, open /avo, look up an order by its number, and run the refund action on it, confirming the status changes and the refund is attributed to you.
04 · start today
Stand up a back office 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
Frequently asked questions
- What is a back office in a Rails app?
- A back office is the internal application your team uses to run the business behind your product: looking up customers, issuing refunds, correcting orders, and handling escalations. It reads and writes the same database your product does, but it is built for staff, not customers, and it is where most day-to-day operational work actually happens.
- Can I build a back office on an existing Rails app?
- Yes. Avo reads the Active Record models you already have, so you point it at Order, Customer, and Refund and get working screens over your current schema. You do not migrate data or stand up a separate service; the back office runs inside your Rails app against the same database.
- How can support issue refunds without a developer?
- Move the refund out of the console and into an Avo Action that calls your existing refund logic. Support runs it from a button behind a confirmation dialog, and your Pundit policy decides who is allowed to. The developer writes the action once instead of running the refund by hand every time.
- How do I track who changed what in a Rails back office?
- Avo Actions run as the signed-in admin, so the person behind each refund or override is known at the time it happens. Combine that with paper_trail or a simple audit table and you get a durable record of who did what, when, and why, which is what finance and support usually need when a customer disputes a change.
- How is a back office different from your resource Index view?
- Your resource Index view is the searchable, filterable list of records, and it is one part of the back office. The full back office adds detail views, the actions your team runs, and the authorization rules that decide who can do what. Avo gives you all of those over your existing models.
- How much does a Rails back office cost with Avo?
- Avo is priced per application with unlimited users, so adding your whole support and operations team does not change the price. That matters for a back office, where the people who need access, agents, ops, finance, are exactly the ones a per-seat tool would charge you for.