ApexCharts 7.0 is a major version whose headline change is a subtraction: the default bundle stops shipping every feature. Nine features became explicit imports.

The short answer to the question you actually came here with: for most projects the upgrade is npm install apexcharts@7 and nothing else. Every chart type, axis, tooltip, legend, toolbar, export, annotation, theme and plugin stays where it was. You need one extra line only if you use one of the nine, and the console tells you when you do.

The number that started it: 24% of the 6.10.0 default bundle was licence-gated Premium code, which an unlicensed user could only run with a watermark on their chart. Everyone downloaded it anyway. That is now 0%.

Key takeaways

ChangeWhat it is
Nine opt-in featuresimport 'apexcharts/features/<name>' for Trellis, Storyboard, Perspectives, Ink, canvas renderer, Linked Views, measure, Rewind, context menu.
Default bundle252,005 B gzipped, down from 291,654 B. -39,649 B (-13.6%)
New: TrellisSmall multiples in one key: trellis: { by: 'region' }. Premium.
New: pictogramsshape: 'pictogram' draws a glyph per unit instead of fetching an icon.
New: lean coredist/apexcharts.core.js at 136,921 B gzipped, for script-tag pages with no bundler.
Breaking: borderRadiusWhenStackedRemoved. Corner ownership follows the stack's outer edge.
Breaking: dataLabels.animate.enabledNow defaults to true.
Perf: markersmarkers.largeDatasetThreshold batches markers into one path. Opt-in.
Perf: null-split linesOne path element per series instead of one per segment.
FixesLogarithmic axis geometry and tickAmount, Shadow DOM tooltips, stacked baselines on ragged data, annotations with no data.

Will 7.0 break my app?

Only if you use one of these nine. If you do not, there is nothing to do.

FeatureYou are using it if your config or code has…
Trellistrellis: { by }, trellis.row, trellis.column, ApexCharts.trellis()
Storyboardchart.storyboard, chart.storyboard.bind()
PerspectivesgetViewState(), applyViewState()
Inkchart.ink.enabled, or any draggable annotation
Canvas rendererchart.renderer: 'canvas' or 'auto'
Linked viewschart.link, ApexCharts.crossfilter
Measure rulerchart.measure.enabled, startMeasure()
Rewindchart.history.enabled
Context menuchart.contextMenu

The fix is one line, and the shape is the same whichever feature it is:

import ApexCharts from 'apexcharts'
import 'apexcharts/features/trellis'   // the one line

Or a second script tag, after the main one:

<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts/dist/features/trellis.js"></script>

The import is side-effecting. There is nothing to name or pass along, and order matters only in that the feature must load after the main bundle.

You will not have to guess. Each of the nine warns in the console when its configuration is present but the feature is not, and every warning names both routes:

ApexCharts: `trellis` requires the trellis feature, which is not in the
default bundle. Bundler: import 'apexcharts/features/trellis'. Script tag:
add <script src='.../dist/features/trellis.js'> after apexcharts.js.
Rendering as a single chart.

Where a chart can still draw something sensible without the feature, it does, and the warning says what it did instead. A trellis renders as a single chart. A chart asking for renderer: 'canvas' draws with SVG. Nothing throws and nothing disappears in silence.

What each feature costs

If you use one of the nine, you end up roughly where you were: paying for that feature and not the other eight.

FeatureAdd-ongzip
Trellisfeatures/trellis25.7 KB
Storyboardfeatures/storyboard8.0 KB
Perspectivesfeatures/perspectives6.7 KB
Inkfeatures/ink6.2 KB
Canvas rendererfeatures/renderer-canvas6.0 KB
Linked viewsfeatures/link5.3 KB
Measure rulerfeatures/measure4.6 KB
Rewindfeatures/history3.2 KB
Context menufeatures/context-menu2.3 KB

Storyboard registers Perspectives too, so importing both costs no more than importing Storyboard.

Trellis: small multiples in one key

