bsmap - Beat Saber Beatmap Scripting Library

raw JSON →
2.3.4 verified Fri May 01 auth: no javascript

bsmap is a general-purpose TypeScript library for creating, reading, and modifying Beat Saber beatmaps programmatically. Current stable version is 2.3.4. Maintained actively with frequent releases (every few months). It supports all major beatmap schema versions (v1 through v4.1.0) and modding extensions like Chroma, Cinema, Noodle Extensions, and Mapping Extensions out of the box. Key differentiators: fully-typed schema wrappers for cross-version compatibility, partial object creation with default filling, tree-shakeable modules, built-in validators and optimisers, and utilities for math, color, and easing. Works with Deno, Bun, Node.js (ESM/CJS), and browser bundlers.

error TypeError: Class constructor Beatmap cannot be invoked without 'new'
cause Using static create() method which was removed in v1.6.0, or incorrectly calling constructor as a function.
fix
Use Beatmap.fromJSON(data) instead of new Beatmap(data).
error ERR_MODULE_NOT_FOUND: Cannot find module 'bsmap'
cause Running ESM without 'type':'module' in package.json, or using wrong import style (default import instead of namespace).
fix
Create script.js and add import * as bsmap from 'bsmap' (ESM) or use require('bsmap') (CJS).
error Duplicate identifier 'Beats'
cause Importing Beats from both main and subpath 'bsmap/beats'.
fix
Import Beats from only one source, preferably 'bsmap/beats'.
error Property 'colorNotes' does not exist on type 'V4Difficulty'
cause Using an older version of bsmap (<1.5.0) that uses different property names for v4.
fix
Update to bsmap >=1.5.0 and use the correct schema property names (e.g., colorNotes instead of notes).
breaking In v1.6.0, constructor and create() static method were removed. Use fromJSON() static method instead.
fix Replace new Beatmap(data) or Beatmap.create(data) with Beatmap.fromJSON(data).
breaking In v2.0.0, the main export changed from default to namespace. import * as bsmap from 'bsmap' is now required, import bsmap from 'bsmap' no longer works.
fix Use import * as bsmap from 'bsmap' (ESM) or const bsmap = require('bsmap') (CJS).
breaking In v2.0.0, some utility classes (e.g., Beats) moved to subpath exports like 'bsmap/beats'. Direct import from main may be removed in future.
fix Import utility classes from their respective subpaths for tree-shaking and future compatibility.
breaking In v1.5.0, v3 beatmap schema updated to 3.3.0. Events collection classes changed; FX Event Box Group and FX Events Collection added.
fix Update any v3 beatmap code to handle new FX event structures. Old v3 events still parse but may not serialize correctly.
gotcha When using Node.js CJS with require('bsmap'), TypeScript types will not be available. You must use a separate type declaration or ESM.
fix Use ESM (import * as bsmap from 'bsmap') in TypeScript or with ts-node. For plain JS, types are not needed.
gotcha Partial creation of beatmap objects: absent fields default to undefined, not schema defaults. Missing required fields may cause validation errors on save.
fix Always provide all required fields or use fromJSON() to fill defaults. Use the 'defaults' utility to apply schema defaults if needed.
npm install bsmap
yarn add bsmap
pnpm add bsmap

Reads a v4 difficulty file, adds a note at beat 10, and writes the modified beatmap back. Uses synchronous file I/O and the V4Note wrapper.

import * as bsmap from 'bsmap';
import { readFileSync, writeFileSync } from 'fs';

// Read a v4 difficulty file
const inputPath = './ExpertPlus.beatmap.dat';
const outputPath = './ExpertPlus_modified.beatmap.dat';

const rawData = readFileSync(inputPath, 'utf-8');
const data = bsmap.readDifficultyFileSync(rawData, 4);

// Modify: add a note at beat 10, line 0, layer 1, color red
const note = new bsmap.V4Note({
  beat: 10,
  color: 0, // Red
  line: 0,
  layer: 1,
  direction: 1, // Down
  type: 0, // Note
});
data.difficulty.colorNotes = data.difficulty.colorNotes || [];
data.difficulty.colorNotes.push(note);

// Write back
const serialized = bsmap.writeDifficultyFileSync(data, 4);
writeFileSync(outputPath, serialized, 'utf-8');
console.log('Done! Modified note added.');