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.
Architecture fit. This is a Nova field, not a standalone component — it is inert without an already-licensed Laravel Nova install, and the Nova seat cost, not the MIT package, is the real procurement question. Architecturally it inverts the usual data-modelling decision: an entire editable grid persists as one JSON blob on a single Eloquent attribute via an array cast. That fits admin-curated, semi-structured configuration (rate cards, tiered discounts, per-region tax tables, feature matrices) where a fixed relational schema would mean recurring migrations. It does not fit anything that needs joins, aggregates, foreign keys, per-cell permissions, or individual-row reporting. The two-part contract — columns() in the resource is the form definition, the JSON blob is the data — has no enforcement mechanism, so schema ownership lands squarely on the application team. That ownership needs a named person, not a tacit assumption.
Integration feasibility. Mechanically cheap: composer require, one nullable JSON migration, an array cast, a Table::make(...)->columns([...]) registration, a normalizing observer, and wildcard validation rules. The package source is small enough that reading vendor/outl1ne/nova-table-field/src and the Vue component is the documentation — a half-day spike, not a week. A bespoke CRUD grid (controller, form request, views, tests) collapses into a few resource lines, moving internal-tooling epics from days to hours.
Technical risk. Concentrated in four places. (1) Supply chain: 40 stars, zero dependents, single maintainer, and a Vue component that tracks Nova majors — every Nova upgrade is an unowned breakage risk until proven otherwise. (2) Schema drift: renaming a column key orphans existing data silently; the grid renders blank and users retype. (3) Payload hygiene: the component can emit blank or partially-filled rows and non-sequential keys, producing {"0": {...}} in the column. (4) Capability ceiling: supported cell types are exactly what the shipped component implements — a date picker or formatted currency input means editing vendor/ or forking. ->sortable() on a JSON attribute is a trap across MySQL/Postgres dialects.
Key questions to settle before adoption.
->canSee() / ->readonly() is cheap and should be configured, not deferred.Stack fit. Laravel + Nova with a recorded PHP/Nova version matrix. Both MySQL and Postgres work but diverge in semantics: MySQL rejects malformed JSON at write time, Postgres jsonb reorders keys. Never assert on raw JSON strings in tests — compare decoded arrays.
Migration path (the sequence that de-risks this).
Product, a tax table on a settings resource — not a hypothetical.'rate_card' => 'array'.php artisan tinker: confirm [['region' => 'eu', 'price' => 10], ...], not [{"0": ...}]. Fix it here or every downstream read, export, and migration inherits the defect.filter() empty rows, cast types, values() to reindex. This is where the blob becomes trustworthy and where derived invariants (sort, dedupe, reject overlapping bands) belong.Resource::rules(): rate_card.*.price etc., plus a max:200 guard rail — a mis-paste into an admin form can otherwise drop thousands of rows into one cell.->onlyOnForms()->hideFromIndex() on the field, with a purpose-built Text display for detail and index.RateCard/RateRow) as the single seam for non-Nova consumers, so a later relational migration changes one class.rate_card_version column, with the normalizer branching on it.Compatibility. Pin the exact version (no caret) for the first release; record Nova and PHP versions in the ADR; add a CI branch that bumps Nova to surface breakage before an unrelated upgrade does. Confirm cell types against the component source during the spike and price the fork if a required input type is missing.
Sequencing. Ship the read/write admin surface first with normalization, validation, and permissions. Treat DTO, schema versioning, and the exit plan as part of the same release, not a follow-up — they are cheap on day one and expensive to retrofit.
Maintenance. Single-maintainer dependency; assume you may carry a fork. Budget for it explicitly — MIT makes forking real, but only if someone has read the component. Keep the version pinned, the Nova matrix recorded, and the schema version column live so key renames become a match statement instead of archaeology.
Support. Expect tickets from two sources: users losing grid state on a 422 (test this path in a real browser — add rows, break a rule, submit), and silent blank cells after a column-key rename. Give the on-call dev a runbook: $model->getRawOriginal('rate_card') and logging inside the observer to see what Nova actually posted, rather than guessing.
Scaling. Right-sized for dozens to low hundreds of rows edited by a handful of trusted internal users. Enforce that with the max:200 rule. Beyond that, or the moment finance wants these values in the warehouse or BI, JSON-in-a-column stops being the answer — flattening/ETL or a relational migration is the next ticket, and the DTO plus schema version are what make it a command with a validation report rather than a rewrite. Field-level permissions are all-or-nothing at the grid level; there is no per-cell permission story.
Failure modes to register. Silent data orphaning on column rename; blank/partial rows becoming non-list JSON; Postgres key reordering breaking naive assertions; MySQL rejecting a payload on an apparently valid save; ->sortable() producing dialect-specific SQL nobody wants to own; a Nova major upgrade breaking the Vue component; and unauthorized edits to business rules because canSee/readonly were never set.
Ramp-up. Low for a Laravel developer — hours, assuming they read the vendor source first. Higher for the product side: the JSON-vs-normalized decision, the audit-trail requirement, the "Nova-only consumption" assumption with a named owner and review date, and the exit budget all need to be written down before adoption. Prototype use is legitimate and valuable — standing up a realistic admin surface for user testing before committing to a permanent schema is exactly the de-risking this field is good at.
How can I help you explore Laravel packages today?