How to Build Dynamic, Filterable Charts with ApexCharts in Laravel 11

             In this comprehensive Laravel 11 tutorial, you'll learn how to create dynamic charts with filter options using ApexCharts — a powerful, modern JavaScript charting library known for its beautiful visuals, interactivity, and responsiveness. We'll build a real-world example: an interactive monthly user registration chart that updates in real-time based on a selected year via a simple dropdown filter and AJAX requests — no page reloads required.

Why is this valuable? Static charts limit insights, especially in admin dashboards, analytics tools, or reporting systems. By adding dynamic filters, you enable users to drill down into data effortlessly (e.g., compare user growth year-over-year), improving decision-making and user experience. This approach combines Laravel's robust backend (Eloquent queries with groupBy and whereYear), secure AJAX handling (CSRF protection), and ApexCharts' flexible frontend rendering — resulting in professional, production-ready data visualization without heavy packages.

Whether you're building SaaS analytics, e-commerce reports, or internal tools, mastering dynamic ApexCharts in Laravel 11 unlocks scalable, engaging dashboards. By the end, you'll have a fully working demo ready to extend with more filters, chart types (bar, line, area), or even Livewire integration for even smoother reactivity.



How to Create Dynamic Charts with Filter Options in ApexCharts Using Laravel 11

Table Of Content

1 Prerequisites

  • PHP 8.2 or higher
  • Composer installed
  • A database like MySQL or MariaDB
  • Basic knowledge of Laravel, PHP, and JavaScript

We'll use Bootstrap for basic styling and jQuery for AJAX, but you can swap them with Tailwind or vanilla JS if preferred.

2 Introduction

In this step-by-step Laravel 11 tutorial, you’ll learn how to seamlessly integrate ApexCharts with dynamic filter options to create powerful, interactive data visualizations. We’ll guide you through generating dummy user records, then displaying a real-time updating line chart (or bar chart — your choice) that shows monthly user registrations for the selected year. Using a clean dropdown filter and AJAX, the chart refreshes instantly without reloading the page — perfect for modern admin dashboards, analytics panels, or reporting features.

By the end of this guide, you’ll have a fully functional, production-ready example of dynamic charts in Laravel 11 with ApexCharts, complete with proper Eloquent queries, secure AJAX handling, and responsive design. Whether you're building SaaS analytics, internal tools, or client-facing reports, this technique will help you deliver professional-grade data visualization with minimal effort.

3 Install a Fresh Laravel 11 Project

3.1 Install Laravel Project

Ensure Composer is installed and use the command:

composer create-project laravel/laravel laravel-apexcharts-demo

Navigate to your project directory:

cd laravel-apexcharts-demo

This sets up Laravel 11 with all necessary dependencies.

3.2 Configure MySql Database

Open the .env file to include database credentials:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=charts
DB_USERNAME=root
DB_PASSWORD=

4 Create Migration

Generate a migration for the users table:

    php artisan make:migration create_users_table

Edit the migration file to define the table structure, then run:
    
<?php
se Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });

        Schema::create('password_reset_tokens', function (Blueprint $table) {
            $table->string('email')->primary();
            $table->string('token');
            $table->timestamp('created_at')->nullable();
        });

        Schema::create('sessions', function (Blueprint $table) {
            $table->string('id')->primary();
            $table->foreignId('user_id')->nullable()->index();
            $table->string('ip_address', 45)->nullable();
            $table->text('user_agent')->nullable();
            $table->longText('payload');
            $table->integer('last_activity')->index();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('users');
        Schema::dropIfExists('password_reset_tokens');
        Schema::dropIfExists('sessions');
    }
};


Run the migration:

php artisan migrate

5 Create Model

Define the User model in app/Models/User.php. Use this model to work with the users table.
    
<?php
namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    /** @use HasFactory<\Database\Factories\UserFactory> */
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var list
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var list
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}


6 Seed Dummy User Data for Testing

To simulate real data, we'll use Laravel's factory to create 200 users with random creation dates over the past 2 years. Update the UserFactory (located in database/factories/UserFactory.php):

php artisan make:factory UserFactory

Update the factory to generate sample data and seed the database:
    
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
 */
class UserFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => Hash::make('password'),
            'remember_token' => Str::random(10),
            'created_at' => fake()->dateTimeBetween('-2 years', 'now'),
            'updated_at' => now(),
        ];
    }

    /**
     * Indicate that the model's email address should be unverified.
     */
    public function unverified(): static
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}

To generate 200 dummy records, use:

php artisan tinker
>>> \App\Models\User::factory()->count(200)->create();
>>> exit

This populates your users table with testable data.

7 Create the Controller for Chart Data

Create a ChartController for chart data handling:

php artisan make:controller ChartController

In the controller, implement index and filterData methods to populate the charts dynamically. This allows you to explore how to create dynamic charts with filter options in ApexCharts using Laravel 11.

<?php
namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class ChartController extends Controller
{
    public function index()
    {
        $years = $this->getAvailableYears();
        $defaultYear = now()->year;
        $chartData = $this->getChartData($defaultYear);

        return view('charts.index', compact('years', 'defaultYear', 'chartData'));
    }

    public function filter(Request $request)
    {
        $year = $request->input('year', now()->year);
        $chartData = $this->getChartData($year);

        return response()->json(['chartData' => $chartData]);
    }

    private function getChartData($year)
    {
        $data = User::select(
                DB::raw('MONTH(created_at) as month'),
                DB::raw('COUNT(*) as count')
            )
            ->whereYear('created_at', $year)
            ->groupBy('month')
            ->orderBy('month')
            ->pluck('count', 'month')
            ->toArray();

        // Fill missing months with 0
        $series = [];
        for ($month = 1; $month <= 12; $month++) {
            $series[] = $data[$month] ?? 0;
        }

        return $series;
    }

    private function getAvailableYears()
    {
        // Dynamically get years from data
        return User::selectRaw('YEAR(created_at) as year')
            ->distinct()
            ->orderBy('year', 'desc')
            ->pluck('year')
            ->toArray();
    }
}
?>

