Dictionaries
The engine is dictionary-driven: it knows nothing about FIX 4.4 or 5.0 SP2 until you hand it a dictionary package. Each package is pure data — fields, messages, components, datatypes, enums — generated from permissively-licensed sources, cross-checked in CI, and shipped with the Tags, MsgType and typed-message exports the other guides use.
Which package?
Match the counterparty. @boarteam/fix-dict-fix44 (FIX 4.4) is the workhorse of FX and CFD connectivity; @boarteam/fix-dict-fix42 covers the older venues that never left 4.2; @boarteam/fix-dict-fix50sp2 is the self-contained FIX 5.0 SP2 dictionary — envelope, session and application messages in one. Read the version off the wire: BeginString (8) says FIX.4.4 or FIX.4.2 directly, while FIX 5.0 SP2 messages say FIXT.1.1 — the transport FIX 5.0 introduced — with the application version riding in ApplVerID (1128) / DefaultApplVerID (1137).
import { createFixEngine, tokenize, type FixEngine } from '@boarteam/fix';import { dictionary as fix44 } from '@boarteam/fix-dict-fix44';import { dictionary as fix42 } from '@boarteam/fix-dict-fix42';import { dictionary as fix50sp2 } from '@boarteam/fix-dict-fix50sp2';// One engine per dictionary, chosen by the message's own BeginString (8).// FIX 5.0 SP2 rides the FIXT.1.1 transport, so that is the BeginString its// messages carry on the wire.const engines: Record<string, FixEngine> = { 'FIX.4.4': createFixEngine(fix44), 'FIX.4.2': createFixEngine(fix42), 'FIXT.1.1': createFixEngine(fix50sp2),};const line = '8=FIXT.1.1|9=74|35=A|49=SENDER|56=TARGET|34=1|52=20240101-12:00:00.000|98=0|108=30|1137=9|10=197|';// tokenize is the cheap way to peek: tag/value pairs, no dictionary needed.const [[, beginString]] = tokenize(line, { soh: '|' });console.log(beginString); // → FIXT.1.1const fix = engines[beginString];const { message } = fix.parse(line, { soh: '|' });console.log(message.name); // → Logon// DefaultApplVerID (1137) names the application version — "9" is FIX 5.0 SP2:console.log(message.fields[1137].value); // → 9What each dictionary contains — counts, provenance, recorded gaps — is documented on coverage & correctness, and the versions pages compare the dialects field by field.
The FIXT.1.1 transport pair
FIX 5.0 split the protocol into a transport layer (FIXT.1.1: the envelope and session messages) and an application layer. For most decoding, the self-contained fix-dict-fix50sp2 is all you need. The fourth package, @boarteam/fix-dict-fixt11, is the transport-only dictionary for the pair API: hand createFixEngine a { transport, app } pair and every validation finding is attributed to its layer — which is the difference between answering with a session Reject (3) and a BusinessMessageReject (j).
import { createFixEngine } from '@boarteam/fix';import { dictionary as fixt11 } from '@boarteam/fix-dict-fixt11';import { dictionary as fix50sp2 } from '@boarteam/fix-dict-fix50sp2';// The FIXT pair: the transport dictionary owns the envelope and session// messages, the application dictionary owns the business messages. Every// validation finding is then attributed to its layer.const fix = createFixEngine({ transport: fixt11, app: fix50sp2 });// A NewOrderSingle missing BOTH its sequence number (session) and its// OrdType (application):const raw = '8=FIXT.1.1|9=115|35=D|49=BUYSIDE|56=SELLSIDE|52=20240101-12:00:00.000|' + '11=ORDER-9|55=EUR/USD|54=1|60=20240101-12:00:00.000|38=250000|10=168|';const { message, issues } = fix.parse(raw, { soh: '|' });console.log(issues.length); // → 0// `layer` tells you which side of the split each problem belongs to — i.e.// whether to answer with a session Reject (3) or a BusinessMessageReject (j):for (const issue of fix.validate(message)) { console.log(issue.layer, issue.code, issue.path);}// → session validate/required-field-missing MsgSeqNum// → application validate/required-field-missing OrdTypeThis reference’s browsable pages cover FIX 4.4, 4.2 and 5.0 SP2; FIXT.1.1 is deliberately not a fourth set of pages, because its 74 fields are a subset of the FIX 5.0 SP2 dictionary’s — as a package it exists precisely for the pair API above.
Skipping the engine: free functions
createFixEngine is a thin convenience that pre-binds the dictionary. Every capability is also exported as a free function — parse, parseAll, validate, encode, tokenize, splitMessages, decodeValue, calculateChecksum, bodyLength, loadDictionary, validateDictionary, the typed-message helpers and the extension helpers — for callers who prefer to pass the dictionary explicitly.
import { loadDictionary, parse, splitMessages, validate } from '@boarteam/fix';import { dictionary as fix44Json } from '@boarteam/fix-dict-fix44';// No engine required. The dict packages ship plain JSON; loadDictionary// builds the indexed runtime form once, and the free functions take it// explicitly — same behaviour, same output shapes as the engine methods.const dict = loadDictionary(fix44Json);const raw = '8=FIX.4.4|9=130|35=W|49=SENDER|56=TARGET|34=2|52=20240101-12:00:00.000|' + '55=EUR/USD|268=2|269=0|270=1.0921|271=1000000|269=1|270=1.0923|271=1000000|10=248';const { message, issues } = parse(raw, dict, { soh: '|' });console.log(message.name, issues.length); // → MarketDataSnapshotFullRefresh 0console.log(validate(message, dict).length); // → 0// splitMessages frames a concatenated capture without decoding it:const log = '8=FIX.4.4|9=68|35=A|49=BUYSIDE|56=BROKER|34=1|52=20240101-12:00:00.000|98=0|108=30|10=014|' + '8=FIX.4.4|9=56|35=0|49=BUYSIDE|56=BROKER|34=2|52=20240101-12:00:01.000|10=237|';console.log(splitMessages(log, { soh: '|' }).length); // → 2Output shapes
Four structures cover everything the engine returns; all four are part of the SemVer contract.
| Type | Shape | Notes |
|---|---|---|
| ParsedMessage | { msgType, name?, beginString?, framed, fields, groups } | fields and groups are keyed by tag; framed only means the envelope was found. |
| ParsedField | { tag, name?, raw, value } | raw is the verbatim wire text (the source of truth for re-encoding); value is typed by the datatype. |
| ParsedGroupEntry | { fields, groups } | Groups are arrays of these, addressed by counter tag (message.groups[268]) — never parallel arrays. |
| FixIssue | { code, severity, message, path?, refTagID?, refSeqNum?, refMsgType?, sessionRejectReason?, layer? } | code is stable under SemVer; layer appears when validating against a transport/application pair. |