Make your Rails CLI output beautiful. Railbow enhances migrations, routes, stats, notes, and more with colorful, emoji-rich, information-dense formatting.
Features
- Migrations - colorful
db:migrateoutput with readable millisecond timing - Migration Status - rich
db:migrate:statuswith git authors, calendar view, table detection, landing dates, and time filtering - Routes - color-coded HTTP verbs and highlighted parameters in
rails routes - Stats - beautiful
rails statstables - Notes -
rails noteswith git blame, author colors, date filtering, and sorting - About - polished
rails aboutoutput - Git Integration - authors, diffs, branch origin, landing dates, uncommitted file indicators
- Calendar View - month separators plus week ticks or full week rows, with optional per-section counts
- Multiple Databases - read one table by default, the rest summarized in a line each (and expanded when they have migrations pending); sharded databases merged with a status per shard
- Ghost Recovery - uses the mighost gem (optional) to recover names, authors, and branch badges for
NO FILEmigrations - Smart Defaults - auto-disables in CI, piped output,
NO_COLOR, and LLM agents
Installation
Option A: Add to your Gemfile (recommended)
gem "railbow", group: :development
bundle install
Option B: Use the CLI wrapper (no Gemfile changes)
gem install railbow
railbow rake db:migrate:status
railbow rails routes
The CLI creates a temporary wrapper so Railbow loads automatically without modifying your project's Gemfile.
Usage
Railbow works automatically once installed. Every example below works with both rails and rake commands.
rails db:migrate
Before:
== CreateProducts: migrating =================================================
-- create_table(:products)
-> 0.0028s
== CreateProducts: migrated (0.0028s) ========================================
After:
π CreateProducts: migrating...
β create_table(:products) β 2.8ms
β
CreateProducts: migrated (2.8ms total)
rails db:migrate:status
This is Railbow's flagship feature. The plain Rails output:
Status Migration ID Migration Name
--------------------------------------------------
up 20260202145343 Add weight field to animals
up 20260203134528 Create vaccination records
up 20260210183902 Create pet tags
up 20260303120000 Add breed restrictions to adoption policies
up 20260313132325 Create veterinary appointments
becomes a rich, information-dense dashboard (with default config):
Feb 2026 W06
ββ β 20260202145343 β 2026-02-02 14:53:43 β Add weight field to animals β animals
ββ β 20260203134528 β 2026-02-03 13:45:28 β Create vaccination records β vaccination_records
ββ β 20260210183902 β 2026-02-10 18:39:02 β Create pet tags β€» Mar 04 β pet_tags
ββββββββΌβββββββββββββββββΌββββββββββββββββββββββΌββββββββββββββββββββββββββββββ
Mar 2026 W10
ββ β 20260303120000 β 2026-03-03 12:00:00 β Add breed restrictions toβ¦ β€» Mar 13 β adoption_policies
ββ β 20260313132325 β 2026-03-13 13:23:25 β Create veterinary appointmβ¦ β PS-142 β veterinary_appointments
Out of the box you get:
- Calendar separators - month + ISO week headers to orient you in time
- Status aliases -
ββ/ββinstead ofup/down(customizable) - Created At - timestamp parsed from the migration ID
- Landing dates -
β€» Mar 04badge when a migration was merged to main after its creation - Branch badges -
β PS-142showing the source branch/ticket - Affected tables - color-coded table names extracted from migration files
- Time filtering - only the last 70 days shown by default (
since: 70d), as a soft limit: never fewer than 10 migrations, however old (since_min: 10) - Your migrations highlighted - rows authored by you are visually distinct
- Pending migrations greyed out - a
downrow keeps its status glyph but loses its colors
Weekly view
By default each new ISO week is marked with a tick on the date column
(calendar: wticks). For a fuller weekly breakdown, wdividers gives every
week its own separator row, and counts appends how many migrations sit under
each separator, week or month:
RBW_CALENDAR=wdividers,counts rake db:migrate:status
Feb 2026 W06 Β· 2 migrations
ββ β 20260202145343 β 2026-02-02 14:53:43 β Add weight field to animals β animals
ββ β 20260203134528 β 2026-02-03 13:45:28 β Create vaccination records β vaccination_records
Feb 2026 W07 Β· 1 migration
ββ β 20260210183902 β 2026-02-10 18:39:02 β Create pet tags β pet_tags
Mar 2026 W10 Β· 1 migration
ββ β 20260303120000 β 2026-03-03 12:00:00 β Add breed restrictions toβ¦ β adoption_policies
Mar 2026 W11 Β· 1 migration
ββ β 20260313132325 β 2026-03-13 13:23:25 β Create veterinary appointmβ¦ β PS-142 β veterinary_appointments
Every separator carries the month, so the week number always sits at the same
offset instead of shifting left on rows that open no new month, and every
separator is drawn in the same muted purple. Both labels are strftime
patterns: label: for months, wlabel: for weeks (defaulting to the same
pattern).
RBW_CALENDAR is the list of week markers, so an empty value leaves you with
month separators and nothing else:
view: "calendar,tables"
calendar: "" # month separators only
Dropping calendar from view instead removes the month separators too.
Multiple databases
Rails runs db:migrate:status once per database, so a multi-database app used
to get one blind table per database: widths that did not line up, the help
printed once per database, and every git lookup repeated. Railbow now renders
the whole run together.
By default you read one table: the first database in database.yml is rendered
in full and every other one is summarized in a line each (db: focus).
π 2 databases Β· primary, raw
ββββ primary Β· myapp_development ββββ 122 of 1500 ββββββββββββββββββββββ
ββ β 20260721130000 β 2026-07-21 13:00:00 β Partition sms notifications β sms_notifications
ββ β 20260727170000 β 2026-07-27 17:00:00 β Add cr push columns to lines β lines
β― raw Β· 5 migrations, all applied Β· latest Jul 22 2026
A database with migrations pending is always expanded, however focused the run is. The summary exists to hide what needs no action, and pending work is the opposite of that.
Setting db to an empty value expands every database that has recent activity,
each as its own section with column widths shared so the tables line up:
db: "" # or RBW_DB= on the command line
ββββ primary Β· myapp_development ββββ 122 of 1500 ββββββββββββββββββββββ
ββ β 20260721130000 β 2026-07-21 13:00:00 β Partition sms notifications β sms_notifications
ββββ raw Β· raw_development βββββββββββββββββββββββββββββββββββββββββββββ
ββ β 20260722120000 β 2026-07-22 12:00:00 β Drop archived activity logs β archived_activity_logs
A section header counts what it is showing: 122 of 1500 means 122 fell inside
the time window. When the window found too few and the since_min floor topped
the section back up, it reads last 10 of 50 instead, because the rows are
then the newest ten rather than a window's worth.
Even then, a database with nothing in the time window collapses to one line
rather than an empty table, which is what keeps the cache, queue and
cable databases of a stock Rails 8 app from drowning out your own:
β― cache Β· 5 migrations, all applied Β· latest Feb 12 2024
β― queue Β· 12 migrations, 3 pending outside the 70d window - RBW_SINCE=all
Databases that share a migrations_paths (horizontal sharding) run the same
files, so they merge into one table carrying a status per database. Drift
between shards is then visible at a glance, and Β· marks a version a shard has
never seen:
ββββ primary + primary_shard_one Β· db/migrate ββββββββββββββββββββββββββ
ββ ββ β 20260601151200 β 2026-06-01 15:12:00 β Add owner id to pets
ββ ββ β 20260726143000 β 2026-07-26 14:30:00 β Add policy number to bills
RBW_DB controls the rest: only:<name> and skip:<name> (both repeatable)
pick databases, full draws every section in full and overrides focus, and
inline merges everything into a single table ordered by version with a Db
column, so the calendar spans the whole application rather than restarting per
database. focus never applies to an inline run - a merged table exists to
hold every database at once.
A single-database app sees none of this: its output is unchanged.
If the mighost gem is installed (gem "mighost", group: [:development, :test]), migrations whose files were deleted (e.g. after switching branches) show up with a π» status and their recovered name instead of a bare ********** NO FILE ********** row - Railbow talks to it through the stable Mighost::API. Each ghost row carries the most informative badge mighost can provide: β‘ <version> when the migration was re-timestamped and lives on under another version (shown with a calmer πͺ¦ status), β₯ <branch> when a branch still holds the file, or β deleted in:<sha> pointing at the commit that removed it. Ghosts dismissed via mighost:dismiss (or hidden by hide_superseded) render as plain NO FILE.
rails db:migrate:down
βͺ CreateProducts: reverting...
β drop_table(:products) β 1.2ms
β
CreateProducts: reverted (1.2ms total)
rails routes
HTTP verbs are color-coded (GET=green, POST=yellow, PATCH/PUT=cyan, DELETE=red), route parameters (:id, *splat) are highlighted, and controller#action pairs stand out.
rails stats
Code statistics rendered as a colorful table with highlighted totals and code-to-test ratio.
rails notes
Annotations enriched with git blame data - author names, commit dates, and color-coded tags (TODO=yellow, FIXME=red, OPTIMIZE=cyan, HACK=red, NOTE=green).
Configuration
Railbow works with zero configuration, but everything is customizable.
Config files
Config is loaded in layers (each overrides the previous):
- Built-in defaults
- Global:
~/.config/railbow/config.yml - Project:
.railbow.yml(commit to git) - Local:
.railbow.local.yml(gitignored, personal overrides)
Generate a config interactively:
railbow init
# or, within a Rails project:
rake railbow:init
Example .railbow.yml
since: 70d
since_min: 10
date: rel
git: "author:me,diff,mask:auto"
view: "calendar,tables"
calendar: wticks
db: focus
compact: "maxw:120"
aliases:
columns:
Status: Live
values:
Status:
up: "ββ"
down: "ββ"
Environment variables
Every option can also be set via RBW_* environment variables, which override config files:
| Variable | Example | Description |
|---|---|---|
RBW_PLAIN |
1 |
Disable all formatting |
RBW_FORCE |
1 |
Force formatting even when piped, in CI, or run by an LLM agent (RBW_PLAIN=1 still wins) |
RBW_SINCE |
2mo, 70d, 1y, all |
Filter migrations by time period |
RBW_SINCE_MIN |
10, 0 |
Never show fewer than this many migrations, however old (default 10; 0 disables) |
RBW_DATE |
full, rel, short, custom(%b %d) |
Date display format |
RBW_GIT |
author:me,diff,mask:auto |
Git integration options |
RBW_VIEW |
calendar,tables |
Enable calendar view and table detection |
RBW_CALENDAR |
wticks, wdividers,counts, `` (empty) |
Week markers: tick marks, full week separator rows, per-section counts. Empty means month separators only |
RBW_DB |
focus (default), `` (empty), only:primary, skip:cache, full, inline |
Multi-database runs: focus one database, expand them all, pick some, or merge them into one table |
RBW_COMPACT |
oneline,dense,noheader,maxw:80 |
Compact display options |
RBW_VERB |
GET,POST |
Filter routes by HTTP method |
RBW_SORT |
file, date |
Sort order for notes |
RBW_HELP |
1 |
Show help messages |
Quick examples
# Last 2 months, calendar view
RBW_SINCE=2mo rake db:migrate:status
# Relative dates, highlight your migrations
RBW_DATE=rel RBW_GIT=author:me rake db:migrate:status
# Full git context with table detection
RBW_GIT=author:all,diff RBW_VIEW=tables rake db:migrate:status
# Only GET routes
RBW_VERB=GET rails routes
# Notes sorted by commit date
RBW_SORT=date rails notes
How It Works
Railbow integrates through a Rails Railtie - it prepends formatter modules onto existing Rails classes without modifying your code:
ActiveRecord::Migration- migration outputActiveRecord::Tasks::DatabaseTasks- migration statusActionDispatch::Routing::ConsoleFormatter::Sheet- routesRails::Info- aboutRails::SourceAnnotationExtractor- notes
Formatting auto-disables when:
RBW_PLAIN=1orNO_COLORis set- Running in CI (
CIenv var) - Output is piped or redirected (non-TTY)
- Running inside an LLM agent (
CLAUDECODEenv var)
RBW_FORCE=1 overrides all of the auto-detection above (useful for capturing
formatted output to a file, or letting an agent inspect the real rendering).
An explicit RBW_PLAIN=1 always wins over RBW_FORCE.
Requirements
- Ruby >= 3.1.0
- Rails >= 7.2 (including 8.x)
Development
bin/setup # Install dependencies
bundle exec rake # Run tests + linting (RSpec + Standard)
bin/console # Interactive prompt
Contributing
Bug reports and pull requests are welcome at github.com/amberpixels/railbow.
License
MIT License. See LICENSE.txt.