This controller fetches monthly user counts for a given year and provides dynamic years based on your data.

8 Create the Blade View with ApexCharts and AJAX Filter

Create resources/views/charts/index.blade.php to render the chart. Use ApexCharts for visualizing user data interactively.
    
  <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic Charts with ApexCharts in Laravel 11</title>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- ApexCharts -->
    <script src="https://cdn.jsdelivr.net/npm/apexcharts@latest"></script>
</head>
<body>
    <div class="container mt-5">
        <h1 class="mb-4">Monthly User Registrations Chart</h1>
        
        <div class="card">
            <div class="card-header">
                <form id="filterForm" class="d-flex align-items-center">
                    <label for="year" class="me-2">Select Year:</label>
                    <select name="year" id="year" class="form-select w-auto me-2">
                        @foreach ($years as $y)
                            <option value="{{ $y }}" {{ $y == $defaultYear ? 'selected' : '' }}>{{ $y }}</option>
                        @endforeach
                    </select>
                    <button type="submit" class="btn btn-primary">Filter</button>
                </form>
            </div>
            <div class="card-body">
                <div id="chart"></div>
            </div>
        </div>
    </div>

    <!-- jQuery -->
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>

    <script>
        var chartData = @json($chartData);
        var options = {
            series: [{
                name: 'Users',
                data: chartData
            }],
            chart: {
                height: 350,
                type: 'bar',
                toolbar: { show: true }
            },
            plotOptions: {
                bar: {
                    horizontal: false,
                    columnWidth: '55%',
                    endingShape: 'rounded'
                },
            },
            dataLabels: { enabled: false },
            stroke: { show: true, width: 2, colors: ['transparent'] },
            xaxis: {
                categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            },
            yaxis: { title: { text: 'Number of Users' } },
            fill: { opacity: 1 },
            tooltip: { y: { formatter: val => val + " users" } },
            colors: ['#008FFB']
        };

        var chart = new ApexCharts(document.querySelector("#chart"), options);
        chart.render();

        // AJAX Filter
        $('#filterForm').on('submit', function(e) {
            e.preventDefault();
            var year = $('#year').val();

            $.ajax({
                url: '{{ route('charts.filter') }}',
                type: 'POST',
                data: { year: year, _token: '{{ csrf_token() }}' },
                success: function(response) {
                    chart.updateSeries([{ data: response.chartData }]);
                },
                error: function(xhr) {
                    console.error('Error:', xhr.responseText);
                }
            });
        });
    </script>
</body>
</html>

    

9 Define Routes

Add routes in routes/web.php for loading and filtering chart data.

use App\Http\Controllers\ChartController;
use Illuminate\Support\Facades\Route;

Route::get('/charts', [ChartController::class, 'index'])->name('charts.index');
Route::post('/charts/filter', [ChartController::class, 'filter'])->name('charts.filter');

10 Folder Structure

11 Run Laravel Server to Test the App

Start the Laravel server:

php artisan serve

Visit http://127.0.0.1:8000/charts in your browser. Select a year from the dropdown and click "Filter" – the chart should update dynamically with monthly user counts.

Troubleshooting Tips

  • No data? Ensure you seeded users and check your database.
  • AJAX errors? Verify CSRF token and console for JS errors.
  • Chart not rendering? Check CDN links and browser console.
  • For production, add error handling in the controller (e.g., validate year input) and consider caching queries.

12 Conclusion

You've now built dynamic, filterable charts in Laravel 11 with ApexCharts — ready for production dashboards. Experiment with different chart types, add date range pickers, or combine with Livewire for even smoother UX.
Revathi M - PHP and CodeIgniter Developer

Written by Revathi M

PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel

Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.

Frequently Asked Questions

ApexCharts is a modern JavaScript charting library for creating interactive, responsive visualizations like bar, line, and area charts. In Laravel, it enables dynamic data dashboards without page reloads, using AJAX for real-time updates.

Include ApexCharts via CDN in your Blade view. Set up a controller to fetch data, render the chart with initial options, and use JavaScript to initialize the chart object.

Create a dropdown for filters (e.g., year selection). On change, use jQuery AJAX to POST the filter value to a Laravel route, fetch filtered data from the database, and update the chart dynamically with chart.updateSeries().

Use the ApexCharts exec() method or updateSeries() with new data returned from an AJAX call. This allows real-time chart refreshes based on user filters.

No, you can integrate directly via CDN and manual JavaScript. Packages like Larapex Charts or akaunting/laravel-apexcharts simplify PHP-based configuration but are optional for basic setups.

The tutorial demonstrates a bar chart (configured as 'bar' type) showing monthly user registrations, though it can be easily changed to line or area charts.

Include the CSRF token in your AJAX data: '_token': '{{ csrf_token() }}'. This ensures secure POST requests to filter routes.

Yes, query Eloquent models (e.g., User::whereYear()->groupBy(month)) to aggregate data like monthly counts, then format and return as JSON for the chart.