Skip to main content
This recipe adds a supplier lead time (lead_time_days, an integer) to products and wires it through every surface the dashboard has:
  • edited on the product form, saved by the form’s own Save button
  • a column in the products table
  • a filter (“lead time > 7 days”) and a sort (“slowest first”) in the list toolbar
It’s the full version of the custom form field recipe — read that one first if you only need the form.

First: should this be a custom field instead?

Default to a custom field definition. It needs zero code — declare the field, and the product form renders and saves it. That covers most plugin data and everything merchants define themselves. Graduate to a real database column only when the attribute needs list operations or database guarantees:
You need…Custom fieldReal column
Editing on the product form✅ zero code✅ this recipe
Filtering / sorting the products list in Admin✅ via Ransack
Merchants defining the field themselves at runtime
Lead time is a column because its whole point is the query: “show me everything that ships slow, slowest first.”

1. Backend

Four small pieces — each one unlocks a specific frontend capability. In a host app these live in backend/ (or api/); in a distributed plugin, in the Rails engine half. The column (with an index — you’re adding it because it will be queried):
The serializer — makes the attribute readable (hydrates the form, renders the table cell):
The permitted param — makes it writable (the form’s Save ships it in the same PATCH as core fields). The read and write names must match:
The Ransack allowlist — makes it filterable and sortable. Both the list’s sort param and every filter predicate go through Ransack, and Ransack refuses attributes that aren’t allowlisted:
Add a Rails validation while you’re here (validates :lead_time_days, numericality: { greater_than_or_equal_to: 0, allow_nil: true }) — it’s the authoritative validation, and the dashboard renders its 422 message inline on the field automatically.

2. Frontend — one declaration

The form widget binds to the host form — no <form> of its own, no save button:

3. Verify it

API first — the fastest way to confirm the backend pieces:
Then the UI: open a product — the Lead time card sits in the sidebar; edit it and the page’s Save button arms; save and reload. On the products list the column renders, the header sorts, and the filter panel offers the numeric operators. Filters and sort round-trip through the URL — a filtered view is shareable — and a CSV export of the filtered list respects your filter, because the export stores the same Ransack predicate hash.

How the pieces map

Frontend behaviorMade possible by
Form input hydrates + saves with the pageserializer attribute + permitted param + formFields/useHostForm
Table cell rendersserializer attribute + ColumnDef.render
Header sort workssortable: true + Ransack allowlist
Filter panel entry worksfilterable: true + filterType + Ransack allowlist
Inline validation messagethe Rails validation (422 → mapped onto the field)
Two ColumnDef extras worth knowing: ransackAttribute points the predicate somewhere other than the column key (the built-in sku column filters through master_sku), and displayable: false makes a filter-only field that never renders as a column.

Reference