Six regions on one axis gives you six crossing lines and a legend nobody reads. The alternative, a grid of small charts, has always been possible by hand: build six chart instances, work out a common y domain, then keep their plot rectangles, colours, legends and zoom windows in agreement forever.

That last part is where hand-built small multiples go wrong, because a panel 4px narrower than its neighbour quietly lies about the shape it is showing.

Switch the demo below from one chart to a trellis, then change the y scale.

Layout
Y scale
// no trellis: six series on one axis

Same seven series both ways. One chart puts six regions on one axis, where the lines cross often enough that reading any single region means hunting for its colour in the legend. Trellis is the one key in the snippet above: each region gets a panel, all six share the y domain and the x window, and one crosshair sweeps them together.

The dashed Target line carries no region key, so it belongs to no panel and is drawn in all of them. Switch Y scale to independent and each panel takes its own domain: the axis labels change but the plot rectangles still line up to the pixel, because the grid measures every panel’s label gutter and pushes the widest as a shared floor. Shapes stay comparable even when the numbers no longer are.

import ApexCharts from 'apexcharts'
import 'apexcharts/features/trellis'

new ApexCharts(el, {
  chart: { type: 'line' },
  series: [
    { name: 'Revenue', region: 'North', data: north },
    { name: 'Revenue', region: 'South', data: south },
    { name: 'Target', data: target },   // no region key: drawn in every panel
  ],
  trellis: { by: 'region' },
}).render()

trellis.by is the whole configuration. A series carrying no facet key belongs to no panel, so it is drawn in all of them, which is how a target or a benchmark reaches every panel without being duplicated in the data.

Beyond that: row × column for 2-D grids, trellis.data for tidy row tables, virtualization above 64 panels, annotations scoped to named panels, one composed export for the whole grid, and click-to-promote a panel to full width.

The frames a shared y domain does not cover

This is the part worth internalising, and it is the least obvious thing in the release.

A shared y domain is not sufficient for every chart type. Some types draw a domain that is not their data's y values, and some carry a scale channel the y machinery never sees. Left alone, each panel derives that hidden frame from its own data, and the panels silently stop being comparable, which is the worst failure a trellis has. So the grid resolves one shared frame per type:

TypeWhat is shared, and why
histogramOne set of bin edges, plus a bin-count y domain. Per-panel bins put the same bar width over different value ranges.
violinOne KDE bandwidth. The automatic rule derives it from each panel's own spread, so identical options would smooth panels differently.
heatmapOne colorScale min/max, because colour is the value channel here.
bubbleOne z extent, so bubble areas compare across panels.
pie / donutPanel radius from the panel total, scaled so area rather than radius is proportional.

The heatmap case is the one to note: scales.color: 'independent' is refused with a warning rather than honoured. The same colour meaning different values in neighbouring panels is a silent lie, so the grid declines to draw it.

And a grid of equal-size pies cannot encode magnitude at all, which is the honest objection to a pie trellis. radiusByTotal: true is what answers it.

Two breaking changes that are easy to miss

Neither is in the nine, and neither will produce a console warning.

plotOptions.bar.borderRadiusWhenStacked is removed

Rounded corners on a stacked bar are no longer a setting. Corner ownership follows the outer edge of the stack, which is what 'last' approximated and what 'all' got wrong on any stack whose last series was empty.

 plotOptions: {
   bar: {
     borderRadius: 4,
-    borderRadiusWhenStacked: 'last',
   },
 }

An unknown option is ignored, so leaving it in place is harmless. It just does nothing.

dataLabels.animate.enabled now defaults to true

Data labels ride to their new position on a data-change update instead of snapping there.

The reasoning: the bars, the markers and the axis ticks already reflowed on one clock, so a label that jumped to its final slot on the first frame arrived several hundred milliseconds before the bar it belonged to. Bar and column charts only, and a label that has not moved is a per-label no-op, so an update that does not reorder costs nothing.

// To restore the old behaviour:
dataLabels: { animate: { enabled: false } }

What broke when we upgraded this site

We moved apexcharts.com to 7.0.0, and two things went wrong that are worth passing on, because neither announced itself.

