To build a chart from database data in Laravel 13, install Chart.js with npm, create a controller method that returns the data as JSON, fetch it in JavaScript and pass it to new Chart(). This keeps the query on the server and the drawing in the browser. This tutorial builds a monthly sales bar chart from an orders table.
Targets: Laravel 13 (PHP 8.3 to 8.5), Vite and the chart.js npm package.
Step 1: Install Chart.js
npm install chart.jsStep 2: Prepare the data
This example assumes an orders table with a total decimal column and the usual created_at timestamp. Create the model and migration if you do not have them:
php artisan make:model Order -mfSchema::create('orders', function (Blueprint $table) {
$table->id();
$table->decimal('total', 10, 2);
$table->timestamps();
});Add a few rows with a factory or Tinker so the chart has something to show. Use different created_at months.
Step 3: Return JSON from a controller
Group orders by month and sum the totals. The query below works on SQLite, MySQL and MariaDB because it uses a format function that each engine supports under its own name, so the example picks the right expression for the current connection:
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
class SalesChartController extends Controller
{
public function __invoke(): JsonResponse
{
$monthExpression = DB::connection()->getDriverName() === 'sqlite'
? "strftime('%Y-%m', created_at)"
: "DATE_FORMAT(created_at, '%Y-%m')";
$rows = Order::query()
->selectRaw("$monthExpression as month, SUM(total) as revenue")
->where('created_at', '>=', now()->subMonths(11)->startOfMonth())
->groupBy('month')
->orderBy('month')
->get();
return response()->json([
'labels' => $rows->pluck('month'),
'values' => $rows->pluck('revenue')->map(fn ($value) => (float) $value),
]);
}
}The expression strings are fixed in the code, so there is no SQL injection risk from user input. If you use PostgreSQL, use to_char(created_at, 'YYYY-MM') instead. The response has two arrays, labels for the x axis and values for the bars.
Step 4: Add the route and the page
use App\Http\Controllers\SalesChartController;
use Illuminate\Support\Facades\Route;
Route::get('/charts/sales', SalesChartController::class)->middleware('auth');
Route::view('/dashboard/sales', 'sales');Create resources/views/sales.blade.php with a canvas element:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Sales</title>
@vite(['resources/js/app.js'])
</head>
<body>
<div style="max-width: 800px; margin: 2rem auto;">
<canvas id="salesChart" data-url="{{ url('/charts/sales') }}"></canvas>
</div>
</body>
</html>Step 5: Draw the chart
Add this to resources/js/app.js:
import Chart from 'chart.js/auto';
const canvas = document.getElementById('salesChart');
if (canvas) {
fetch(canvas.dataset.url, { headers: { 'Accept': 'application/json' } })
.then((response) => response.json())
.then((data) => {
new Chart(canvas, {
type: 'bar',
data: {
labels: data.labels,
datasets: [{
label: 'Revenue',
data: data.values,
backgroundColor: 'rgba(13, 110, 253, 0.6)',
borderRadius: 4,
}],
},
options: {
responsive: true,
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true } },
},
});
});
}The import chart.js/auto registers all chart types and components automatically. It is the easiest choice. For a smaller bundle you can import only the pieces you use and register them yourself, as the Chart.js documentation explains.
How do I change the chart type?
Change type: use 'line' for trends, 'pie' or 'doughnut' for shares of a total, and 'radar' or 'polarArea' for other comparisons. A line chart usually needs tension: 0.3 in the dataset for a smooth curve and fill: false to avoid the filled area.
How do I refresh the chart without reloading the page?
Keep the chart in a variable and update it when new data arrives:
chart.data.labels = data.labels;
chart.data.datasets[0].data = data.values;
chart.update();Call that code from a timer or a button click that fetches the endpoint again.
How do I check that it works?
- Run
npm run devandphp artisan serve. - Open
/charts/salesin the browser (while logged in) and confirm you get JSON withlabelsandvalues. - Open
/dashboard/salesand confirm the bars appear.
Common mistakes
- Chart is not defined: you did not import it in a script that Vite builds.
- Empty chart: the JSON endpoint returned an error. Check the network tab.
- Database-specific date functions:
DATE_FORMATdoes not exist in SQLite and PostgreSQL. Pick the right function per database. - Canvas sized by CSS only: put the canvas in a container with a width, and keep
responsive: true. - Returning decimals as strings: cast revenue to float so Chart.js handles the values as numbers.
FAQ
Can I use the CDN instead of npm?
Yes, with a script tag, but npm with Vite gives you version control and bundling.
Do I need a Laravel package for charts?
No. Chart.js works directly with a JSON endpoint. Packages mainly wrap the same pattern.
How do I show several datasets?
Add more objects to the datasets array, each with its own label, data and color.



