fx3costa/laravelchartjs
Laravel ChartJS is a Laravel wrapper for Chart.js that lets you create interactive, responsive charts directly in PHP. It offers a simple API, Blade-friendly rendering, multiple chart types, and easy configuration for dashboards, reports, and data visualizations.
Install and wire it up
composer require fx3costa/laravelchartjs
On Laravel 5.5+ the ChartjsServiceProvider is auto-discovered. On older apps (or if discovery is disabled) register manually in config/app.php:
'providers' => [
Fx3costa\LaravelChartjs\Providers\ChartjsServiceProvider::class,
],
'aliases' => [
'Chartjs' => Fx3costa\LaravelChartjs\Facades\Chartjs::class,
],
Publish the config so you can see which Chart.js build is injected and how:
php artisan vendor:publish
First use case — one line chart in a dashboard
Build the chart in the controller (or a dedicated chart class), not in Blade:
use Chartjs;
public function index()
{
$chart = Chartjs::name('signups')
->type('line')
->labels(['Jan', 'Feb', 'Mar', 'Apr'])
->datasets([
[
'label' => 'Signups',
'data' => [12, 19, 8, 24],
'backgroundColor' => 'rgba(54, 162, 235, 0.2)',
'borderColor' => 'rgba(54, 162, 235, 1)',
'borderWidth' => 2,
'fill' => false,
],
])
->options([
'responsive' => true,
'maintainAspectRatio' => false,
'legend' => ['display' => false],
]);
return view('dashboard', compact('chart'));
}
<div style="height: 320px;">
{!! $chart->render() !!}
</div>
render() emits the <canvas> plus the inline new Chart(...) bootstrap. Note the unescaped Blade echo — it is markup, not a string.
Where to look first
vendor/fx3costa/laravelchartjs/src/ChartBuilder.php — the fluent API is small; read it end to end (10 minutes) and you know the whole package.Keep chart construction out of views. The fluent builder returns a mutable object; treat it like a DTO. Two clean options:
App\Charts\RevenueChart with a build() method) once you pass ~3 charts. This makes them unit-testable and keeps fat controllers thin.class RevenueChart
{
public function build(array $rows)
{
return Chartjs::name('revenue')
->type('bar')
->labels(array_column($rows, 'month'))
->datasets([[
'label' => 'Revenue',
'data' => array_column($rows, 'total'),
'backgroundColor' => '#4e73df',
]])
->options(['scales' => ['yAxes' => [['ticks' => ['beginAtZero' => true]]]]]);
}
}
Multiple charts on one page. Each call to Chartjs::name() must use a unique name — the name becomes the canvas id and the JS lookup key. Loop over a collection of pre-built charts in the view:
@foreach ($charts as $chart)
<div class="card"><div style="height:300px">{!! $chart->render() !!}</div></div>
@endforeach
Multi-dataset and mixed charts. datasets() accepts any array of arrays; Chart.js v2 supports per-dataset type so you can overlay a line on bars:
->datasets([
['type' => 'bar', 'label' => 'Orders', 'data' => [10, 20, 30]],
['type' => 'line', 'label' => 'Forecast', 'data' => [12, 18, 33], 'fill' => false],
])
Aggregating data. Do the grouping in SQL, not PHP:
$rows = Order::selectRaw('DATE_FORMAT(created_at, "%Y-%m") as month, SUM(total) as total')
->groupBy('month')->orderBy('month')->get();
AJAX / filter-driven charts. The package renders server-side once; it does not react to filters. Two viable patterns:
['labels' => ..., 'datasets' => ...] from an API route, then call chart.data = payload; chart.update(); in JS, keeping a reference to the instance you created. Use the package for the initial paint, plain Chart.js for updates.Layout. Always give the canvas a bounded parent with an explicit height and set maintainAspectRatio => false; otherwise the chart grows unbounded on resize and inside flex/grid children.
Chart.js v2 only. This wraps Chart.js 2.x. If your app already loads Chart.js 3/4 via npm or another CDN, you get two global Chart definitions and one of them wins silently — charts render blank or with v3-incompatible options ignored. Audit window.Chart in the console. v2 is EOL; for new work, consider Chart.js 4 directly or a maintained wrapper.
The JS comes from a CDN. Rendering assumes network access at page load, which breaks offline/intranet deployments and needs the CDN host in your CSP script-src. There is no SRI hash. For production, download the pinned Chart.js build, host it yourself, and adjust the config so the package points at your asset path — or drop the package's script injection and include Chart.js in your own bundle.
Duplicate canvas IDs. Reusing the same name — e.g. two Chartjs::name('chart') calls in a loop without varying the name — produces duplicate id attributes. The browser binds the script to the first element only, so the second container stays blank while the first chart quietly redraws. Generate names like "revenue-{$tenant->id}" and Str::slug() them.
Collections and value types. ->labels($collection) generally works because Collection is JsonSerializable, but pluck() on an Eloquent collection can surface model attributes or unexpected keys — call ->values()->all() to guarantee a plain list. Database decimals and SUM() results often arrive as strings ("1234.50"), which Chart.js v2 will happily plot as category labels rather than numbers. Cast with (float) or ->map(fn ($v) => (float) $v).
Pass options as arrays, never JSON strings. Passing json_encode([...]) gives you a quoted string inside the generated options object and the chart ignores everything. Same for nested keys — Chart.js v2 uses plural scales, xAxes/yAxes (v3+ renamed these to x/y), and legend not plugins.legend.
Escaping. {!! $chart->render() !!} is required, and json_encode escapes / by default so a </script> in a label will not break out — but do not rely on that for arbitrary user input. Sanitize label text; anything echoed raw into a <script> context is a risk you should consciously accept.
Payload size. Every chart embeds its full dataset as inline JSON. A few thousand points will bloat the HTML and delay first paint. For large series, downsample server-side, paginate the time window, or move to the AJAX pattern.
Modern Laravel compatibility. Check the composer.json constraint before promising this to a team. The package targets Laravel 5.x; on Laravel 9/10/11 Composer may refuse to install and you'll be choosing between --ignore-platform-reqs (works, but unverified) and a fork. Given 0 dependents and a 2023 last release, budget for the possibility of maintaining a fork — it is a ~300-line wrapper, so forking is cheap.
Debugging checklist. View source and confirm: (a) exactly one <script src="...chart.js"> tag, (b) the canvas id matches the getElementById in the inline script, (c) the JSON blob parses (paste it into the console), (d) typeof Chart !== 'undefined' before your inline script runs. Console errors of Chart is not defined almost always mean the CDN was blocked.
Testing. Assert on the canvas id and the serialized data rather than the markup:
$response->assertSee('id="signups"', false);
$response->assertSee('"data":[12,19,8,24]', false);
Extension points. Publish the config/views to change the injected script tag or wrapper markup; wrap the facade behind your own ChartFactory so you can swap implementations later without touching every controller.
How can I help you explore Laravel packages today?