outl1ne/nova-table-field
Nova Table Field adds an editable table field to Laravel Nova, letting you manage rows and columns of structured data directly inside resource forms. Configure columns, row limits, and formatting to store lightweight tabular data without extra models or migrations.
Product, per-region tax tables, tiered discount bands on Plan, seasonal surcharges, feature-comparison grids, per-client fee schedules. This field collapses a bespoke CRUD screen — controller, FormRequest, Blade views, feature tests — into a handful of lines in a Nova resource, moving internal-tool epics from days of estimate to hours and freeing capacity for customer-facing work.columns()) and data lives in JSON, adding a column to the grid is a config change, not a migration. That directly buys product optionality: you can iterate on the shape of the data with users for weeks before committing to a relational design.Adopt when most of the following hold:
Look elsewhere when:
orderByRaw with json_extract / -> operators that diverge between MySQL and Postgres. If sorting or filtering on these rows is a stated requirement, that is the signal to normalize — do not build a JSON-query layer you will throw away.vendor/ or forking; price that in during the spike.Adoption gate checklist. Before committing, the PM should be able to answer yes to: one named real grid exists to build first (not a hypothetical); a nullable json column plus array cast is acceptable as the system of record; a normalizer/observer and max:N row guard rails will be written (the field will happily accept a mis-paste of thousands of rows); a rate_card_version column or _v key will exist from day one; the field will be onlyOnForms()/hideFromIndex() with a purpose-built read view beside it; the version is pinned in composer.json and the Nova/PHP matrix is recorded in the ADR; and the JSON → relational exit plan (normalizer + DTO + schema version) is named even if it never runs.
For executives (one paragraph): We already pay for Laravel Nova to run our admin panel. This is a free, MIT-licensed field that adds a spreadsheet-style editor to it, so ops can maintain things like rate cards, tax tables, and discount tiers themselves instead of filing engineering tickets. Backlog items that would have been "build a small admin screen" — days of engineering — become config changes measured in hours. The trade-off is real and worth stating: the data lands in a JSON column rather than a normalized table, which is fine while it's small, admin-edited, and only viewed in Nova, and we will keep the exit path open so a later move to proper tables is a migration, not a rewrite. The main risk is dependency risk: it's a small, single-maintainer package, so we pin the version and can fork it if needed.
For engineering (the ask): Spike it in an afternoon. Read the small source in vendor/ before designing a schema around guessed API — the field class and its chainable methods are the documentation. Pick one real grid, add a nullable json column with an array cast, register the field, save one row, and confirm the shape in tinker ([['region' => 'eu', ...]], not [{"0": ...}]). Then harden it: normalize on save in a model observer (filter() + values() — never trust the browser payload, blank rows reach the server), validate with wildcards in Resource::rules() including an max:N row cap, seed defaults so the first edit isn't a blank grid, hide the raw blob outside forms with onlyOnForms()/hideFromIndex() and pair it with a Text field displayUsing a formatted read view, expose a DTO instead of the raw column, version the stored shape, and feature-test the resource endpoint round trip rather than the Vue widget. Field-level permissions (canSee, readonly) are cheap — use them to keep the grid away from users who shouldn't edit business rules. Pin the version without a caret for the first release, note Nova/PHP in the ADR, and add a CI branch that bumps Nova so breakage surfaces before an unrelated upgrade causes it. Budget the exit: the realistic regret case is finance wanting these rows in the warehouse, so keep the normalizer, DTO, and schema version in place from day one.
The one-line version: already have Nova? This turns a days-long admin CRUD build into an afternoon of config — as long as we treat the JSON as a deliberate, documented, exit-planned choice rather than an accident.
How can I help you explore Laravel packages today?