kirschbaum-development/nova-chartjs
Add interactive Chart.js charts to Laravel Nova dashboards with ready-made, configurable cards. Display responsive line, bar, pie, and other visualizations using simple PHP options alongside your Nova metrics.
Architecture fit. Nova Chart.js is a thin adapter, not a platform: a single Nova Field class on the PHP side and a Vue component that hands a config array to Chart.js. The package ships its own compiled dist/ bundle, registers no service provider, publishes no config, and runs no migrations. That means it inserts into an existing Nova application with essentially zero architectural surface — no queue workers, no new datastore, no auth boundary, no background jobs. For a TPM, the important consequence is that the integration risk is concentrated in exactly one place (Nova's field-component contract) rather than spread across the stack. Everything the package actually does — aggregating a series server-side and serializing it into the Nova payload — is logic you would otherwise write yourself, and it is logic you end up owning regardless.
Integration feasibility. High, conditional on a single gate. Release 2.0.0 (March 2023) is the Nova 4 release and nothing has shipped since, so Nova 5 is unverified and there is no maintainer signal to suggest it is coming. The go/no-go is composer why-not kirschbaum-development/nova-chartjs run against the pinned Nova major — an hour of work, not a spike. If it passes, a working record-scoped chart on a Resource detail view is a half-day proof. If it fails, the realistic fallback is a fork, not a workaround, and that should be scoped and approved before commitment rather than discovered mid-sprint.
Technical risk. Contained but real, and unusually concentrated in silent failure. The signature failure mode is a blank canvas with no PHP exception — caused by Nova component API drift, blocked assets under CSP, or a closure that threw and returned null. None of those surface as an error to the user; they surface as "the chart is missing" in a demo. Secondary risks: the bundled Chart.js major in dist/ is invisible to Dependabot and CVE scanning, and the README's "dashboard cards and resource metrics" language overstates what the implementation does — this is a detail-view field, and Nova's native Value/Trend/Partition metrics already own the dashboard surface. That copy discrepancy is the single most likely source of a mis-scoped ask.
Key questions to resolve before approval.
composer why-not pass today? (Hard gate.)Stack fit. Requires Laravel + Nova (paid license, already sunk if we run Nova — disqualifying if we don't) and PHP. Nothing else. It does not require Node, a build step, or a front-end toolchain on our side, which is the actual value proposition: it replaces roughly 200–400 lines of bespoke Vue plus asset registration with composer require and a closure. That saving is bounded — hours to a day, not a sprint — but it is real for a team without front-end capacity, and it is the cheapest credible way to test whether internal reporting demand is durable before funding a BI procurement cycle (Metabase, Looker, Power BI).
Migration path. Asymmetric, and this is the crux of the commercial argument. Chart.js datasets/options arrays are portable nearly verbatim to Filament, Blade, or a BI tool. The NovaChartjs::make() fluent chain is not. The migration strategy is therefore architectural discipline on day one: push as much logic as possible into (a) pure series-builder methods that are unit-testable without Nova and (b) plain config arrays, and keep the fluent chain as thin as possible. Do that and a future move is a two-day edit in one factory file; skip it and the same move is a two-week sweep across every resource. Concretely: one TrendChart::make() wrapper, one ChartSeries service class, one palette constant — and every resource calls the wrapper, never the package directly.
Compatibility. Two axes. First, Nova major — a hard pin, verifiable in an hour, and the only thing that can block us outright. Second, Chart.js version — ours if we import it in-app (don't), theirs if we don't (the bundled dist/ major, which we should identify by reading the artifact). Also verify serialization hygiene: Eloquent collections in the payload cause bloat or errors, MySQL SUM/COUNT come back as strings and need explicit int/float casts, and missing time buckets must be backfilled or Chart.js draws a trend line that is wrong in a way nobody notices until someone makes a decision on it.
Sequencing.
composer why-not against pinned Nova. Stop here if it conflicts.ChartSeries with bucket backfill, timezone handling, casting, empty-range behavior; unit tests for all four. This is the durable asset.TrendChart factory + palette constant. Migrate the canary to it.Maintenance. Budget 2–5 engineer-days per year, concentrated around Nova upgrades. Treat the package as vendored, not installed: we own the code in practice, the source is readable in an afternoon, and a fork is a realistic option rather than an escalation. The annual cost is not the license (MIT, free) and not the runtime (none added) — it is the calendar reminder before each Nova major plus the two-minute canary click afterward. That canary resource is the entire regression suite for this dependency, and it should be documented as such so it survives staff turnover.
Support. Assume we are the only user. 51 stars, zero dependents, last release March 2023 — nobody is exercising our edge cases and there is no one to file a bug report with. Internal-izing that expectation matters: the audience must be internal (ops, support, CS, leadership) where a stale dependency is an annoyance rather than an outage. Do not put this in front of customers. Do not let a chart become load-bearing for a contractual SLA or a revenue-facing report.
Scaling. Scales fine along the axes that matter and poorly along the ones that don't. Each chart field on a detail view is one aggregate query per page load — acceptable at low concurrency for an internal tool, unacceptable as a pattern if someone stacks six charts on a page hitting a wide table. Mitigations are cheap and should be policy, not improvisation: ->onlyOnDetail() by default (index views render per row — that is an N+1 you built on purpose), one query per chart budgeted, and Cache::remember with a 15-minute TTL on anything expensive. Point count is the other ceiling: Chart.js is comfortable to ~1–2k points with no downsampling, and the package offers none — a year of daily rows is fine, five years of hourly rows is not. Downsample in SQL before the closure sees it. Visibility gating via ->canSee(fn ($request) => $request->user()->can('viewAnalytics', $this)) is genuinely load-bearing, not cosmetic: the closure is evaluated server-side, so a hidden field means the query never runs. Use policies for anything that leaks revenue or customer behavior.
Failure modes. Rank-ordered by likelihood and cost:
| Failure | Symptom | Detection | Response |
|---|---|---|---|
| Nova major drift | Blank canvas, no PHP error | Canary click post-upgrade | Fork, or replace field definitions (configs survive) |
| CSP blocks assets | Blank canvas, no error | Network tab on first render | Fix CSP or reject the package |
| Closure throws / returns null | Blank canvas | dd() in the closure |
Harden series method; add empty-state |
| Bundled Chart.js CVE | No symptom until it matters | Manual tracking (Dependabot is blind to dist/) |
Swap the dist asset in a fork |
| Missing time buckets | Misleading trend line, plausible-looking | Unit test on backfill | Backfill in ChartSeries |
| String-typed aggregates | Broken axis scales | Unit test on casting | Explicit int/float casts |
| Chart instance leak on re-render | Memory growth over long sessions | Read the Vue component for destroy() |
Note it; avoid hot re-mounts |
| Pie chart of a time series | Unreadable legend, wrong message | Design review | Horizontal bar instead |
The blank canvas is the one to rehearse, because it is silent and it will happen in front of a stakeholder. Debug order is fixed: network tab for the package JS → Nova version → dd() inside the closure.
Ramp-up. Low and front-loaded. The fluent API is small (make(), chart type, data, options) — the real learning surface is Chart.js option syntax, not this package. A mid-level Laravel engineer is productive in a day; the reusable assets (ChartSeries, TrendChart, palette constant) mean every chart after the first is closer to an hour. The genuine onboarding risk is not skill, it is expectation: someone will read "dashboards and cards" and ask for a dashboard, a date filter, or an export. Kill that in the kickoff by stating the boundary explicitly — this field owns the record, native Nova metrics own the dashboard, and a date picker on the chart is a different product decision, not a field option. Setting that line before code is written is the highest-leverage thing a TPM does on this package; it is the difference between a contained 2–5 day integration and an internal admin tool that quietly grows into an unscoped analytics product.
How can I help you explore Laravel packages today?