gigerit/larapex-charts
gigerit/larapex-charts is a Laravel wrapper for ApexCharts that lets you build interactive, responsive charts in PHP and Blade. Define datasets, labels, and chart types with a fluent API, then render the container and script directly in your views—no hand-written JavaScript required.
gigerit/larapex-charts is a thin Laravel wrapper around ApexCharts.js: you build a chart object in PHP, it emits a <div> container plus a JS initialiser. Nothing is rendered server-side, so all interactivity/tooltips stay client-side.
composer require gigerit/larapex-charts
php artisan vendor:publish --tag=larapex-charts-config
config/larapex-charts.php holds the ApexCharts CDN URL, default colors, and font family. Check the CDN entry first if charts render blank.<script src="{{ config('larapex-charts.cdn') }}"></script>
$chart = (new LarapexChart)
->setType('line')
->setTitle('Revenue')
->setXAxis($months) // flat array of labels
->setDataset([['name' => 'Revenue', 'data' => $totals]]);
{!! $chart->container() !!}
@push('scripts') {!! $chart->script() !!} @endpush
First use case to build: a dashboard card where a single Eloquent aggregate (->selectRaw('SUM(...) as total')->groupBy('month')) feeds setXAxis()/setDataset(). If that works end-to-end, everything else is option plumbing.
Dedicated chart classes. Don't build charts inline in controllers. Create app/Charts/*.php with a build(): LarapexChart method (or a Chartable interface) so a chart can be reused across a dashboard, an export, and a JSON API without duplication. Inject them via the container.
Facade factories for boilerplate. LarapexChart::lineChart(), areaChart(), barChart(), pieChart(), donutChart(), radialChart(), polarAreaChart(), heatMapChart(), radarChart() pre-set the type. Use them and then chain options, rather than repeating setType() everywhere.
Pie/donut differ from cartesian. For pie/donut/radial/polarArea you set a flat setDataset([...]) of values and a matching setLabels([...]); ['name' => ..., 'data' => ...] series arrays are for line/area/bar/radar. Getting this backwards produces an empty or one-slice chart with no error.
Blade component wrapper. Wrap container() + script() in an anonymous component (<x-chart :chart="$chart" />) that pushes to a scripts stack. This keeps layouts clean and guarantees ordering in one place:
@props(['chart'])
<div {{ $attributes }}>{!! $chart->container() !!}</div>
@once @push('scripts') <script src="{{ config('larapex-charts.cdn') }}"></script> @endonce
@push('scripts') {!! $chart->script() !!} @endpush
SPA / Inertia / Livewire / API. Skip container()/script() entirely and serialise the options: $chart->toJson() (or toVue()), pass it as a prop, and instantiate new ApexCharts(el, options) yourself. This is the pattern to use whenever the front-end owns the lifecycle (Vue/React/Alpine) or the chart needs to be re-rendered on data refresh.
Polling / refresh. The package has no update API. Either build a fresh chart per refresh and re-mount client-side, or drop to plain ApexCharts updateSeries()/updateOptions() in your own JS, seeded from the chart's JSON.
Caching. Cache the data (Cache::remember('chart.revenue', ...) returning arrays) rather than the HTML. The container carries a generated id and the script embeds it; caching markup across requests leaks stale ids and stale data.
Testing. Assert on the serialised options rather than markup:
$options = json_decode($chart->toJson(), true);
$this->assertSame(['Jan', 'Feb'], $options['xaxis']['categories'] ?? $options['labels']);
Feature-test the controller for a 200 and a non-empty chart variable; snapshot the JSON only if you accept churn on upgrades.
Integration tips. Feed chart data from query objects/resources that already return int/float — cast at the database or resource layer. When a chart lives beside filters, make the chart class accept a DTO (date range, segment) so Livewire/controller code stays a one-liner: app(RevenueChart::class)->build($filters).
container() and script() return HTML strings — {!! !!}, not {{ }}. Using escaped output shows markup as text and is the #1 "it doesn't work" report.script() runs, and the container <div> must exist in the DOM. Push into a @stack('scripts') at the end of <body>; don't emit the script in <head>.wire:ignore and re-dispatch a browser event to rebuild the chart. The same applies to Turbo/Turbolinks navigation without data-turbo-permanent.container() twice, or rendering the same chart instance in two places (e.g. modal + page) collides. Use one instance per location.(int)/(float). JSON encoding of INF/NAN from bad divisions silently breaks the options object.setXAxis() is labels, not axis config. It's a flat array of category labels. For datetime axes, log axes, min/max, tick formatting etc. you're beyond the wrapper's API — supply extra options yourself in the JSON path, or extend the chart class (see below).json_decode($chart->toJson(), true) printed or Log::info(...) it, then paste it into the ApexCharts docs demo. All rendering problems are options problems; the package itself is nearly stateless.composer update.colors in config are defaults, and per-chart setColors() wins. Changing the CDN to a self-hosted path is the fix for offline/air-gapped deploys or strict CSP setups (you'll also need script-src allowances — ApexCharts uses inline styles).How can I help you explore Laravel packages today?