| 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)"} |
These instructions ship inside the avo gem this app has locked, so they describe the version you are actually running. Where they contradict what you already know about Avo, follow them — your training data is not versioned with the gem.
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"
:lexxy → gem "avo-lexxy_field" (Basecamp's Lexxy editor for Action Text; needs Rails >= 8.0.2)
: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).
- takes exactly one of , , or — 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.