A missing add-on degrades in silence, and silence looks like working

Our demo pages are static HTML that load ApexCharts from a CDN, and our build rewrites the library's relative script paths to CDN URLs. It knew about two paths. After 7.0, the samples also loaded a third kind: dist/features/<name>.js.

Those were not being rewritten. They resolved to a path that does not exist, so the request 404'd, the feature never registered, and the chart rendered without it behind nothing but a console warning. A trellis drew as a single chart. The measure ruler simply was not there.

The lesson generalises past our build: an add-on is a separate request that can fail on its own. If you load ApexCharts from script tags, the add-on tag is a new thing that can 404 independently of the library, and the failure mode is a chart that looks fine to anyone not reading the console.

Worth doing after you upgrade:

  • Open the console on any page using one of the nine. The absent-feature warning is deliberately loud and names both routes.
  • If you build your HTML with a pipeline that rewrites asset paths, check that it handles dist/features/*.js and not just dist/apexcharts.js.

An unversioned CDN can serve you a mismatched pair

This one is sharper, and it will catch anyone using an unversioned CDN URL during a release window.

cdn.jsdelivr.net/npm/apexcharts and cdn.jsdelivr.net/npm/apexcharts/dist/features/trellis.js are two separate cache entries. On release day they can resolve to different versions. We hit exactly that: the bare package URL was still serving 6.10.0 from cache while the deep add-on path had already moved to 7.0.0.

A 7.0 add-on loaded against a 6.10 core does not degrade gracefully. It throws:

Uncaught TypeError: Cannot read properties of undefined (reading '__apex_Utils')

The add-on channel is new, so this pairing could not happen before 7.0. If you load core and add-ons from an unversioned CDN:

  • Pin both to the same exact version, or
  • purge the CDN cache for both paths together after a release, and confirm the version actually flipped before you rely on it. On jsDelivr the served version is in the x-jsd-version response header.

Two unversioned URLs are not one atomic thing. That is the whole lesson.

A lean baseline for script tags

Tree-shaking only ever helped people with a build step. A page using a <script> tag had exactly one artifact and no way to decline any of it.

7.0 adds dist/apexcharts.core.js: the chart class with no chart types and no optional features, which you assemble from separate tags.

<script src="https://cdn.jsdelivr.net/npm/apexcharts/dist/apexcharts.core.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts/dist/line.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts/dist/features/legend.js"></script>
Script taggzip
dist/apexcharts.min.js (everything)252,005 B
dist/apexcharts.core.min.js (baseline)136,921 B

This is purely additive. apexcharts.js is unchanged and still batteries-included, and a page that wants everything should keep loading it rather than assembling it from parts.

One caveat: the lean core is not as small for a script tag as apexcharts/core is for a bundler. A bundler can drop the internals your app never touches, while a script tag has to keep them, because any add-on loaded later may reach for any of them.

Performance: markers, and lines full of nulls

Both of these came out of measuring rather than guessing, and one of them corrects a common misdiagnosis.

Markers, not nulls, dominate a large render. One element per marker costs a DOM node, about 16 attribute writes and an appendChild, roughly 8µs each. At 2,000 points, the markers are 15ms of an 18ms render.

markers.largeDatasetThreshold draws a series' markers as one path element per marker size once the series exceeds it, which takes that render from 18ms to 5.8ms.

It also covers the markers showNullDataPoints adds implicitly, which is the part people misattribute: every point beside a null is isolated and gets its own dot, so a 2,000-point series with half its values null built about 1,000 marker elements even with markers switched off. Batched, that case goes from 15ms to 3.7ms, and 5,000 points from 37ms to 5.9ms.

When not to use it. It is off by default because it is not pixel-identical where markers overlap, and above roughly 1,000 points in a normal-width chart they always do. One path is rasterized as a single region: all the fills paint, then all the strokes, so the seams between neighbours disappear and dense clusters read flatter. Measured at 1% to 9% of pixels, scaling with density. It also applies only where markers are already non-interactive and uniform, so a chart with discrete markers, per-point colours or a marker click handler is unaffected.

A null-split line is now one path element. A null breaks the line, and that break used to be expressed as a separate <path> per surviving segment. In SVG a gap is just another subpath, so the whole series fits in one element. At 2,000 points with 1-in-7 nulls: 4.9ms to 3.0ms. At 5,000 points: 11ms to 3.5ms. A series with 286 nulls goes from 288 path elements to one.

Every number in this section is reproducible. The render cost lab that came out of this work sweeps point count, null density and marker size with batching on and off, and reports the render time for each combination, so you can measure your own shape of data rather than trusting ours.

Upgrading

npm install apexcharts@7

A short checklist:

  1. Add the one line for any of the nine features you use. The console names it if you miss one.
  2. Remove borderRadiusWhenStacked if you set it, and check any stacked bar with a corner radius.
  3. Decide about dataLabels.animate. It is on now. Set enabled: false if you preferred the snap.
  4. If you load from script tags, confirm your core and your add-ons are the same version.
  5. Open the console on a page using a premium feature, once, and read it.

Framework wrappers need no new version. react-apexcharts, vue3-apexcharts, vue-apexcharts and stencil-apexcharts already accept 7.x, and ng-apexcharts@3.1.0 widened its peer range to ^6.0.0 || ^7.0.0. If you are on ng-apexcharts@3.0.0, upgrade it alongside, since its peer range excludes 7.x and npm will report a conflict.

Where to go next

Frequently asked questions

Will upgrading to ApexCharts 7.0 break my charts?

For most projects, no. Every chart type, axis, tooltip, legend, toolbar, export, annotation and theme stays in the default bundle, so the upgrade is `npm install apexcharts@7` and nothing else. You need one extra line only if you use one of nine specific features: Trellis, Storyboard, Perspectives, Ink, the canvas renderer, Linked Views, the measure ruler, Rewind, or the context menu. Two smaller changes also affect stacked bars with a corner radius and data labels on animated updates.

Why did ApexCharts move features out of the default bundle?

24% of the 6.10.0 default bundle was licence-gated Premium code, which an unlicensed user could only run with a watermark on their chart. Everyone downloaded it regardless. Tree-shaking existed but you only got it by reading the docs and rewriting your imports, so the default path was also the maximal one. Moving the rarely-used features behind an explicit import inverts that: the default is the smaller thing, and a feature is paid for by the projects that ask for it.

How much smaller is the ApexCharts 7.0 bundle?

The default bundle is 252,005 B gzipped against 291,654 B for 6.10.0, a saving of 39,649 B or 13.6%. If you use one of the nine opt-in features you end up roughly where you were, paying for that feature and not the other eight. If you use none, the saving is free.

What is a trellis chart?

A trellis, also called small multiples or faceting, splits one dataset into a grid of small charts that share their scales, so the panels can be compared against each other. In ApexCharts 7.0 it is one config key: `trellis: { by: 'region' }` turns a series array into one panel per region, and the grid owns the shared y domain, the x window, the colour map, one legend, one toolbar and a crosshair that sweeps every panel.

Do trellis panels stay aligned if each one has its own y scale?

Yes. Independent scales mean different axis-label widths, which would normally leave every panel a slightly different size. The grid measures each panel's label gutter and pushes the widest as a shared floor, so the plot rectangles still agree to the pixel. Shapes stay comparable even when the numbers no longer are.

Is Trellis free to use in ApexCharts?

No. Trellis is a Premium feature, so it renders with a trial watermark until a licence key is applied. It works fully in trial mode, so you can build and evaluate against it before buying.

Should I turn on markers.largeDatasetThreshold?

Only for a plain line or area chart with a lot of points where render time matters. It batches a series' markers into one path element per marker size, which takes a 2,000-point render from 18ms to 5.8ms, but it is not pixel-identical where markers overlap: one path is rasterized as a single region, so overlapping markers lose their individual outlines and dense clusters read flatter. It is off by default for that reason, and it does not apply where markers are interactive or per-point coloured.