The free, zero-dependency, TypeScript-first FIX analyzer that runs in the browser. Paste a raw FIX message — get back named fields, repeating groups expanded into real nested objects, and every framing / datatype / validation problem as data. No license key. No backend. No session engine.
📖 Documentation · 🧭 API reference ·
A raw FIX log line is piped into
examples/demo-decode.mjsand decoded in stages — raw blob → named, typed fields → theNoMDEntriesrepeating group expanding into nested Bid/Offer objects → a clean verdict. Then a deliberately-corrupted message is piped in: it parses anyway, every defect returned as data instead of an exception. It's a small example script calling the library (not a bundled CLI); the same pure engine runs unchanged in a browser tab — no Node-only APIs in the core. Generated from.github/demo.tapeviascripts/render-demo.sh, so it stays correct by construction.
import { createFixEngine } from '@boarteam/fix';
import { dictionary } from '@boarteam/fix-dict-fix44';
const fix = createFixEngine(dictionary);
// A Market Data Snapshot (35=W). SOH shown as | for readability.
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 } = fix.parse(raw, { soh: '|' }); // never throws
message.msgType; // "W"
message.name; // "MarketDataSnapshotFullRefresh"
message.framed; // true
message.fields[55].value; // "EUR/USD" (name: "Symbol")
issues; // [] — framing, checksum, and datatypes all verified clean
fix.validate(message); // [] — required fields, enums, and conditional rules all passThe decoded message — note the 268 repeating group is an array of nested objects (not parallel arrays):
Malformed input is data, never an exception. You have logs full of half-truncated, corrupted, or hand-edited messages, and you have watched free FIX parsers die on them. This one does not. Feed it 39=Z (not a valid OrdStatus), 6=not-a-number, and a wrong BodyLength / CheckSum, and parse still returns — the problems come back in issues:
// fix.parse(brokenRaw) — still no throw; message is populated, issues describe the damage:
{ "code": "parse/invalid-float", "severity": "error",
"message": "Field AvgPx (6) value \"not-a-number\" is not a valid float.", "path": "AvgPx" }
{ "code": "parse/checksum-mismatch", "severity": "error",
"message": "CheckSum is 000 but the computed value is 060." }
{ "code": "parse/body-length-mismatch", "severity": "warning" }
// fix.validate(message):
{ "code": "validate/value-not-in-enum", "severity": "error",
"message": "Field OrdStatus (39) value \"Z\" is not an allowed value.", "path": "OrdStatus" }
// ...plus validate/required-field-missing for any absent required fields.That is the whole pitch: paste a message, get the structure and every defect, in the browser, with zero dependencies. No socket, no sequence numbers, no connect(). Try it without installing anything — the playground runs this engine client-side; the guides and API reference are at boar.team/fix/docs.
There is a real gap in the FIX tooling landscape, and @boarteam/fix aims squarely at it: a library you can drop into a log viewer, a web dashboard, a test harness, or a CI assertion to decode, validate, and re-encode FIX — with no commercial gate and no server-side session machinery.
Point-in-time snapshot, June 2026. The alternatives are capable tools — this table is about category fit, not quality. The honest differentiators here are browser support, zero dependencies, the analyzer (not session-engine) focus, and the never-throws contract.
| Library | License | Price | Browser | Runtime deps | Parse | Validate | Encode | Never-throws | FIX versions | Stars | npm/mo. |
|---|---|---|---|---|---|---|---|---|---|---|---|
| @boarteam/fix (TS) | Apache-2.0 | Free | Browser + Node | 0 | ✅ | ✅ | ✅ | ✅ | 5.0SP2/FIXT.1.1 + 4.4 + 4.2 | new repo | ~204 |
| fixparser (TS) | Commercial | Free tier needs a registered license key; encode + connectivity gated behind Pro (~$5K+/yr) | Browser (partial) | has deps | ✅ | ✅ | Pro-only | n/a | multiple | ~51 | ~9,375 |
| jspurefix (TS) | Apache-2.0 | Free | Node-only | has deps + heavy post-install | ✅ | ✅ | ✅ (full session engine) | n/a | multiple | ~75 | ~61,370 |
For context — a different category, not embeddable analyzers: QuickFIX (C++) and QuickFIX/J (Java) are server-side session/transport engines; simplefix (Python) is lightweight but unmaintained and Python-only; hosted decoders (FIXSIM, Esprow, and similar) are paste-in SaaS, not npm libraries. If you need a socket that maintains sequence numbers and heartbeats, you want a session engine — see What this is not.
1. It works in front of you. The block above is real captured output, not a sketch. The repeating group becomes an array of nested objects you can index into; numeric fields like MDEntryPx (270) and OrderQty (38) come back as typed numbers, not strings. Pure string / Uint8Array in, structured object out, over TextEncoder / TextDecoder — no Node-only APIs in the core, so the same engine runs in a browser tab and on a Node backend.
2. It is provably correct, and it never throws. parse and validate return findings as data — they never throw, hang, or crash, even on garbage input. BeginString (8), BodyLength (9), and CheckSum (10) are computed byte-accurately on encode, and repeating groups are reconstructed faithfully. Correctness is pinned by golden fixtures and a reference oracle for the market-data and session message sets, a decode → encode round-trip across all 93 FIX 4.4 and all 115 FIX 5.0 SP2 messages, a cross-engine check (FIXT frames encoded here are parsed and validated clean by quickfix-go's transport/application validator), and an adversarial robustness suite that asserts parse and validate never throw, hang, or crash on malformed input. CI runs on Node 18 / 20 / 22 and a browser-like environment, and a bundle check enforces the zero-dependency, browser-safe surface on every commit.
3. It is enterprise-safe. Zero runtime dependencies — nothing to vet transitively, no heavy post-install, nothing that breaks the browser build. No telemetry. No license key, no registration, no gated "Pro" tier. Apache-2.0, with its explicit patent grant. The engine is pure and deterministic — no wall clock, no randomness, no global state — and it never invents session fields (you supply sequence numbers and timestamps), which is exactly why it is trivially testable and safe to embed.
You need the engine plus at least one dictionary:
npm install @boarteam/fix @boarteam/fix-dict-fix44
# or
pnpm add @boarteam/fix @boarteam/fix-dict-fix44Swap in @boarteam/fix-dict-fix42 for FIX 4.2, or @boarteam/fix-dict-fix50sp2 for FIX 5.0 SP2 over FIXT.1.1 (with @boarteam/fix-dict-fixt11 as the transport-only dictionary for layer-attributed validation). The dictionary packages declare a peer dependency on @boarteam/fix. Requirements: Node ≥ 18, or any modern browser. Both are zero-dependency, dual ESM + CJS, with TypeScript types included.
The full documentation lives at boar.team/fix/docs — every code sample on it is compiled and executed against the released package at build time, so the docs cannot drift from the library.
| Guide | What it covers |
|---|---|
| Parse | parse / parseAll, raw vs typed values, repeating groups, pipe-delimited logs, never-throws |
| Validate | The FixIssue shape, stable issue codes as the SemVer contract, severities, layers |
| Encode | Tag-keyed encode, dictionary field order, byte-accurate BodyLength / CheckSum |
| Typed messages | The message() builder, compile-error safety, render(), immutability, type guards |
| Inbound messages | toInbound, the name-keyed body and the session envelope, switch (msgType) dispatch |
| Extend a dictionary | Venue-custom tags with defineExtension — cTrader's 1007/1008 as the worked example |
| Dictionaries | Choosing FIX 4.4, 4.2 or 5.0 SP2, the FIXT pair API, free functions and output shapes |
Alongside the guides:
- API reference — every export, signature by signature, generated from the
dist/api.jsonmodel this package ships (seedocs/api-json.md), with source links and a "since" badge per export. - Diagnostics catalogue — every
parse/*,validate/*,dict/*andextend/*issue code the engine can emit, each with its usual cause. Build-time checked against the installed package, so it is never out of date. - Playground — paste a raw message and decode it in the browser, running this engine client-side.
- FIX reference — the browsable dictionary behind it all: tags, messages, components, datatypes, and per-dialect views for FIX 4.4, FIX 4.2 and FIX 5.0 SP2.
The rest of this README is the short tour.
import { createFixEngine } from '@boarteam/fix';
import { dictionary } from '@boarteam/fix-dict-fix44';
const fix = createFixEngine(dictionary);
// fix: { dictionary, parse, parseAll, encode, validate }parse accepts a string or Uint8Array and never throws — it returns { message, issues }.
const { message, issues } = fix.parse(raw);
message.msgType; // "8"
message.name; // "ExecutionReport"
message.beginString; // "FIX.4.4"
message.framed; // true
// Typed values where the dictionary says so:
message.fields[38].value; // 1000000 OrderQty (typed number, not "1000000")
message.fields[6].value; // 1.0921 AvgPx (typed number)
message.fields[55].value; // "EUR/USD" Symbol
// Each field keeps the verbatim wire value as the source of truth for re-encode:
message.fields[55].raw; // "EUR/USD"
// Repeating groups are arrays of nested objects:
for (const entry of message.groups[268] ?? []) {
console.log(entry.fields[269]?.value, entry.fields[270]?.value);
}Use parseAll for a stream of concatenated messages (e.g. a log file).
→ Full guide: Parsing FIX messages.
validate checks presence (required fields), enum membership, datatypes, and conditional-required rules against the dictionary, returning a FixIssue[]:
const problems = fix.validate(message);
for (const issue of problems) {
console.log(issue.severity, issue.code, issue.message, issue.path);
}Every FixIssue carries a stable code (e.g. validate/value-not-in-enum, parse/checksum-mismatch), a severity (error | warning | info), a human message, and — where relevant — a path, refTagID, refSeqNum, refMsgType, or sessionRejectReason. The codes are part of the SemVer contract; the human message is not.
→ Full guide: Validating FIX messages. Every issue code the engine can emit is catalogued, with its usual cause, in the diagnostics reference.
import { MsgType, Tags } from '@boarteam/fix-dict-fix44';
const wire = fix.encode({
msgType: MsgType.NewOrderSingle,
fields: {
[Tags.SenderCompID]: 'BUYSIDE',
[Tags.TargetCompID]: 'SELLSIDE',
[Tags.MsgSeqNum]: 42,
[Tags.SendingTime]: '20240101-12:00:00.000',
[Tags.ClOrdID]: 'ORDER-1',
[Tags.Symbol]: 'EUR/USD',
[Tags.Side]: '1',
[Tags.TransactTime]: '20240101-12:00:00.000',
[Tags.OrderQty]: 1_000_000,
[Tags.OrdType]: '2',
[Tags.Price]: 1.0921,
},
});encode emits fields in dictionary order and computes BeginString (8), BodyLength (9), and CheckSum (10) byte-accurately. It is pure — you supply the session fields (sequence number, sending time); the engine never invents them.
→ Full guide: Encoding FIX messages.
encode above is the untyped, tag-keyed primitive. For a statically-typed encode side, each dictionary package also ships a message factory generated from the same dictionary: creating a message for a MsgType yields a builder that knows only that message's fields/groups and their value types, and renders byte-identical to encode.
import { message, MsgType } from '@boarteam/fix-dict-fix44';
const wire = message(MsgType.MarketDataSnapshotFullRefresh) // typed to this message's body
.set('MDReqID', 'req-1')
.set('Symbol', 'EUR/USD')
.set('NoMDEntries', [
{ MDEntryType: '0', MDEntryPx: '1.1050' }, // group entries are typed too
{ MDEntryType: '1', MDEntryPx: '1.1052' },
])
.render({
SenderCompID: 'BUYSIDE',
TargetCompID: 'SELLSIDE',
MsgSeqNum: 42,
SendingTime: '20240101-12:00:00.000',
});Illegal fields, wrong value types, and malformed group entries are compile errors; value types follow the field datatype (enumerated → the value union; Boolean → boolean; numeric → number | string; else string). Envelope/session fields (MsgSeqNum/SenderCompID/SendingTime/TargetCompID; framing 8/9/10 computed) are supplied to render — the body type excludes them, so the library holds no sequence counter, clock, or comp-IDs. message(...) is a fast, fluent mutable builder for hot loops; message.immutable(...) is copy-on-write; both accept a bulk object and double as a typed read model (msg.get('Symbol')). The engine façade mirrors it: createFixEngine<MessageBodies>(dictionary).create(msgType). Venue-custom tags need no regenerated dictionary — see Extending the dictionary and each package's README.
Where the concrete type is erased — a generic send(message: MessageView<any>), a log-metadata helper — message.msgType === 'W' cannot narrow the body, so each dictionary package also ships isMessageType, a guard keyed on the MsgType value (and a MessageOf<M> alias for annotations), plus isInboundType/InboundOf<M>, the same pair for a message that arrived rather than one you built. Inside it, reads are typed to that message with no casts; the runtime is a plain string compare, the typing comes from the generated body registry. The engine binds the same guard as createFixEngine<MessageBodies>(dictionary).is.
import { isMessageType, MsgType } from '@boarteam/fix-dict-fix44';
if (isMessageType(message, MsgType.MarketDataSnapshotFullRefresh)) {
const securityID = message.get('SecurityID'); // string | undefined
for (const entry of message.get('NoMDEntries') ?? []) {
entry.MDEntryPx; // MDFullGrp_NoMDEntriesEntry — entry fields typed
}
}→ Full guide: Typed messages.
The same per-message types work on the way in. parse above returns the wire faithfully but tag-keyed (message.fields[262].value, groups under message.groups[268]) — the right shape for a codec, the wrong one for application code, which ends up hand-writing a switch (msgType) plus a tag-to-field mapper per message. toInbound re-keys it by dictionary name, splits the standard header/trailer into a typed envelope, and hands back a read model that narrows per MsgType.
import { inboundKnownGuard, loadDictionary, parse, toInbound } from '@boarteam/fix';
import { dictionary as fix44, MsgType, type MessageBodies } from '@boarteam/fix-dict-fix44';
// The dict packages ship the dictionary as data; `parse` wants it indexed.
const dictionary = loadDictionary(fix44);
const isKnownInbound = inboundKnownGuard<MessageBodies>(dictionary);
const { message, issues } = parse(raw, dictionary); // gate on `issues` first
const inbound = toInbound(message, dictionary);
inbound.envelope.MsgSeqNum; // session envelope, before narrowing
if (isKnownInbound(inbound)) {
switch (
inbound.msgType // narrows per case
) {
case MsgType.Logon:
inbound.get('HeartBtInt'); // typed to LogonBody
break;
case MsgType.MarketDataSnapshotFullRefresh:
inbound.get('NoMDEntries')?.[0]?.MDEntryPx; // the typed entry array
break;
default:
break; // known, but not handled here
}
}Repeating groups are arrays of entry objects under their counter's name — the declared counter is not a property, because the entry array is the count. Header/trailer fields land on envelope, matching the body types, which exclude them by construction. The isKnownInbound guard is not ceremony: an unrecognised MsgType cannot be a member of the message union (a msgType: string member overlaps every literal, so no case eliminates it and its loose get makes every branch uncallable), and it does not parse like one either — it is read flat, with no groups reconstructed. inboundTypeGuard is the single-MsgType if form — each dictionary package ships it ready-bound as isInboundType, so binding it yourself is only necessary for a dictionary you extended. Nothing is re-parsed: toInbound is a pure re-keying, and the original ParsedMessage stays reachable as inbound.parsed for byte-faithful re-encoding. The engine façade binds .inbound(), .isInbound and .isKnown. Runnable: examples/read-a-message.mjs.
→ Full guide: Inbound messages.
Most captured logs render the SOH separator as |. Pass it through:
const { message, issues } = fix.parse(pipeDelimitedLine, { soh: '|' });parse options: soh (default the SOH byte 0x01; pass "|" to read pipe-delimited logs) and checkFraming (default true; framing findings come back as issues, never a rejection).
Real venues extend FIX beyond the spec — cTrader, for example, sends SymbolName(1007) and
SymbolDigits(1008) inside the NoRelatedSym repeating group of SecurityList (35=y).
Unknown tags inside a group break group reconstruction, and encode only emits fields the
dictionary places. extendDictionary fixes both from one declaration:
import { defineExtension, extendDictionary, createFixEngine } from '@boarteam/fix';
import { dictionary as fix44 } from '@boarteam/fix-dict-fix44';
const ctrader = defineExtension({
id: 'ctrader',
fields: {
SymbolName: { tag: 1007, type: 'String' },
SymbolDigits: { tag: 1008, type: 'int' },
},
messages: {
SecurityList: { groups: { NoRelatedSym: { append: ['SymbolName', 'SymbolDigits'] } } },
},
});
const { dictionary, issues } = extendDictionary(fix44, ctrader); // never throws — issues are data
const fix = createFixEngine(dictionary);
// SecurityList now parses with 1007/1008 nested per instrument, and encode round-trips them.An extension can add fields, enum values on existing fields, components, whole
messages (the standard header/trailer are injected for you), and place members into message
bodies or repeating groups — after: 'Instrument' anchors a wire position, dotted paths
('NoRelatedSym.NoUnderlyings') reach nested groups. Placements are append-only by design: a
group's first field is its entry delimiter, and extendDictionary reverts any operation that
would shift one (extend/group-delimiter-shift), skips duplicates, and rejects placements that
could re-parse into the wrong group (extend/ambiguous-boundary). Everything it does or refuses
to do is reported through stable extend/* issue codes — error means skipped or reverted, so
if the base passed validateDictionary and the result has no error-severity issues, the result
passes too.
The same declaration drives the typed maps — no duplication, full literal typing (TS ≥ 5.0):
import { extendTags, invertTags, tagsOf } from '@boarteam/fix';
import { Tags as Fix44Tags } from '@boarteam/fix-dict-fix44';
export const Tags = extendTags(Fix44Tags, tagsOf(ctrader)); // Tags.SymbolName hovers as 1007
export type TagName = keyof typeof Tags; // includes 'SymbolName'
export const TagNames = invertTags(Tags); // TagNames[1007] hovers as 'SymbolName'For a reusable dialect, put the declaration and the derived exports in one module mirroring a
dict package's surface (dictionary, Tags, TagNames, MsgType, MsgTypeNames) — see the
runnable examples/extend-dictionary.mjs. Two things keep such
a module honest: fail fast on error-severity issues at module init, and — if the module ships as
a package that emits .d.ts — annotate the exports with the provided alias types (ExtendTags,
InvertTags, ExtendMsgTypes, InvertMsgTypes) so the declaration emit stays small and nominal
instead of structurally inlining the 900-key base map. The same pattern scales to publishable
venue packages (e.g. a future @boarteam/fix-dict-fix44-ctrader).
→ Full guide: Extending a dictionary.
If you do not want the engine wrapper, the same capabilities are exported as free functions: parse, parseAll, encode, validate, tokenize, splitMessages, decodeValue, calculateChecksum, bodyLength, loadDictionary, Dictionary, validateDictionary, the typed-message helpers (messageFactory, messageTypeGuard, createMessage, createImmutableMessage, toInbound, inboundTypeGuard, inboundKnownGuard), and the dictionary-extension helpers (extendDictionary, defineExtension, tagsOf, msgTypesOf, extendTags, invertTags, extendMsgTypes, invertMsgTypes).
Every one of them is documented, signature by signature, in the API reference.
ParsedMessage—{ msgType, name?, beginString?, framed, fields, groups }.ParsedField—{ tag, name?, raw, value }.rawis the verbatim wire value (the source of truth for re-encoding);valueis the typed value.ParsedGroupEntry—{ fields, groups }. Repeating groups are arrays of nested objects, addressed by their counter tag (e.g.message.groups[268]), not parallel arrays.FixIssue—{ code, severity, message, path?, refTagID?, refSeqNum?, refMsgType?, sessionRejectReason? }.
For a library that sits in a trading firm's toolchain, the dependency tree is the attack surface and the audit burden. @boarteam/fix has no runtime dependencies — nothing to vet transitively, nothing to phone home, nothing that breaks the browser build. A CI bundle check enforces this on every commit, so the zero-dependency, browser-safe surface cannot silently regress.
The dictionaries ship as data, generated by the separate @boarteam/fix-codegen generator from permissively-licensed sources. The counts below are read directly from the shipped dictionary files, and each dialect is browsable field-by-field on the FIX reference — the coverage page renders the declared gaps below from the same shipped data.
| Fields | Messages | Components | Datatypes | Inline repeating groups |
|---|---|---|---|---|
| 912 | 93 | 105 | 23 | 92 |
Generated directly from the QuickFIX FIX.4.4 XML data dictionary. Because the 4.4 structure is generated from QuickFIX, we do not claim it is "cross-checked against QuickFIX" — that would be circular. There are 36 recorded coverage gaps, all of one kind: conditional-overlay-unmatched — conditional-required rules from the facts overlay that did not match a member in the QuickFIX-sourced structure. They are recorded as data in the dictionary, naming exactly what they touch; they are not parsing failures and do not affect the market-data or session message sets.
| Fields | Messages | Components | Datatypes |
|---|---|---|---|
| 405 | 46 | 2 | 21 |
Generated from the FIX Repository 2010 Edition and genuinely cross-checked against the QuickFIX FIX42.xml dictionary by a CI drift gate. There are 2 recorded coverage gaps, both synthesized-datatype: MultipleValueString (synthesized over base String) and Length (synthesized over base int) are each referenced by fields but absent from Datatypes.xml.
| Fields | Messages | Components | Datatypes | Inline repeating groups |
|---|---|---|---|---|
| 1,452 | 115 | 176 | 28 | 147 |
The self-contained FIX 5.0 SP2 dictionary: the FIXT.1.1 session envelope + the 7 session messages + the 108 base-SP2 application messages, with version: 'FIX.5.0SP2', beginString: 'FIXT.1.1', and applVerID: '9' modelling the transport/application split FIX 5.0 introduced. Generated from the QuickFIX/J FIX50SP2.xml + FIXT11.xml data dictionaries (base SP2 — deliberately not the quickfix C++ copy, which was regenerated at EP280) and cross-checked against quickfix-go's independently-maintained FIX50SP2.xml by a CI drift gate with a reviewed EP-drift baseline. Descriptions come from FIX Orchestra (EP307 application layer + EP247 session layer; 99.9% of fields, 99.7% of enum values — the remainder is recorded in coverageGaps). Golden FIXT frames encoded by this engine are parsed and validated clean by quickfix-go's transport/application validator (cross-engine check). Known limitations: XMLnonFIX(n) is not shipped (QuickFIX/J master comments it out; quickfix-go omits it), and like FIX 4.2 there are no conditional-required (C) markings.
| Fields | Messages | Components | Datatypes |
|---|---|---|---|
| 74 | 7 | 4 | 9 |
The transport-only dictionary: the FIXT session envelope (a strict superset of the FIX 4.4 header — adds ApplVerID(1128), CstmApplVerID(1129), ApplExtID(1156), HopGrp) and the session messages, with Logon requiring DefaultApplVerID(1137) — in the data and in the generated LogonBody type. Pair it with an application dictionary (createFixEngine({ transport, app })) for layer-attributed validation: every finding is tagged session or application, so you know whether to answer with a session Reject(3) or a BusinessMessageReject(j).
Field, enum, and datatype descriptions are sourced from the Apache-2.0 FIX Orchestra files.
@boarteam/fix is an analyzer, deliberately not a session/transport engine. It does not — and will not — open sockets, manage sequence numbers, send heartbeats, handle logon/logout, or persist session state. Those belong to a session engine (QuickFIX, QuickFIX/J, jspurefix, and friends), and we cede that lane to them on purpose.
What it owns instead: decoding and validating messages you already have, encoding messages you construct, and doing all of it in a browser tab or a CI step. If your job is a log viewer, a web decode dashboard, a test harness, or a CI assertion, this is built for you. If your job is to be the FIX connection on the wire, reach for a session engine and let this analyze its traffic.
| Package | Version | What it is |
|---|---|---|
@boarteam/fix |
0.4.0 | The engine: tokenize / parse / validate / encode + the dictionary runtime + the FIXT pair API. |
@boarteam/fix-dict-fix44 |
3.0.0 | Full FIX 4.4 dictionary as data. Peer-depends on @boarteam/fix. |
@boarteam/fix-dict-fix42 |
3.0.0 | Full FIX 4.2 dictionary as data. Peer-depends on @boarteam/fix. |
@boarteam/fix-dict-fix50sp2 |
1.0.0 | FIX 5.0 SP2 over FIXT.1.1, self-contained (envelope + session + app messages). |
@boarteam/fix-dict-fixt11 |
1.0.0 | FIXT.1.1 transport-only dictionary (envelope + session messages), for the pair API. |
All packages: zero runtime dependencies, dual ESM + CJS, Node ≥ 18, browser + Node, Apache-2.0. The dictionary packages also export dictionary, Tags (name → tag), MsgType (name → msgtype), and DICTIONARY_VERSION (e.g. "FIX.4.4", "FIX.5.0SP2").
Which dictionary to pick, and how the FIXT pair fits together: Dictionaries guide. To browse the data itself before installing anything, each dialect has a reference view — FIX 4.4, FIX 4.2, FIX 5.0 SP2 — and there is a 4.4 vs 4.2 diff.
This is pre-1.0 software, and we are explicit about what that means rather than hiding it — because you have been burned by abandoned free FIX parsers before.
The SemVer contract covers three things:
- the output shape —
ParsedMessage,FixIssue, and the dictionary JSON contract; - the accepted input to
parse/encode/validate; - the set and meaning of issue codes (the stable
codestrings, e.g.parse/checksum-mismatch,validate/value-not-in-enum). The human-readablemessageis not part of the contract.
While on 0.x, a breaking change to any of those ships as a minor bump (0.x → 0.(x+1)); additive changes and fixes are patch bumps. Pin a version, and the issue codes you assert against will not silently change underneath you.
Maintenance pledge. A monthly release / maintenance cadence. Consistency is the whole point — the prior generation of free JS FIX parsers died from neglect, and that is exactly the failure mode this project is built to avoid. Issues and feedback shape the road to 1.0.
Roadmap. FIX 5.0 / FIXT.1.1 dictionaries through the same pipeline — shipped: @boarteam/fix-dict-fix50sp2 + @boarteam/fix-dict-fixt11, generated and cross-checked like the 4.x dicts, with the transport/application pair API in the engine. Next: a CLI and first-class FIX Orchestra support.
This is a pnpm monorepo. Tests run on Node 18 / 20 / 22 plus a browser-like environment.
pnpm install
pnpm build # build all packages (tsup -> ESM + CJS + d.ts)
pnpm test # vitest: golden fixtures, round-trip, adversarial suite
pnpm typecheck # tsc --noEmit across packages
pnpm check:bundle # enforce the zero-dependency, browser-safe surface
pnpm examples # run the runnable examplesThe dictionary data is produced by the separate @boarteam/fix-codegen generator from QuickFIX / FIX Repository structure and Apache-2.0 FIX Orchestra descriptions.
Contributions are welcome — see CONTRIBUTING.md for the development workflow, the DCO sign-off requirement, and how the FIX 4.2 cross-check drift gate works. Please also read our CODE_OF_CONDUCT.md. Security reports go through SECURITY.md. When changing parsing, validation, or encoding behavior, add or update a golden fixture so the change is provable.
Apache-2.0 © Boar Team.
Dictionary structure is derived from QuickFIX (QuickFIX Software License) and the FIX Repository (both permissively licensed); field, enum, and datatype descriptions come from the Apache-2.0 FIX Orchestra sources. Full attribution lives in NOTICE and THIRD-PARTY-NOTICES.txt. This project carries only permissively-licensed data and does not include CC BY-ND FIX-specification prose.
FIX is a trademark of FIX Protocol Limited; this is an independent project, not affiliated with or endorsed by FIX Protocol Limited.

{ "msgType": "W", "name": "MarketDataSnapshotFullRefresh", "framed": true, "fields": { "55": { "tag": 55, "name": "Symbol", "raw": "EUR/USD", "value": "EUR/USD" }, }, "groups": { "268": [ // MDEntryType 0 = Bid; price + size come back as typed numbers, not strings { "fields": { "269": { "name": "MDEntryType", "value": "0" }, "270": { "name": "MDEntryPx", "value": 1.0921 }, "271": { "name": "MDEntrySize", "value": 1000000 }, }, }, // MDEntryType 1 = Offer { "fields": { "269": { "name": "MDEntryType", "value": "1" }, "270": { "name": "MDEntryPx", "value": 1.0923 }, "271": { "name": "MDEntrySize", "value": 1000000 }, }, }, ], }, }