<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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
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
| Event | Detail | Description |
|---|---|---|
bmxChange |
BmxDataMapperChangeDetail |
Fired after the map changes. |
bmxSelect |
{ readonly target: string; readonly mapping?: BmxMapperMapping | undefined; } |
Fired when a target field is selected. |
Methods
| Method | Signature | Description |
|---|---|---|
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
| Slot | Description |
|---|---|
label |
Rich label content, in place of the label property. |
CSS shadow parts
| Part | Description |
|---|---|
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
| Property | Description |
|---|---|
--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. |