matheusmarnt/livecharts
LiveCharts is a reactive chart abstraction for Laravel using a pure PHP fluent API. Build 18 chart types and render via a single Livewire component. Supports ApexCharts and Chart.js with pluggable engines, enabling easy updates without JS boilerplate.
^8.2^10.0 || ^11.0 || ^12.0 || ^13.0^3.0 || ^4.0Laravel 10 caveat: the package's
composer.jsondeclares^10.0for runtime compatibility, but the test matrix only exercises Laravel 11/12/13 becausepestphp/pest-plugin-laravel ^3.0requires Laravel 11+. Production use on Laravel 10 is supported but not CI-validated.
composer require matheusmarnt/livecharts
The service provider is auto-registered via Laravel's package discovery. The LiveCharts facade is aliased automatically.
php artisan livecharts:install
The installer:
config/livecharts.phplivecharts.js, apexcharts.js, chartjs.js, plus Chart.js plugins for treemap, matrix, sankey, financial, luxon, adapter-luxon) to public/vendor/livecharts/jsstubs/livecharts (used by make:chart)⚠️ Assets must be published for
localandbothmodesThe default asset mode is
both(local first, CDN fallback). The files inpublic/vendor/livecharts/js/must exist for this to work. If you skiplivecharts:install, assets are missing after a fresh clone, or a deployment wipespublic/vendor/, every chart will fail silently withApexCharts is not definedorChart is not defined.Re-publish assets on demand:
php artisan vendor:publish --tag=livecharts-assets --forceVerify the output:
ls public/vendor/livecharts/js/ # Expected: apexcharts.js chartjs.js livecharts.js (+ plugin bundles)If you prefer no local files, set
LIVECHARTS_ASSETS_MODE=cdnin.env— no publish step needed.
Environment-specific invocations
Environment Artisan invocation php artisan serve(host)php artisan <command>Laravel Sail ./vendor/bin/sail artisan <command>Docker Compose (non-Sail) docker compose exec app php artisan <command>
LiveCharts ships in both mode by default (since v2.2.0): the locally-published engine bundles are served first, with the matching jsDelivr CDN URL wired as the <script onerror> fallback.
Since v2.7.7, LiveCharts uses the navigate strategy by default. Charts emit their engine scripts via Livewire's [@assets](https://github.com/assets) block — no [@liveChartsScripts](https://github.com/liveChartsScripts) directive required. This strategy works correctly with wire:navigate SPA navigation.
<!DOCTYPE html>
<html>
<head>
[@livewireStyles](https://github.com/livewireStyles)
</head>
<body>
{{ $slot }}
[@livewireScripts](https://github.com/livewireScripts)
</body>
</html>
If you need to use the legacy stack strategy (e.g. for non-Livewire contexts or manual placement), set LIVECHARTS_ASSETS_STRATEGY=stack and add [@liveChartsScripts](https://github.com/liveChartsScripts) before [@livewireScripts](https://github.com/livewireScripts):
LIVECHARTS_ASSETS_STRATEGY=stack
<body>
{{ $slot }}
[@liveChartsScripts](https://github.com/liveChartsScripts)
[@livewireScripts](https://github.com/livewireScripts)
</body>
wire:navigate(Livewire SPA): thenavigatestrategy handles SPA navigation automatically. Thestackstrategy does not supportwire:navigate— charts on navigated pages will throwlivecharts is not defined.
Asset modes:
both(default — local-first with CDN fallback),local(no CDN), orcdn(no local). Switch viaLIVECHARTS_ASSETS_MODEin.envorconfig/livecharts.php. See Local assets below.
use Matheusmarnt\LiveCharts\Facades\LiveCharts;
$chart = LiveCharts::line()
->title('Monthly Revenue')
->labels(['Jan', 'Feb', 'Mar'])
->dataset('2026', [100, 200, 150])
->colors(['#3B82F6']);
Generate a class:
php artisan make:chart RevenueChart --type=bar
Edit app/Charts/RevenueChart.php:
namespace App\Charts;
use Matheusmarnt\LiveCharts\Charts\Chart;
use Matheusmarnt\LiveCharts\Charts\Dataset;
use Matheusmarnt\LiveCharts\Enums\TwColor;
class RevenueChart extends Chart
{
protected string $type = 'bar';
public function __construct()
{
parent::__construct();
$this
->title('Revenue')
->labels(['Jan', 'Feb', 'Mar'])
->datasets([
Dataset::make('2026')
->data([400, 300, 600])
->backgroundColor(dark: TwColor::Emerald400, light: TwColor::Emerald600),
]);
}
}
Available --type values: line, bar, area, pie, donut, radar, scatter, bubble, heatmap, rangeBar, radialBar, polarArea, boxPlot, treemap, candlestick, matrix, sankey.
Stubs: if you accepted the stubs prompt during
livecharts:install, the generator stub lives atstubs/livecharts/chart.php.stub. Edit that file to customize the boilerplate emitted bymake:chart.
<livewire:livecharts :chart="$chart" />
For a class-based chart:
<livewire:livecharts :chart="new App\Charts\RevenueChart" />
The Livewire component handles mount, hydration, theme detection, and re-render. No JavaScript glue required.
$chart->poll(5000); // milliseconds
The component subscribes to wire:poll="refresh" and dispatches a browser event every tick:
window.addEventListener('livecharts:refreshed', (e) => {
// e.detail.id — the chart's DOM id
})
To hydrate fresh data, override refresh() on a parent Livewire component (re-fetch your data, then re-emit the chart payload).
$chart
->onDataPointClick('chart-clicked')
->onZoom('chart-zoomed')
->onSelection('chart-selected')
->onScroll('chart-scrolled');
use Livewire\Attributes\On;
#[On('chart-clicked')]
public function handle(array $data): void
{
// $data: ['seriesIndex' => 0, 'dataPointIndex' => 2, 'value' => 150, 'label' => 'Mar']
}
$chart
->broadcastOn('private-charts.'.$user->id)
->broadcastAs('chart.updated');
Subscribe via Laravel Echo and the chart re-renders when the channel fires.
The default engine is set in config/livecharts.php:
'engine' => env('LIVECHARTS_ENGINE', 'apexcharts'),
Override per chart:
LiveCharts::line()->engine('chartjs')->labels(...)->dataset(...);
Register a custom engine adapter at runtime (typically in a service provider's boot()):
use App\LiveCharts\Engines\HighchartsAdapter;
use Matheusmarnt\LiveCharts\Facades\LiveCharts;
LiveCharts::registerEngine('highcharts', HighchartsAdapter::class);
The custom class must implement Matheusmarnt\LiveCharts\Contracts\EngineAdapter. Built-in adapters: apexcharts, chartjs.
Internal API change (v2.0+):
EngineFactory::register()andEngineFactory::resolve()are now instance methods bound as a singleton on the container. Public callers should use the facade method above; advanced callers can resolve viaapp(EngineFactory::class).
The package ships pre-built engine bundles via Vite (vite build) under resources/dist:
| Bundle | Source | Purpose |
|---|---|---|
livecharts.js |
resources/js/livecharts.js |
Alpine component + Livewire hooks |
apexcharts.js |
npm apexcharts@^5.10.6 |
ApexCharts engine |
chartjs.js |
npm chart.js@^4.5.1 |
Chart.js engine (UMD-mirrored named exports) |
chartjs-treemap.js |
npm chartjs-chart-treemap |
Treemap plugin |
chartjs-matrix.js |
npm chartjs-chart-matrix |
Matrix plugin |
chartjs-sankey.js |
npm chartjs-chart-sankey |
Sankey plugin |
chartjs-financial.js |
npm chartjs-chart-financial |
Candlestick plugin |
chartjs-luxon.js + chartjs-adapter-luxon.js |
npm luxon + chartjs-adapter-luxon |
Time-axis adapter for candlestick |
livecharts:install republishes all of these to public/vendor/livecharts/js. To re-publish on demand:
php artisan vendor:publish --tag=livecharts-assets --force
Switch modes:
LIVECHARTS_ASSETS_MODE=both # default — local-first, CDN fallback via <script onerror>
LIVECHARTS_ASSETS_MODE=local # no CDN
LIVECHARTS_ASSETS_MODE=cdn # no local copy required
Under the navigate strategy (default), engine scripts are emitted via Livewire [@assets](https://github.com/assets) blocks — Livewire deduplicates them and re-ensures them across wire:navigate transitions. Under the stack strategy, [@liveChartsScripts](https://github.com/liveChartsScripts) emits the right <script> tags for the active mode and registers only the bundles required by the engines actually rendered on the page.
Building from source: if you fork the package or modify
resources/js/livecharts.js, runnpm ci && npm run buildto regenerate the bundles. Thejs-build.ymlworkflow fails CI when the committedresources/dist/is out of sync with the source.
use Matheusmarnt\LiveCharts\Enums\ThemeMode;
$chart->theme(ThemeMode::Auto); // enum form (recommended)
$chart->theme('auto'); // string form — still supported
Available modes: ThemeMode::Auto, ThemeMode::Light, ThemeMode::Dark.
auto follows Tailwind's .dark class on <html> (default) or the prefers-color-scheme media query:
// config/livecharts.php
'theme' => [
'mode' => env('LIVECHARTS_THEME', 'auto'),
'auto_detect' => env('LIVECHARTS_THEME_DETECT', 'class'), // 'class' | 'media'
],
Charts re-color live when the theme toggles — the JS observer patches updateOptions / chart.update directly, no Livewire roundtrip required.
v2.6+ ships TwColor, a 289-case backed enum covering all Tailwind v4 color families. Every chart element accepts dark: / light: named-arg pairs:
use Matheusmarnt\LiveCharts\Enums\TwColor;
$chart
->titleColor(dark: TwColor::Amber300, light: TwColor::Amber600)
->legendColor(dark: TwColor::Slate200, light: TwColor::Slate700)
->gridColor(dark: TwColor::Slate800, light: TwColor::Slate200)
->tooltipColor(dark: TwColor::White, light: TwColor::Slate900)
->backgroundColor(dark: TwColor::Slate900, light: TwColor::White);
Single-value form sets both themes to the same hex:
$chart->titleColor(TwColor::Slate500);
$chart->titleColor('#6b7280'); // plain hex still works
Dataset-level background/border split:
use Matheusmarnt\LiveCharts\Charts\Dataset;
Dataset::make('Revenue')
->data([100, 200, 150])
->backgroundColor(dark: TwColor::Emerald400, light: TwColor::Emerald600)
->borderColor(dark: TwColor::Emerald300, light: TwColor::Emerald700);
Palette presets auto-fill dataset colors:
use Matheusmarnt\LiveCharts\Enums\TwPalette;
$chart->palette(TwPalette::Vibrant); // Vibrant | Muted | Monochrome | Pastel | Neon
Helper methods on TwColor:
TwColor::Sky500->withAlpha(0.6); // 'rgba(14,165,233,0.6)'
TwColor::Sky500->lighter(2); // TwColor::Sky300
TwColor::Sky500->darker(1); // TwColor::Sky600
TwColor::ramp('sky'); // [Sky50, Sky100, ..., Sky950]
All ApexCharts/Chart.js layout primitives are exposed as fluent methods that merge into the engine's options:
$chart
->xaxis(['type' => 'datetime', 'tickAmount' => 6])
->yaxis(['min' => 0, 'forceNiceScale' => true])
->grid(['show' => false])
->stroke(['width' => 2, 'curve' => 'smooth'])
->markers(['size' => 4])
->dataLabels(['enabled' => false]);
Escape hatch for engine-specific options not covered by fluent methods:
$chart->options([
'chart' => ['animations' => ['enabled' => false]],
'tooltip' => ['shared' => true, 'intersect' => false],
]);
php artisan vendor:publish --tag=livecharts-views
Edits to the published Blade files override the package defaults.
php artisan vendor:publish --tag=livecharts-translations
Bundled locales: en, pt_BR, es.
php artisan vendor:publish --tag=livecharts-stubs
Outputs stubs/livecharts/chart.php.stub. make:chart reads the application stub if present, falling back to the package default.
composer update matheusmarnt/livecharts
php artisan vendor:publish --tag=livecharts-config --force
php artisan vendor:publish --tag=livecharts-assets --force # only if running in local asset mode
When upgrading across a major version (e.g. 1.x → 2.x) review CHANGELOG.md for breaking changes before running --force on the config.
The package ships a debug route that renders one of every chart type for visual smoke-testing. Open it in your default browser with:
php artisan livecharts:preview # opens the URL via the OS-native opener
php artisan livecharts:preview --no-open # only prints the URL (CI / headless)
The command detects the host OS and spawns the appropriate opener (open on macOS, xdg-open on Linux/BSD, cmd /c start on Windows). On failure it falls back to printing the URL with the warning string from livecharts.preview.open_failed.
The route is registered automatically at /livecharts/preview under the web middleware group — make sure your local server (php artisan serve, Sail, or Docker) is running before invoking the command. Restrict or disable the route in production by overriding LiveChartsServiceProvider::registerRoutes() from a child provider, or by gating it via middleware in your application.
How can I help you explore Laravel packages today?