Build Chart Plugins With ApexCharts Weave
Weave is the plugin platform that ships with ApexCharts 6.0. It answers a question the library got for years: "how do I draw my own thing on the chart without forking it or hacking the SVG?" A Weave plugin registers once by name, subscribes to lifecycle hooks like draw, and paints into its own sandboxed layer using a stable API. It reads scales, data, and theme, but it never touches the chart's internal state, so it cannot break rendering and it survives version upgrades.
This post walks through a real plugin end to end: the mean-line plugin you see running below, how the API is scoped so a plugin is safe, and how to configure and publish one.
Key takeaways
- A plugin is registered once, globally, and activated per chart.
ApexCharts.registerPlugin({ name, setup }), thenplugins: [{ name }]on each chart that wants it. - It draws into its own layer.
api.layer()returns a sandboxed SVG group that is wiped at the start of every draw pass, so the plugin can never corrupt the chart's own output. - It is fed a stable API, not internals:
api.scales(data to pixels),api.data(series),api.theme(colors and tokens),api.emit(events), andapi.store(per-plugin state). - Events are namespaced.
api.emit('x')arrives asplugin:<name>:x, so it can never trigger the chart's internal subscribers. apiVersionis a contract. It lets the library reject a plugin written against an incompatible API.
See it live
The dashed mean line below is not in the chart config. It is drawn by a plugin that, on every draw pass, reads the series, computes the mean, converts it to a pixel, paints a line and label into its own layer, and emits the value back to the page. Press New data: the plugin recomputes and redraws, and the readout updates from the emitted event.
The dashed mean line is not part of the chart config. A Weave plugin computes it on every draw pass and paints it into its own layer.
Anatomy of a plugin
A plugin is an object with a name, an optional apiVersion, and a setup function. Register it once:
import ApexCharts from 'apexcharts'
import 'apexcharts/features/weave'
ApexCharts.registerPlugin({
name: 'mean-line',
apiVersion: 1,
setup(api) {
// Subscribe to a lifecycle hook. `draw` fires on every paint.
api.on('draw', (ctx) => {
const { scales, data } = ctx
if (!scales || !data.length) return
const series = data.filter((s) => !s.hidden)[0]
if (!series || !series.points.length) return
const ys = series.points.map((p) => p.y).filter((y) => y != null)
const mean = ys.reduce((a, b) => a + b, 0) / ys.length
// A layer is this plugin's own SVG group, cleared each draw pass.
const layer = api.layer({ z: 'front' })
layer.clear()
const y = scales.y(mean) // data value -> pixel
const accent = api.theme.token('accent') || '#008FFB'
layer.line({ x1: 0, y1: y, x2: scales.gridWidth, y2: y, stroke: accent, width: 2, dash: 5 })
layer.text({ x: scales.gridWidth - 6, y: y - 7, text: `mean ${mean.toFixed(1)}`, color: accent, anchor: 'end' })
// Hand the value back to the page.
api.emit('meanComputed', { value: mean })
})
},
})
Then activate it on any chart by naming it:
const chart = new ApexCharts(el, {
series: [{ name: 'Value', data: [42, 61, 38, 74, 55, 48, 67, 52] }],
chart: { type: 'line' },
plugins: [{ name: 'mean-line' }], // <- activation
})
chart.render()
Registration is global and one-time; activation is per chart. The same plugin can be on some charts and absent on others, and re-registering the same name simply replaces the definition.
Why a plugin is safe: the sandbox
The whole design goal of Weave is that a plugin cannot break the chart. Three things enforce that.
It only gets the API, not the chart object. setup(api) hands over a curated surface. There is no back door to internal series arrays or the SVG.js tree.
| API member | What it gives you |
|---|---|
api.scales | x(v), y(v, axis) to convert data to pixels, plus gridWidth / gridHeight and domains |
api.data | The resolved series: name, hidden, color, and points[] |
api.layer(opts) | A sandboxed SVG group to draw into (line, path, rect, circle, text, clear) |
api.theme | mode, foreColor, seriesColor(i), and token(name) for design tokens |
api.emit(name, detail) | Fire an event on the chart's bus, namespaced as plugin:<name>:<...> |
api.store | A per-plugin object for state that persists across draws |
api.options | The live options for this plugin (from the plugins array), refreshed on update |
Its drawing is isolated. api.layer() returns the plugin's own group, and it is wiped at the start of every draw pass. That is why you must call api.layer() inside the handler and never cache it across draws: a handle from a previous pass points at a detached node, and its writes vanish silently.
Its events cannot collide. api.emit('meanComputed', ...) arrives on the chart bus as plugin:mean-line:meanComputed. Listen for it from the page:
chart.addEventListener('plugin:mean-line:meanComputed', (c, payload) => {
console.log('mean is', payload.value)
})
Because the name is namespaced, a plugin can never fire the chart's own lifecycle events by accident.
Configuring a plugin per chart
Activation takes options, and they are available live as api.options, refreshed whenever the chart's plugins config changes. That means updateOptions({ plugins: [{ name, options }] }) reconfigures a running plugin in place, no re-registration needed.
// activation with options
plugins: [{ name: 'mean-line', options: { color: '#ef4444', showLabel: false } }]
// inside setup, read them each draw
api.on('draw', (ctx) => {
const { color = '#008FFB', showLabel = true } = api.options
// ...draw using color; skip the label when showLabel is false
})
The apiVersion contract
apiVersion declares which version of the plugin API your setup was written against. The library uses it to refuse a plugin built for an incompatible contract, so a plugin that expects an older or newer API fails loudly at registration instead of drawing garbage later. Set it to the version you developed against (1 today) and bump it when you migrate to a future API revision.
Publishing a plugin to npm
A distributable plugin is just a module that registers itself. Two patterns work:
Side-effect registration (simplest for consumers):
// apexcharts-mean-line/index.js
import ApexCharts from 'apexcharts'
ApexCharts.registerPlugin({ name: 'mean-line', apiVersion: 1, setup /* ... */ })
Consumers import 'apexcharts-mean-line' once and add { name: 'mean-line' } to their charts.
Explicit register function (avoids bundling a second ApexCharts copy):
// apexcharts-mean-line/index.js
export function registerMeanLine(ApexCharts) {
ApexCharts.registerPlugin({ name: 'mean-line', apiVersion: 1, setup /* ... */ })
}
Consumers call registerMeanLine(ApexCharts) with their own instance. This is the safer pattern for a published package, because it uses the host app's ApexCharts rather than pulling in its own. Whichever you choose, document the options your plugin reads and the events it emits, since those are your public surface.
When to reach for a plugin (and when not to)
Use Weave when you need to draw something the built-in options do not express: a reference band computed from the data, a custom overlay, a domain-specific marker, an integration that reports values back to your app. Use it especially when you want that behavior reusable across many charts or shippable as a package.
Do not reach for a plugin when a built-in option already does the job. A static horizontal line is an annotation, not a plugin. A one-off label is dataLabels. Plugins earn their keep when the behavior is computed, reusable, or distributable.
Summary
Weave makes ApexCharts extensible without forking it. A plugin registers once by name, activates per chart through the plugins array, and paints into its own sandboxed layer using a stable API of scales, data, and theme. It cannot read internals, its layer is isolated, and its events are namespaced, so it is safe by construction and survives upgrades via apiVersion. Import apexcharts/features/weave, write a setup that hooks draw, and you can draw anything you can compute. See the plugins guide for the full API reference, and the rest of what shipped in ApexCharts 6.0.
Frequently asked questions
What is Weave in ApexCharts?
Weave is the plugin platform introduced in ApexCharts 6.0. A plugin is registered once globally with ApexCharts.registerPlugin({ name, setup }), then activated per chart through the top-level plugins array. In its setup it subscribes to lifecycle hooks (such as draw) and paints into its own sandboxed SVG layer using a stable API: api.scales to convert data to pixels, api.data for the series, api.theme for colors, api.layer to draw, and api.emit to send events back. It never reaches into chart internals.
How do I register and activate a Weave plugin?
Register once with ApexCharts.registerPlugin({ name: 'my-plugin', apiVersion: 1, setup(api) { api.on('draw', ctx => { ... }) } }). Then activate it on any chart by adding it to that chart's options: plugins: [{ name: 'my-plugin', options: { ... } }]. Registering is global and idempotent (re-registering a name replaces it); activation is per chart, so the same plugin can be on some charts and not others.
Can a Weave plugin break the chart or read its internals?
No. A plugin only receives the stable api object (scales, data, theme, layer, emit, store). It draws into its own layer, which is cleared at the start of every draw pass, so it cannot corrupt the chart's own SVG. Its events are namespaced as plugin:<name>:<event>, so they can never trigger the chart's internal lifecycle subscribers. The apiVersion field lets the library reject a plugin written against an incompatible contract.
How do I publish an ApexCharts plugin to npm?
Ship a module whose side effect calls ApexCharts.registerPlugin, or export a register function that takes ApexCharts so the plugin does not bundle its own copy. Consumers import your package once, then add { name } to their plugins array. Document the options your plugin reads (available live as api.options) and the events it emits.