Migrating from v6 to v7
v7 is a ground-up rewrite that shifts the package to ESM-first, replaces the old multi-backend architecture with a single unified player, and moves the Regions API into a proper plugin pattern. Most upgrades take about ten minutes.
Imports & build
v7 is an ES module package ("type": "module" in package.json). The named export map ships three surfaces:
| Surface | Import path |
|---|---|
| Core | wavesurfer.js |
| Plugin (ESM) | wavesurfer.js/dist/plugins/<name>.esm.js |
| Plugin (short alias) | wavesurfer.js/plugins/<name> |
// v7 — recommended
import WaveSurfer from 'wavesurfer.js'
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js'
// or the short alias form:
import RegionsPlugin from 'wavesurfer.js/plugins/regions'
Both forms resolve to the same ESM bundle. If your bundler still requires CJS, a .cjs fallback is provided automatically via the "require" field in the export map.
CDN / UMD — the global object changed. In v6 the namespace lived on window.WaveSurfer; it still does, but plugin globals are now accessed as WaveSurfer.RegionsPlugin, WaveSurfer.TimelinePlugin, etc.:
<script src="https://unpkg.com/wavesurfer.js@7"></script>
<script src="https://unpkg.com/wavesurfer.js@7/dist/plugins/regions.js"></script>
<script>
const ws = WaveSurfer.create({ container: '#waveform', url: '/audio.mp3' })
const regions = ws.registerPlugin(WaveSurfer.RegionsPlugin.create())
</script>
Creating an instance
WaveSurfer.create(options) still works the same way. The main option-level changes:
backend is now a two-value string
v6 had several backend strings ('MediaElement', 'WebAudio', 'MediaElementWebAudio'). v7 exposes exactly two:
backend?: 'WebAudio' | 'MediaElement' // defaults to 'MediaElement'
If you were using 'MediaElementWebAudio' or any other value, switch to 'WebAudio' or omit the option entirely to stay on MediaElement.
Pre-decoded peaks and duration
v7 accepts peaks and duration directly in the constructor options, so you can render a waveform without fetching the audio file at all:
WaveSurfer.create({
container: '#waveform',
peaks: [new Float32Array([0, 0.5, 1, 0.5, 0])],
duration: 30,
})
media option replaces manual element injection
Pass an existing <audio> or <video> element via the media option instead of patching it in after creation.
Regions API
The Regions plugin was completely rewritten in v7. There is no longer a built-in regions namespace on the core instance — you register RegionsPlugin explicitly and interact through the returned plugin object.
Setup
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js'
const ws = WaveSurfer.create({ container: '#waveform', url: '/audio.mp3' })
const regions = ws.registerPlugin(RegionsPlugin.create())
Adding regions
const region = regions.addRegion({
start: 1,
end: 4,
color: 'rgba(0, 100, 255, 0.2)',
content: 'Verse 1',
drag: true,
resize: true,
})
No built-in data field
v6 regions accepted a free-form data object. v7’s RegionParams has no data property — store arbitrary metadata in a Map keyed by region.id instead:
const meta = new Map()
const region = regions.addRegion({ start: 1, end: 3 })
meta.set(region.id, { label: 'verse', track: 2 })
Region events
Listen via the plugin object (not the core instance):
regions.on('region-clicked', (region, e) => {
e.stopPropagation()
region.play()
})
regions.on('region-updated', (region) => {
console.log('New start:', region.start, 'end:', region.end)
})
Full list of plugin-level events: region-initialized, region-created, region-update, region-updated, region-removed, region-clicked, region-double-clicked, region-in, region-out, region-content-changed.
See the Plugins reference for more.
Timeline
formatTimeCallback now receives only one argument — the time in seconds. The pxPerSec second argument that existed in v6 has been removed:
// v6 — two arguments
formatTimeCallback: (seconds, pxPerSec) => { ... }
// v7 — one argument
formatTimeCallback: (seconds) => {
const m = Math.floor(seconds / 60)
const s = Math.round(seconds % 60)
return `${m}:${s.toString().padStart(2, '0')}`
}
Everything else (height, timeInterval, primaryLabelInterval, container, insertPosition, style) is unchanged.
See the Timeline plugin page for the full option reference.
Events
The core event names are largely the same. A few additions and renames to be aware of:
| v6 name | v7 name | Notes |
|---|---|---|
waveform-ready |
ready |
ready now passes duration as its argument |
audioprocess |
audioprocess |
Unchanged; fires only during playback |
seek |
seeking |
Fires on user seeks; argument is currentTime |
redraw |
redraw + redrawcomplete |
redrawcomplete fires after all channels are drawn |
In v7 the idiomatic way to unsubscribe is to call the function that ws.on() returns. The ws.un() and ws.unAll() methods still exist, but the returned-unsubscribe pattern is preferred.
See Events for the complete list.
Before & after
A typical v6 setup with a regions plugin, updated to v7:
// ── BEFORE (v6) ─────────────────────────────────────────────
import WaveSurfer from 'wavesurfer.js'
const ws = WaveSurfer.create({
container: '#waveform',
backend: 'MediaElement',
plugins: [
WaveSurfer.regions.create({
regions: [
{ start: 1, end: 3, color: 'rgba(0,0,0,0.1)', data: { id: 'intro' } },
],
}),
],
})
ws.load('/audio.mp3')
ws.on('region-click', (region) => region.play())
// ── AFTER (v7) ──────────────────────────────────────────────
import WaveSurfer from 'wavesurfer.js'
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js'
const ws = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
// backend: 'MediaElement' is the default — no need to set it
})
const regions = ws.registerPlugin(RegionsPlugin.create())
ws.on('ready', () => {
regions.addRegion({ start: 1, end: 3, color: 'rgba(0,0,0,0.1)', content: 'Intro' })
})
regions.on('region-clicked', (region, e) => {
e.stopPropagation()
region.play()
})
Key differences at a glance:
- Plugins are registered with
ws.registerPlugin(Plugin.create()), not passed inline tocreate() - Audio is loaded via the
urloption (orws.load()) — not a separatews.load()call aftercreate() - Region events are on the plugin object, not on the core
wsinstance - Region
datais handled externally ws.on()returns an unsubscribe function; use it instead ofws.un()
If you hit an issue not covered here, check the full API reference or open a discussion on the GitHub Discussions page.