Skip to content

Generators

terrazzo:install

Sets up Terrazzo in your Rails app.

bash
rails g terrazzo:install

Options

OptionDefaultDescription
--namespaceadminAdmin namespace (controller prefix, route scope, JS directory)
--bundlerviteJavaScript bundler. vite emits vite_client_tag and vite_javascript_tag for the explicit admin JSX entrypoint; esbuild emits javascript_include_tag and a root app/javascript/admin.js entrypoint for Rails' default esbuild glob. Page mappings are explicit and can be extended through custom_page_mapping.js.
bash
# Vite (default)
rails g terrazzo:install

# esbuild
rails g terrazzo:install --bundler=esbuild

# Custom namespace
rails g terrazzo:install --namespace=backstage

For older scripts, rails g terrazzo:install backstage is also accepted as a namespace alias. Prefer --namespace; do not combine both forms with different values.

Creates:

  • Admin Superglue infrastructure (entry point, page mapping, visit handler, flash slice, layouts)
  • A Vite entrypoint shim under the app's configured Vite entrypoints directory when --bundler=vite (default: app/frontend/entrypoints/admin/application.jsx)
  • Vite watchAdditionalPaths entries for generated admin JS and view files that live outside Vite's sourceCodeDir
  • Vite local-development wiring so Procfile.dev runs bin/vite dev and a stock Rails-only bin/dev starts Procfile.dev
  • app/javascript/admin/generated_page_mapping.js for resource-specific view mappings generated by Terrazzo
  • app/javascript/admin/custom_page_mapping.js for manual page mappings you own
  • app/assets/stylesheets/admin.css with Tailwind sources for package defaults and local admin files
  • components.json with shadcn aliases for the admin component and UI primitive paths, unless the app already has one
  • jsconfig.json with a root @/* path alias for shadcn-style tooling, unless the app already has jsconfig.json or tsconfig.json
  • Admin application controller
  • Admin routes namespace
  • Dashboards for all existing ApplicationRecord models
  • App-level component, field, and UI barrels that fall back to the terrazzo package

When --bundler=vite, the installer requires vite_rails because the generated admin layout uses Vite Rails asset helpers. Add gem "vite_rails" and run bundle exec vite install first so config/vite.json, vite, and vite-plugin-ruby are present, or pass --bundler=esbuild. For Vite installs, the generator also ensures Procfile.dev includes vite: bin/vite dev. If bin/dev is the stock Rails-only server script, it is replaced with a Foreman runner. Custom bin/dev scripts are preserved and reported so you can wire Vite into your own dev process. The generator checks package.json for the required Terrazzo frontend packages and prints the package-manager command to install anything missing or pinned below Terrazzo's supported version floors. This includes Tailwind CSS 4 or newer because the generated admin stylesheet uses Tailwind 4 syntax. It also adds a build:admin:css package script when @tailwindcss/cli 4 or newer is installed and no Tailwind build script or Tailwind 4-compatible tailwindcss-rails setup compiles Terrazzo's Rails-linked admin stylesheet. For Vite installs, it adds a package build script when one is missing; if Terrazzo created build:admin:css, that CSS command is included in build. If Tailwind package tooling is missing or too old, it warns with the matching install command. The @tailwindcss/vite plugin alone is not treated as sufficient because the generated layout links app/assets/stylesheets/admin.css as a Rails stylesheet asset. When components.json does not exist, the install generator creates one that points shadcn CLI output at app/views/admin/components and app/views/admin/components/ui (or the matching custom namespace). Existing components.json files are left untouched. When neither jsconfig.json nor tsconfig.json exists, the installer also creates a small jsconfig.json so editor tooling and shadcn-style generators can resolve root @/* imports. Existing JS/TS configs are left untouched. The installer also verifies that at least one concrete ApplicationRecord model exists and that those model tables exist before generating dashboards. Run bin/rails db:prepare first if the generator reports missing tables. Resource-specific view generators update generated_page_mapping.js; add hand-written custom pages to custom_page_mapping.js.

terrazzo:dashboard

Generates a dashboard for a specific model.

bash
rails g terrazzo:dashboard Product
rails g terrazzo:dashboard Blog::Post

The model must already exist; the generator inspects the model class to infer fields, associations, enums, rich text, and attachments.

Options

OptionDefaultDescription
--namespaceadminAdmin namespace
--bundlerviteJavaScript bundler (vite or esbuild). Resource-specific view mappings are handled by generated_page_mapping.js when needed.

Creates:

  • app/dashboards/product_dashboard.rb
  • app/controllers/admin/products_controller.rb

For namespaced models, terrazzo:dashboard Blog::Post writes app/dashboards/blog/post_dashboard.rb and app/controllers/admin/blog/posts_controller.rb with explicit Blog::PostDashboard and Admin::Blog::PostsController module nesting. Deeper namespaces follow the same convention, so Store::Catalog::Product writes app/dashboards/store/catalog/product_dashboard.rb and app/controllers/admin/store/catalog/products_controller.rb.

The generator inspects your model's columns and associations:

  • Primary key columns → Field::String
  • String columns → Field::String
  • Integer/float columns → Field::Number
  • Boolean columns → Field::Boolean
  • Date columns → Field::Date
  • DateTime columns → Field::DateTime
  • Text columns → Field::Text
  • has_rich_text attributes → Field::RichText
  • Enums → Field::Select with collection
  • belongs_to associations → Field::BelongsTo
  • belongs_to polymorphic associations → Field::Polymorphic
  • has_many associations → Field::HasMany
  • has_one associations → Field::HasOne
  • has_one_attached Active Storage attachments → Field::Asset

FORM_ATTRIBUTES excludes id, created_at, and updated_at. COLLECTION_ATTRIBUTES is limited to 4 attributes.

terrazzo:field

Generates a custom field type and app-owned React components.

bash
rails g terrazzo:field Rating

Creates:

  • app/fields/terrazzo/field/rating.rb
  • app/views/admin/fields/rating/IndexField.jsx
  • app/views/admin/fields/rating/ShowField.jsx
  • app/views/admin/fields/rating/FormField.jsx

The generator creates or updates the app-level field and UI barrels, then registers the custom field components in app/views/admin/fields/index.js so package pages and ejected pages can render Field::Rating.

For custom namespaces, pass the same namespace you used at install time:

bash
rails g terrazzo:field Rating --namespace=backstage
OptionDefaultDescription
--namespaceadminAdmin namespace whose field barrel should register the custom field components

terrazzo:views

Regenerates the shared page stubs, app-level barrels, and navigation props.

bash
rails g terrazzo:views

Use this after upgrading the gem to refresh the default wiring. This will overwrite local edits to those generated stubs, barrels, and navigation props. Eject individual files when you want app-owned React source.

terrazzo:views:index, terrazzo:views:show, terrazzo:views:new, terrazzo:views:edit

Ejects a view for customization. Without a resource argument, ejects the shared JSX page component. With a resource argument, ejects a complete resource-specific view: the page JSX, its associated partial (_collection.jsx or _form.jsx), and a .json.props that extends the base serialization.

If the app-level field, component, or UI barrels are missing, these generators create the base package re-exports before writing the page files. When a shared page target still contains Terrazzo's generated package stub, the generator replaces it without a conflict prompt. Customized shared pages still use Rails' normal overwrite prompt.

bash
# Eject the shared JSX page component
rails g terrazzo:views:show

# Eject a resource-specific view
rails g terrazzo:views:show User
rails g terrazzo:views:index Order
rails g terrazzo:views:edit Product
rails g terrazzo:views:new BlogPost
rails g terrazzo:views:index Catalog::Product
rails g terrazzo:views:index catalog/product

Resource arguments accept either Ruby constant style (Catalog::Product) or Rails path style (catalog/product).

When ejecting for a resource, the generator creates a complete set of files:

  • terrazzo:views:index Userindex.jsx + _collection.jsx + index.json.props
  • terrazzo:views:edit Useredit.jsx + _form.jsx + edit.json.props
  • terrazzo:views:new Usernew.jsx + _form.jsx + new.json.props
  • terrazzo:views:show Usershow.jsx + show.json.props

Because edit and new share the same _form.jsx partial via a relative import, ejecting one without the other means the non-ejected view continues using the built-in form. If the counterpart is still Terrazzo's generated package stub, form-page ejection replaces it automatically so both pages use the same app-owned form. For missing resource-specific counterparts, the edit and new generators prompt unless you pass --with-counterpart or --no-with-counterpart.

The ejected .json.props calls the gem's base partial and leaves room for custom props:

ruby
# app/views/admin/users/show.json.props
json.partial! partial: "terrazzo/application/show_base"
# Add custom props below:
# json.customProp @resource.some_method

Resource-specific view generators register the page in app/javascript/admin/generated_page_mapping.js and make sure page_to_page_mapping.js merges that manifest. If the main mapping file has been customized so heavily that Terrazzo cannot find a pages object to merge into, the generator fails instead of creating an unreachable page. Either restore the generated mapping shape or wire generatedPageMapping into your custom mapping file yourself.

Options

OptionDefaultDescription
--namespaceadminAdmin namespace
--with-counterpart / --no-with-counterpartpromptFor views:new and views:edit, choose whether to also eject the counterpart page that shares _form.jsx

terrazzo:eject

Ejects supported app-owned starter files from Terrazzo.

bash
rails g terrazzo:eject pages/index
rails g terrazzo:eject pages/edit
rails g terrazzo:eject fields/string
rails g terrazzo:eject fields/asset
rails g terrazzo:eject fields/number # also customizes Field::Money rendering
rails g terrazzo:eject components/Layout
rails g terrazzo:eject components/ResourceTable
rails g terrazzo:eject ui/button
rails g terrazzo:eject navigation

For custom namespaces, pass the same namespace you used at install time:

bash
rails g terrazzo:eject pages/index --namespace=backstage
OptionDefaultDescription
--namespaceadminAdmin namespace whose app-owned files should receive the ejected source

Unsupported targets fail the generator instead of silently doing nothing, so typos are safe to catch in scripts and CI. When a target path still contains Terrazzo's generated package stub, ejection replaces it without a conflict prompt. If the file has already become app-owned/customized, Rails' normal overwrite prompt still applies. Dependency files copied only to satisfy the requested target's imports are non-destructive: Terrazzo skips them when they already exist and leaves their barrel exports alone. Run terrazzo:eject for that dependency directly when you want Rails' overwrite prompt and registration for it.

Ejected files import through app-level barrels:

  • app/views/admin/fields/index.js keeps export * from "terrazzo/fields" and registers local field overrides.
  • app/views/admin/components/index.js keeps export * from "terrazzo/components" and registers local component overrides. Ejecting components/Layout also registers it with setLayout.
  • app/views/admin/components/ui/index.js keeps export * from "terrazzo/ui" and exports local UI primitives for app-owned/ejected files.

If one of those barrels is missing, terrazzo:eject creates the base package re-export before writing the ejected file. This keeps standalone ejection commands buildable, while installed apps continue using the barrels created by terrazzo:views.

Page ejection copies required partials with the page when they are missing. For example, pages/index also copies _collection.jsx, and pages/edit or pages/new also copies _form.jsx. If you have already customized one of those shared partials, later page ejections preserve it. If the matching form page is still a generated package stub, ejecting pages/edit or pages/new also replaces that counterpart so both pages use the shared app-owned _form.jsx.

UI ejection copies required local primitive dependencies with the requested file when those dependencies are missing. For example, ui/pagination also copies ui/button, and ui/sidebar also copies the primitives it imports locally. Ejected UI primitives are consumed by app-owned pages, fields, and components that import from ../components/ui; packaged default pages continue using the package defaults until you eject or override the relevant page, field, or component.

The default index already supports search, filter facets, sortable rows, row actions, bulk actions, toolbar actions, page header actions, CSV exports, empty states, header/row/cell metadata, and a pagination footer with page and total-count context. Prefer dashboard hooks such as attribute_label, attribute_hint, collection_header_options, collection_row_options, collection_cell_options, collection_item_actions, collection_bulk_actions, collection_toolbar_actions, layout_actions, csv_attributes, empty_collection_message, and the NAVIGATION_* constants before ejecting full pages or navigation.

The older terrazzo:views:field, terrazzo:views:layout, and terrazzo:views:navigation commands remain as compatibility aliases for the ejection workflow. Prefer terrazzo:eject for new customization.

terrazzo:routes

Generates the admin namespace routes.

bash
rails g terrazzo:routes

Inserts a namespace :admin block with resource routes and a root route into your config/routes.rb. The generator fails if it cannot find any concrete ApplicationRecord models, because there would be no valid resource controller for the admin root. Namespaced models generate matching nested route namespaces, so Store::Catalog::Product routes to admin/store/catalog/products instead of an invalid combined namespace. If the namespace already exists, it prints the cleanly indented routes to copy into that block instead of rewriting your existing route file.

Released under the MIT License.