Read EDF files
in JavaScript.

edfcore opens EDF, EDF+, BDF and BDF+ recordings (EEG, sleep studies, ECG) in the browser and in Node. Read any ten seconds of a twelve-hour file without loading the rest. When a file is wrong, you get an error that names the byte.

npm install edfcoreQuick startOpen the inspector

Zero dependencies. TypeScript types included. MIT licensed.

An electroencephalogram trace sweeping across an instrument display, drawn from sample values encoded and decoded the way edfcore reads them.

Ten seconds out of twelve hours reads ten seconds of bytes.

An EDF file stores every channel interleaved inside fixed-size data records, so a time window maps to a contiguous run of records. edfcore reads exactly that run. Opening a file reads the header and nothing else. It doesn't scan.

Reading a window in the browser
import { openEdf, blobSource, getSignal, readWindow, toPhysical } from 'edfcore';

const recording = await openEdf(blobSource(file));
const fp1 = getSignal(recording.header, 'Fp1');

const [chunk] = await readWindow(recording, {
  signalIndices: [fp1.index],
  startSeconds: 30,
  durationSeconds: 10,
});

const microvolts = toPhysical(fp1, chunk.signals[0].digital);

Python has the tooling for EDF. JavaScript didn't.

This is every EDF package on npm, surveyed in mid-2026:

CapabilityBefore edfcore
Ships TypeScript typesNo standalone reader does
Random access to a time windowNothing published — the file is loaded whole
BDF / 24-bit samplesNo published package can read them
Discontinuous EDF+DRejected, or silently decoded as contiguous
Typed, located errorsconsole.warn and null, or bare thrown strings
Header validationEssentially none

The strongest prior art is @epicurrents/edf-reader, worth using if you want a whole viewer framework.See the full comparison.

A bad file either throws or reports itself.

One rule decides which. If edfcore can't continue without inventing something, it throws. If it can continue on what the file says, it records a diagnostic on the result. There's no third option, and nothing is written to the console.

A header that declares digitalMinimum === digitalMaximum defines no scale: the gain is a division by zero. edfcore sets signal.scale toundefined, so reading the gain without checking is a compile error andtoPhysical throws at runtime. EDFlib substitutes a gain of 1 here and returns ADC counts labelled as microvolts.

Diagnostics on the result
const recording = await openEdf(source);

for (const problem of recording.header.diagnostics) {
  console.log(problem.code, problem.byteOffset, problem.message);
}
// DEGENERATE_DIGITAL_RANGE 3400 signal 7 "EMG Chin" declares digitalMinimum ===
//   digitalMaximum === 0, so physical scale is undefined (division by zero). …

Discontinuous recordings.

Sleep and long-term-monitoring studies are routinely discontinuous. Most readers either reject those files or decode them as though the gaps weren't there, producing a timeline that's wrong by hours.

readWindow always returns an array of chunks, one per contiguous run, including for continuous files. A window inside a gap returns an empty array.

Reading a discontinuous file
// A discontinuous recording is a list of runs, never one continuous block.
const chunks = await readWindow(recording, {
  signalIndices: [0],
  startSeconds: 0,
  durationSeconds: 7200,
});

chunks.length;                    // one chunk per contiguous run
chunks[1].precededByGap;          // EdfGap — where the recorder stopped

Try it on your own file.

The inspector runs in your browser. Nothing is uploaded, because there's no server to upload to. That's the same reason edfcore suits clinical and research data.