v2.6.0

<bmx-data-mapper>

Draw how data in one shape becomes data in another: a shop's order into your invoice, a CRM export into your customer table, a partner's API into the message yours accepts. Source fields on the left, target fields on the right, a line for every mapping between them.

7 properties · 2 events · 13 methods · 7 parts

Example

Drag from a source field to a target field, or pick one with Space and press Enter on a target. The Preview tab shows the invoice each order becomes.

Show markup
<bmx-data-mapper id="ex-dm" label="Shop order to invoice"></bmx-data-mapper>
<p class="note" id="ex-dm-out" role="status" style="margin: 12px 0 0">Drag from a source field to a target field, or pick one with Space and press Enter on a target. The Preview tab shows the invoice each order becomes.</p>

<script type="module">
  await customElements.whenDefined('bmx-data-mapper');
  const mapper = document.getElementById('ex-dm');
  const out = document.getElementById('ex-dm-out');
  const orders = [
    { id: 'SO-1001', placed: '2026-10-09', buyer: { first: 'ada', last: 'LOVELACE', mail: 'Ada@Example.com', phone: '+44 20 7946 0000' }, ship: { country: 'United Kingdom', city: 'London', zip: 'EC1A 1BB' },
      items: [{ code: 'BMX-PRO', qty: 2, price: 1174 }, { code: 'BMX-RENEW', qty: 0, price: 499 }, { code: 'BMX-GOLD', qty: 1, price: 1404 }] },
    { id: 'SO-1002', placed: '2026-10-10', buyer: { first: 'grace', last: 'hopper', mail: 'grace@navy.example', phone: null }, ship: { country: 'France', city: 'Paris', zip: '75001' },
      items: [{ code: 'BMX-WEB', qty: 1, price: 399 }] },
  ];
  const invoice = {
    type: 'object',
    required: ['invoiceNo', 'customer', 'lines'],
    properties: {
      invoiceNo: { type: 'string' },
      issued: { type: 'string', format: 'date' },
      due: { type: 'string', format: 'date', description: 'Thirty days after the order' },
      customer: { type: 'object', required: ['name'], properties: { name: { type: 'string' }, email: { type: 'string' }, telephone: { type: 'string' }, country: { type: 'string', description: 'ISO 3166 code' }, postcode: { type: 'string' } } },
      lines: { type: 'array', items: { type: 'object', required: ['sku'], properties: { sku: { type: 'string' }, quantity: { type: 'integer' }, unitPrice: { type: 'number' }, amount: { type: 'number' } } } },
      total: { type: 'number' },
    },
  };
  await mapper.setSourceFromSample(orders);
  await mapper.setTargetFromSchema(invoice);
  mapper.sample = orders;
  const map = await mapper.getMap();
  await mapper.setMap({
    ...map,
    name: 'Shop order to invoice',
    source: { ...map.source, name: 'Shop order' },
    target: { ...map.target, name: 'Invoice' },
    arrays: [{ target: 'lines', source: 'items', filter: '=items.qty > 0' }],
    lookups: { countries: { entries: [['United Kingdom', 'GB'], ['France', 'FR'], ['Germany', 'DE']] } },
    mappings: [
      { target: 'invoiceNo', expression: '="INV-" & MID(id, 4, 10)' },
      { target: 'issued', source: 'placed' },
      { target: 'due', expression: '=placed + 30' },
      { target: 'customer.name', expression: '=PROPER(buyer.first & " " & buyer.last)' },
      { target: 'customer.country', source: 'ship.country', lookup: 'countries' },
      { target: 'lines[].amount', expression: '=items.qty * items.price' },
      { target: 'total', expression: '=SUM(items.qty * items.price)' },
    ],
  });
  await mapper.autoMap();
  await mapper.selectTarget('customer.name');
  mapper.addEventListener('bmxChange', event => {
    const errors = event.detail.problems.filter(p => p.severity === 'error').length;
    out.textContent = errors ? `${errors} problem(s) to fix before this map can run.` : 'The map is complete.';
  });
</script>
<bmx-data-mapper label="Order to invoice"></bmx-data-mapper>
<script type="module">
  const mapper = document.querySelector('bmx-data-mapper');
  await mapper.setSourceFromSample(orders);          // or setSourceFromSchema(jsonSchema)
  await mapper.setTargetFromSchema(invoiceSchema);
  mapper.sample = orders;                             // for the live preview
  await mapper.autoMap();                             // matches fields by name
  mapper.addEventListener('bmxChange', e => save(e.detail.map));
