| name | avo-fields |
| description | Add or change fields in an Avo resource's `def fields` — pick the `as:` type, set options (required, default, help, visibility, formatting), and use computed and view-specific fields. Use when the user wants to add a field to an Avo resource, or — said without naming Avo — "add a status field to the Project model", "make the email field required", "show the user's avatar", "add a dropdown for order status", "the price should display as currency", "add a rich-text editor for the body", "hide the notes field on the index page", "add a star rating", "make the name column sortable", "show a badge for the order state", or "add a color picker / date picker / progress bar to a model". For belongs_to / has_many / has_one and other association fields, use the avo-associations skill instead. |
| allowed-tools | Read, Edit, Write, Glob, Grep, Bash, WebFetch |
| metadata | {"requires-gem":"none — Community; some field types need companion gems (see Gotchas)"} |
Add or change an Avo field
In Avo (a Rails admin framework), the columns shown on a resource's Index, Show, New, and Edit pages are declared as fields inside the def fields method of app/avo/resources/<model>.rb. Almost every "add/change/hide a field on the X model" request lands in that one method.
A field looks like:
field :column_name, as: :field_type, **options
:column_name — a database column or any method/attribute on the model.
as: :field_type — how it renders (:text, :select, :boolean, :money, …). Omit as: and it defaults to :text.
**options — label, visibility, formatting, validation cues, etc. (see Key options).
Docs (fetch on demand — do not rely on memory for exact option names):
When this applies
Use this skill for anything inside def fields except associations:
- Adding, removing, renaming, or reordering a field.
- Changing a field's type (e.g. plain text → a
select dropdown or a badge).
- Setting options: required, default, help text, placeholder, disabled/readonly, sortable, visibility per view, formatting, copyable, etc.
- Computed (block) fields that derive a value not stored in a column.
- Arranging fields with the layout DSL (
panel, card, sidebar, tabs/tab, header, column width).
- Auto-generating fields with
discover_columns / discover_associations.
Not this skill:
belongs_to, has_many, has_one, has_and_belongs_to_many and other relationship fields → avo-associations skill.
- Authoring a brand-new custom field type (a reusable component you invent) → avo-custom-fields skill. Note
rails g avo:field NAME scaffolds a new custom type; it is not how you add a normal field — for that you just edit def fields.
Workflow
-
Find the resource file. It's app/avo/resources/<model>.rb (singular, snake_case — e.g. the Project model → app/avo/resources/project.rb). If unsure, Glob app/avo/resources/*.rb or Grep for the model name. Read the file and locate def fields (or the view-specific methods below).
-
Confirm the backing attribute. The first argument should match a real DB column or a model method. Check db/schema.rb (or the model) so you use the right column name and can pick a type that fits its data type. If the value isn't stored anywhere, it must be a computed block field.
-
Pick the field type using the decision table. When in doubt, fetch that type's docs page.
-
Check for a gem gate. A few types need a companion gem or ENV var and will error without it — see Gotchas. If the user asks for one of those, tell them the gem to add.
-
Place the field in the intended view(s). By default a field shows on all four views; use def fields for all views, or a view-specific method (below) when a view needs a different set. Respect existing ordering and formatting in the file.
-
Add options — start minimal (as: + label if needed), then layer on only what the request asks for.
-
Report what changed (see Report). Don't run the app; if you want to sanity-check syntax, a ruby -c on the file is enough.
Which method to edit
def fields is the catch-all used for every view when no more specific method exists. Override per view or per view-group only when a view genuinely needs a different set:
| Method | Applies to |
|---|
def fields | any view with no specific method |
def index_fields | Index |
def show_fields | Show |
def edit_fields | Edit and Update |
def new_fields | New and Create |
def display_fields | Index and Show |
def form_fields | New, Create, Edit and Update |
Specific view methods beat view-group methods, which beat fields. Prefer a single def fields + per-field hide_on:/only_on: for small differences; reach for separate methods only when the field lists really diverge.
Choosing a field type
Map the need to an as: type. All built-in types are Community (free); the ⚠️ ones need a companion gem (see Gotchas).
| The user wants… | Use | Example |
|---|
| A short single-line string | :text | field :title, as: :text |
| A long / multi-line string | :textarea | field :body, as: :textarea |
| A number | :number | field :age, as: :number |
| A masked password input | :password | field :password, as: :password |
| A yes/no checkbox | :boolean | field :active, as: :boolean |
| A hash of on/off toggles | :boolean_group | field :roles, as: :boolean_group, options: {admin: "Admin", editor: "Editor"} |
| A dropdown of fixed choices | :select | field :type, as: :select, options: {Draft: :draft, Live: :live} |
| Multiple choices from a fixed set | :select (multiple: true) or :checkbox_list | field :tags, as: :select, multiple: true, options: {...} |
| Radio buttons | :radio | field :plan, as: :radio, options: {...} |
| A country picker | :country | field :country, as: :country |
| A date | :date | field :birthday, as: :date |
| A date and time | :date_time | field :published_at, as: :date_time |
| A time only | :time | field :opens_at, as: :time |
| A colored status pill (map value → color) | :badge | field :status, as: :badge, options: {success: "done", warning: "pending"} |
|
For anything relationship-shaped ("show the user's posts", "attach an author") use the avo-associations skill.
Key options
Every field accepts these common options (full list + types at the field-options-api page). Pass a literal value or, unless noted, a lambda.
name — override the label (default is the humanized id). field :is_available, as: :boolean, name: "Availability". For localized apps, translate via i18n instead of hardcoding.
- Visibility —
hide_on:, show_on:, only_on:, except_on: take :index, :show, :new, :edit, :preview plus shorthands :forms (new+edit), :display (index+show), and :all (only for hide_on/show_on). Example: field :notes, as: :textarea, hide_on: [:index, :show].
visible: — a lambda for conditional display. It can see context and resource; the record is resource.record. On create the record is nil, so use safe navigation: visible: -> { resource.record&.published? }.
required: — adds an asterisk (cosmetic only; Avo adds it automatically when the model has a presence validator). Real enforcement is model validation.
disabled: vs readonly: — both render the input disabled on forms. disabled: also ignores the value on save (safe against DOM tampering). readonly: is UI-only — a user can re-enable it and submit; don't rely on it for protection.
default: — pre-fills the New form (and action modals): default: -> { Time.current }.
help: / label_help: — help text (HTML allowed) below the input (forms only) or below the label (every view).
placeholder: — placeholder for empty text-like inputs.
sortable: — makes the Index column sortable. true for real columns; a -> { query.order(...) } lambda (receives query and direction) for computed fields.
Computed (block) fields
When the value isn't a stored column, pass a block. Inside it you have record, resource, and view:
field "Full name", as: :text do
"#{record.first_name} #{record.last_name}"
end
field "Has posts", as: :boolean do
record.posts.any?
end
Block fields render only on Index and Show (they have no input) and can't use sortable: true (pass a sort lambda instead). Give them an explicit label string as the first argument.
Layout inside def fields
The same method arranges fields. Use panel/card to group, sidebar (inside a panel) for compact fields, tabs/tab for tabbed sections, header to reposition the page chrome, and per-field width: for multi-column rows. This overlaps page-level layout — see the fields-layout docs page before building complex structures.
def fields
field :id, as: :id
panel title: "Details" do
field :first_name, as: :text, width: 50
field :last_name, as: :text, width: 50
sidebar do
field :active, as: :boolean, only_on: :show
end
end
end
Field discovery
To auto-generate fields from the model instead of listing them, call discover_columns (columns, rich text, tags) and discover_associations (attachments, relationships). Scope with only:/except:, and pass any other keyword to forward it to every discovered field:
def fields
discover_columns only: [:title, :body, :published_at]
discover_associations except: [:audit_logs]
end
Gotchas
- Gem-gated types break without their companion gem (the field silently fails to render or raises). If the user asks for one, tell them to add the gem:
:rhino → gem "avo-rhino_field"
:markdown → gem "marksmith" + gem "commonmarker"
:money → gem "avo-money_field" + gem "money-rails", "~> 1.12" (and monetize :price_cents on the model)
:location / :area → gem "mapkick-rb" (not mapkick) + MAPBOX_ACCESS_TOKEN env var
- Reactive fields (
react_on:) → gem "avo-reactive_fields"
- Some of these gems live on the
packager.dev source — point the user to the Avo 4 upgrade guide's gems section.
tip_tap is deprecated — use :rhino for a WYSIWYG editor.
markdown was renamed. The old markdown field is now easy_mde; the new markdown is the Marksmith editor. Don't confuse them.
required: and readonly: are cosmetic. Enforce with model validations (validates :x, presence: true) and use disabled: when you need the value ignored on save.
format_using: runs on form views too — return the raw value when view.form? so the input stays editable, or use a format_display_using:/format_*_using: variant.
- Computed block fields show only on Index/Show and can't be
sortable: true.
visible: lambdas see resource.record == nil on create — always safe-navigate (resource.record&.foo).
select takes exactly one of options:, grouped_options:, or enum: — never combine them.
Report
After editing, tell the user:
- The file and method you changed (
app/avo/resources/<model>.rb → def fields).
- Each field added/changed: column,
as: type, and any notable options.
- Any gem or ENV var they must add for a gated type, and any model change needed (validation for
required, monetize for money, an enum for an enum-backed select).
- Which views the field now appears on, if you set visibility.