Skip to main content
Every list page in the dashboard renders through <ResourceTable>, which reads its column definitions from a table registry keyed by table name (products, orders, customers, …). That gives you two levers:
  1. Extend a built-in table — add, patch, or remove columns (including their filters) without forking the page.
  2. Define your own table — declare columns once with defineTable, then render them with <ResourceTable> on your own page.

Add a column to a built-in table

Imperatively, from src/plugins.ts (or a plugin entry):
Or declaratively through defineDashboardPlugin — same effect, grouped with your other extensions:
New columns append after the existing ones. Column keys must be unique per table — adding a duplicate throws with a message telling you to use updateColumn instead.
Timing is taken care of. Built-in tables register when their page first loads, which may be after your code runs. Mutations against a table that isn’t registered yet are queued and replayed the moment it appears — and mutations against a table that never registers simply never fire. That means extending an optional feature’s table is safe even when that feature isn’t installed.

Modify or remove built-in columns

Declaratively:
updateColumn patches shallowly — pass only the fields you want to change. removeColumn of an unknown key is a no-op.

Column definition

Filter types

When a column is filterable, filterType decides which filter UI it gets:
filterTypeFilter UIExtra fields
'string' (default)Text input with contains/equals operators
'boolean'Yes/no toggle
'number', 'currency'Numeric comparisons
'date'Date range picker (store-timezone aware)
'enum'Fixed option listfilterOptions: [{ value, label }] (required)
'resource'Multi-select autocomplete against another resourcefilterResource (required) — queryKey, search, hydrate, getOptionLabel
'tags'Tag autocompletetaggableType (required), e.g. 'Spree::Product'
A 'resource' filter searches as the admin types and resolves already-selected IDs back to labels on page reload:
Filtering and sorting are executed by the Admin API (Ransack), not in the browser — key (or ransackAttribute) must be a filterable/sortable attribute the API allows.

Define your own table

For your own list page, declare the table once — typically right next to your defineDashboardPlugin call:
Then render it from your page component:
<ResourceTable> handles pagination, sorting, the filter panel, column visibility, and URL round-tripping of all of it. See Routes for the page around it — the example Brands plugin shows the complete wiring.

Bulk actions, row actions, reordering

These are props on <ResourceTable>, composed by the page that renders the table:
Use them freely on your own pages. They are not registry-driven — a plugin can’t inject bulk or row actions into a built-in page’s table today. If a built-in page needs an injection point, that’s what slots are for.

Reference