</script>

Mapping: drag from a source field to a target field, or pick a source field with Space and press Enter on a target field. A target field can instead be worked out with a formula - the library's spreadsheet formulas, with the source fields as names (PROPER(buyer.first & " " & buyer.last), `SUM(items.qty

  • items.price)`) - passed through a lookup table, and given a default. A target list is made from a source list, one item per item, optionally filtered by a formula; inside it the source item's fields are in reach. Auto-map matches the rest by name and common synonyms (email and mail, postcode and zip).

Checking: unmapped required fields, fields out of reach, formulas that do not read or name unknown fields, risky type changes - listed, marked on the rows, and each a click away. The preview runs the map over the sample records as you edit, showing the result and, for each record, what could not be done.

The map is plain JSON (map, and bmxChange): keep it where you keep things. Run it anywhere with dist/node/bmx-data-mapper.mjs - in a bundle, a Web Worker or Node - or with run() here. Nothing is sent anywhere.

Properties

PropertyAttributeTypeDefaultDescription
autoMapThreshold auto-map-threshold number 0.6 How alike names must be (0 to 1) for auto-map to match them.
hideLabel hide-label boolean false Hide the label visually while keeping it as the accessible name.
label label string — The name of the element for assistive technology, and its visible label.
map map BmxDataMap | string { source: { fields: [] }, target: { fields: [] }, mappings: [] } The map: source and target shapes, mappings, list mappings and lookup tables (object or JSON). Two-way.
readonly readonly boolean false Show the map without letting it change.
sample property only unknown — Sample source data for the preview: one record, or an array of records (or JSON).
strings strings Partial<BmxDataMapperStrings> | string — Words to show instead of the English ones, as an object or JSON.

Events

EventDetailDescription
bmxChange BmxDataMapperChangeDetail Fired after the map changes.
bmxSelect { readonly target: string; readonly mapping?: BmxMapperMapping | undefined; } Fired when a target field is selected.

Methods

MethodSignatureDescription
autoMap autoMap() => Promise<BmxMapperSuggestion[]> Maps unmapped target fields by name. Returns what was added.
check check() => Promise<BmxMapperProblem[]> The map's problems.
getMap getMap() => Promise<BmxDataMap> The map as it is now.
redo redo() => Promise<boolean>
run run(record?: unknown) => Promise<BmxMapperResult> Runs the map over one record (default: the current sample record).
runAll runAll(records?: unknown[]) => Promise<{ output: Record<string, unknown>[]; issues: BmxMapperIssue[]; }> Runs the map over many records (default: the whole sample).
selectTarget selectTarget(path: string) => Promise<boolean> Selects a target field and shows its mapping.
setMap setMap(map: BmxDataMap | string, options?: { keepHistory?: boolean; }) => Promise<void> Replaces the map. keepHistory lets the reader undo back to the previous one.
setSourceFromSample setSourceFromSample(sample: unknown) => Promise<void> Sets the source fields from sample data (one record or several), keeping mappings that still fit.
setSourceFromSchema setSourceFromSchema(schema: unknown) => Promise<void> Sets the source fields from a JSON Schema.
setTargetFromSample setTargetFromSample(sample: unknown) => Promise<void> Sets the target fields from sample data.
setTargetFromSchema setTargetFromSchema(schema: unknown) => Promise<void> Sets the target fields from a JSON Schema.
undo undo() => Promise<boolean>

Slots

SlotDescription
label Rich label content, in place of the label property.

CSS shadow parts

PartDescription
inspector The tabs under the fields.
label The label.
row A field row; also row-selected, row-mapped, row-problem, row-picked.
source The source fields' panel.
target The target fields' panel.
toolbar The bar above the fields.
wires The connector lines.

CSS custom properties

PropertyDescription
--bmx-data-mapper-gutter The space between the trees where the lines run.
--bmx-data-mapper-height The height of the field trees.
--bmx-data-mapper-indent How far each level is indented.
--bmx-data-mapper-radius Corner radius of the frame.
--bmx-data-mapper-row-height One field row.
--bmx-data-mapper-wire A mapping's line.
--bmx-data-mapper-wire-array A list-to-list line.
--bmx-data-mapper-wire-formula A line into a formula.