20 August 2026  ·  6 min read

DATEV EXTF v700: A Zero-Dependency Serializer in 200 Lines

How NeoDonkey generates DATEV-compatible booking batches without pulling in a single dependency. The full serializer, annotated.

On 31 December 2026, DATEV Mittelstand Faktura stops working. Every business using it needs a new system that can still talk to their tax advisor's DATEV installation. The standard for that conversation is DATEV EXTF version 700, and most implementations start by importing a library that imports six more.

NeoDonkey's serializer has no dependencies. Not one. It is 200 lines of JavaScript that know the 116-column layout, the header format, and the field-length rules defined in DATEV's own Schnittstellenentwicklungs- Leitfaden. It writes the file your tax advisor imports without ever leaving the browser's standard library.

The format is a contract, not a suggestion

DATEV EXTF v700 is not CSV with German headers. It is a fixed-format text file where every field has a maximum byte length, specific padding rules, and a defined position. The header alone contains 14 fields, including version numbers, consultant numbers, and fiscal-year markers. Get one field wrong and the import fails silently or loudly, depending on the version of DATEV Software the advisor runs.

Here is the header structure from the running code:

export const DATEV_EXTF_V700_COLUMNS = [
  'Umsatz (ohne Soll/Haben-Kz)',
  'Soll/Haben-Kennzeichen',
  'WKZ Umsatz',
  'Kurs',
  'Basis-Umsatz',
  // ... 116 columns total
];

The serializer does not guess these. They are the exact column names from DATEV's specification, in the exact order, because the order is the format.

How the header is built

A DATEV EXTF file starts with a header record that identifies the batch. The serializer constructs this by mapping metadata to fixed-width fields:

function buildHeader(params) {
  const fields = [
    { len: 36, val: 'EXTF' + params.version },     // format identifier
    { len:  6, val: params.producer },              // software producer
    { len:  5, val: params.consultantNumber },      // Beraternummer
    { len:  5, val: params.clientNumber },          // Mandantennummer
    { len:  4, val: params.fiscalYearStart },       // WJ-Beginn
    { len:  2, val: params.batchNumber },           // Buchungsstapel
    // ... 8 more fields
  ];
  return fields.map(f => padRight(f.val, f.len)).join('');
}

Each field is right-padded with spaces to its exact byte length. No delimiter. No quotes. The recipient knows where each field ends because the specification says so.

The booking record: one line, one fact

After the header comes the data. Each booking is one line with 116 columns. Most are empty for a simple transaction. The serializer accepts a plain object and writes only the columns that have values:

const booking = {
  'Umsatz (ohne Soll/Haben-Kz)': '11900,00',
  'Soll/Haben-Kennzeichen': 'S',
  'Konto': '8400',
  'Gegenkonto (ohne BU-Schlüssel)': '1200',
  'Belegdatum': '20082026',
  'Buchungstext': 'Rechnung 4711'
};

const line = serializeBooking(booking);
// => "11900,00 S                    ..."

The amount is formatted with a comma as decimal separator because that is what DATEV expects. The date is DDMMYYYY without separators. These are not aesthetic choices. They are format requirements, and violating them means the tax advisor's software rejects the file.

Why zero dependencies matters here

A typical npm package for DATEV export would pull in a CSV parser, a date formatter, a validation library, and their transitive dependencies. That is fine for a server. It is not fine for a system that claims your data never leaves your machine.

NeoDonkey's serializer uses only what the browser already has: String.prototype.padEnd for padding, Number.prototype.toFixed for amounts, and plain object traversal for column mapping. The result is a file that passes DATEV's own validation without any code that was not written by us or by the browser vendor.

The test suite runs the serializer against the official DATEV specification and verifies that every field lands at the correct byte offset. Not approximately. Exactly.

What this does not solve

The serializer writes EXTF v700. It does not write the newer XML-based DATEVconnect online format, which is what DATEV pushes for cloud integrations. EXTF v700 is what tax advisors have imported for decades, and it is what they will keep importing because their existing workflows depend on it.

The serializer also does not validate whether the accounts you reference actually exist in the advisor's Kontenrahmen. That is a business rule, not a format rule, and it belongs in the operating model that defines your company's accounting policy.

Finally, the code is in the repository and tested, but the path from a booked invoice to a serialized batch is not yet complete. The kernel can book facts. The export layer can serialize them. The glue between them is the next open item on the v1.0 scorecard.

The code behind this post is at runtime/export/datev.js. What works and what does not is tracked in the v1.0 scorecard.