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 to create()
  • Audio is loaded via the url option (or ws.load()) — not a separate ws.load() call after create()
  • Region events are on the plugin object, not on the core ws instance
  • Region data is handled externally
  • ws.on() returns an unsubscribe function; use it instead of ws.un()

If you hit an issue not covered here, check the full API reference or open a discussion on the GitHub Discussions page.