> ## Documentation Index
> Fetch the complete documentation index at: https://docs.variable.global/llms.txt
> Use this file to discover all available pages before exploring further.

# Import behavior by type

> What each import type creates, how it matches existing records, and when a re-import creates new data vs. reuses what you already have

Every import type follows its own rules for two questions:

1. **What does it create?** - which records appear in your workspace.
2. **When does it create a new record vs. reuse an existing one?** - how a row is matched against data you already have, and what happens when it matches.

Knowing these rules lets you re-run an import without guessing whether you'll end up with duplicates.

## At a glance

| Import type                                      | What it creates                                                                    | Matched against existing by                                     | Re-importing a matching row                                         |
| :----------------------------------------------- | :--------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :------------------------------------------------------------------ |
| **Products**                                     | A product owned by your company                                                    | SKU, within your company                                        | Match mode decides: reused (default), overwritten, or blanks filled |
| **Materials**                                    | A source product, plus a supplier when supplier columns are present                | SKU, within the owning supplier                                 | Match mode decides: blanks filled (default), reused, or overwritten |
| **Bill of materials (BOM)**                      | Inputs on the target product, plus the supplier parts and materials they reference | Products by SKU + ownership; inputs by source + quantity + unit | Products reused; inputs added only to unmodeled products            |
| **Suppliers**                                    | A supplier company, plus contacts when contact columns are present                 | UUID, then your internal ID, then name                          | Reused, never overwritten; new contacts merged in                   |
| **Locations**                                    | A named location, optionally geocoded                                              | Location name, within the owning company                        | Reused; blank fields filled in, set values kept                     |
| **Activity data**, **Air travel**, **Transport** | One activity record per row                                                        | Not matched                                                     | **A new record every time**                                         |

<Warning>
  **Products, materials, suppliers, locations, and BOMs are safe to re-run** - matching records are reused or updated in place, so you won't create duplicates. **Activity, air travel, and transport imports are not** - every row becomes a new record each time you import the file. See [Activity data, air travel, and transport](#activity-data-air-travel-and-transport) below.
</Warning>

<Info>
  The sample rows below use Variable's canonical column names. Your file's headers don't need to match - the [column mapping step](/docs/import/upload-map-columns) lets you map any header to the right field.
</Info>

## Products

Creates a **product owned by your company** for each row.

* **Matched by:** SKU, scoped to your company. If a row has no SKU, the product name is used instead.
* **On a match:** the [import match mode](#import-match-mode-products-and-materials) decides. By default (**Create new only**) the existing product is reused and left untouched. Choose **Overwrite existing** or **Fill missing only** to update its scalar fields - weight, codes, description, notes - from your file; its links (supplier, datasets, model inputs) are never changed.
* **Created new when:** no product matches - by SKU, or by name when the row has no SKU. A SKU-less row gets a SKU generated from its name on creation.

**Sample rows:**

```csv theme={"system"}
name,sku,unitCode,weightQuantity,weightUnitCode
Mountain Bike,BIKE-001,item,15.2,kg
Road Bike,BIKE-002,item,8.4,kg
```

Alongside `weightQuantity`/`weightUnitCode`, product rows can carry `lengthQuantity`/`lengthUnitCode`, `volumeQuantity`/`volumeUnitCode`, and `areaQuantity`/`areaUnitCode`. Map a quantity column without its unit and Variable assumes the SI base unit (m, m3, m2); leave a dimension blank and it stays unset.

## Materials

Creates a **source product** (a material or supplier part) for each row, and a **supplier company** when supplier columns are present.

* **Matched by:** SKU, scoped to the owning supplier. The supplier is matched first by UUID, then by your internal supplier ID, then by supplier name. Name is the fallback when a row has no SKU.
* **On a match:** the [import match mode](#import-match-mode-products-and-materials) decides. By default (**Fill missing only**) blank scalar fields on the existing material are filled in from your file, while values you've already set are kept. Choose **Create new only** to leave matched materials untouched, or **Overwrite existing** to replace their values; the supplier link and any datasets or inputs are never changed in any mode.
* **Created new when:** no material matches - by SKU, or by name when the row has no SKU. A SKU-less row gets a SKU generated from its name on creation.

**Sample rows:**

```csv theme={"system"}
name,sku,supplier,supplierId,unitCode,weightQuantity,weightUnitCode
Aluminum Alloy 6061,ALU-6061,RimSupply,SUP-001,kg,1,kg
Natural Rubber,RUB-NAT,TireCo,SUP-002,kg,1,kg
```

Alongside `weightQuantity`/`weightUnitCode`, material rows can carry `lengthQuantity`/`lengthUnitCode`, `volumeQuantity`/`volumeUnitCode`, and `areaQuantity`/`areaUnitCode`. Map a quantity column without its unit and Variable assumes the SI base unit (m, m3, m2); leave a dimension blank and it stays unset.

## Bill of materials (BOM)

Creates **inputs** (line items) on the product you're importing into, plus any **supplier parts** and **materials** the rows reference. See [What happens after upload](/docs/import/import-materials) for the full 3-level structure.

* **Matched by:** products are matched by SKU and ownership; individual inputs are matched by their source product, quantity, and unit.
* **On a match:** products are reused and left untouched. Inputs are only added to a product that was **created during this import**, is still **UNKNOWN**, or has **no inputs yet**. A product that's already LIVE with inputs is treated as fully modeled and keeps its existing inputs.
* **Created new when:** the product or input isn't already present under those rules.

**Sample rows** (a root product on level 0, with a supplier part and its material on level 1):

```csv theme={"system"}
bomLevel,productSku,productName,quantity,unitCode,supplierName,supplierPartId,materialName,weightAmount,weightUnitCode
0,BIKE-001,Mountain Bike,1,item,,,,,
1,RIM-001,Alloy Rim,2,item,RimSupply,RS-RIM-42,Aluminum Alloy 6061,0.3,kg
```

<Note>
  If a re-import isn't adding inputs to a product, the product is most likely already LIVE with a model. See [Import troubleshooting](/docs/import/import-troubleshooting#input-deduplication) for how to add to it.
</Note>

## Suppliers

Creates a **supplier company** for each row, plus **contacts** when contact columns (name or email) are present.

* **Matched by:** UUID, then your internal supplier ID, then supplier name.
* **On a match:** the existing supplier is reused and left untouched. A contact is added only when no contact with the same email or name is already on that supplier.
* **Created new when:** no supplier matches by ID or name.

**Sample rows:**

```csv theme={"system"}
name,orgNumber,internalId,contactName,contactEmail
RimSupply,123456789,SUP-001,John Doe,john@rimsupply.com
TireCo,987654321,SUP-002,Jane Smith,jane@tireco.com
```

## Locations

Creates a **named location** for each row, optionally geocoded to coordinates when address details are provided.

* **Matched by:** location name, within the owning company - the company named in the `companyId` column, or your own company for locations you hold directly.
* **On a match:** the existing location is reused. Address fields from your file **fill in blanks** but do **not** overwrite values that are already set.
* **Created new when:** no location with that name exists for that company.

**Sample rows:**

```csv theme={"system"}
name,address1,postCode,city,country,type
Bike Assembly Plant,Industriveien 12,3045,Drammen,Norway,factory
Oslo Parts Warehouse,Torggata 23,0183,Oslo,Norway,warehouse
```

## Activity data, air travel, and transport

These three types record **transactions and events** - a purchase, a sale, a flight, a shipment. Each row becomes one activity record. Air travel and transport are activity types with their own columns (flight details, logistics weight and distance, and so on).

* **Matched by:** nothing. Imported activity rows are not matched against existing activities.
* **Always created new:** every row produces a new record on every import.

**Sample rows - activity data:**

```csv theme={"system"}
name,direction,startDate,endDate,quantity,unitCode,productName,productSku
Aluminum tubing purchase Q1,Purchase,2026-01-15,2026-01-15,1200,kg,Aluminum Alloy 6061,ALU-6061
```

**Sample rows - air travel:**

```csv theme={"system"}
direction,name,startDate,flightRoute,flightCabinClass,quantity
Purchase,Supplier visit Oslo to Taipei,2026-02-03,OSL-TPE,Economy,8300
```

**Sample rows - transport:**

```csv theme={"system"}
direction,transportTypeName,startDate,endDate,name,logisticsWeightInKg,logisticsDistanceInKm
Purchase,Sea,2026-03-01,2026-03-20,Frame shipment Shanghai to Oslo,5000,18000
```

<Warning>
  Because activity rows aren't deduplicated, **re-importing the same activity file will create the records again.** Import each period's data once. To correct activities you've already imported, edit or delete them in the app or via the API rather than re-importing - see the [Activity API](/api-reference/v1/activity/patch).
</Warning>

## Import match mode (products and materials)

Product and material imports let you choose what happens to **matched** rows. A match-mode selector appears on the upload screen for these two import types. **The default depends on the type** - product imports start on **Create new only**, material imports on **Fill missing only** - but you can switch to any mode before you submit:

* **Create new only** (default for product imports) - matched rows are skipped, and only new items are added. This is the safe, no-surprises option, and the same way supplier and BOM imports already behave.
* **Overwrite existing** - the values in your file win. Each scalar field on the matched item is replaced with the imported value wherever your file provides one.
* **Fill missing only** (default for material imports) - only fields that are currently **empty** are filled in; values you've already set are kept. A stored `0` or an empty text value counts as set and is preserved - Variable treats *zero as a value and only "missing" as missing*.

Use overwrite or fill-missing to backfill enriched source data - weights, CPC/HS codes, descriptions, notes - onto items you created earlier, without re-keying them by hand.

<Note>
  **Scalar fields only.** Overwrite and fill-missing update an item's own fields - weight, SKU, codes, description, notes, and the like. They never touch its **relationships**: the supplier, assigned datasets, and bill-of-materials inputs on an existing item are left exactly as they are, in every mode. Updating those by import is a planned follow-up.
</Note>

Because matched rows are still deduplicated by SKU (or name), re-importing never creates duplicates regardless of mode - the only question is whether matched items are left alone, overwritten, or topped up.

### Undoing an update

Deleting an import doesn't just remove the items it created - it also **reverts the scalar updates** it made to pre-existing items. Overwritten fields are restored to their prior values, and fields it filled in are cleared again. The revert is guarded: any field you (or a later import) changed after the import is left exactly as it is, so a delete never clobbers newer edits. When some fields are skipped for that reason, the app tells you how many couldn't be reverted.

### Other types

Suppliers and BOMs always **reuse, not update** a matched record, so a re-import won't rewrite curated data. Locations are a partial exception - a re-import fills in address fields that were previously blank but won't overwrite values that are already set. Activity, air travel, and transport rows are never matched and always create a new record. To change one of these after import, edit it directly in the app or via the API.

## Next steps

* [Data import overview](/docs/import/import-overview) - the import workflow end to end
* [What happens after upload](/docs/import/import-materials) - the 3-level BOM structure
* [Import troubleshooting](/docs/import/import-troubleshooting) - duplicate handling and common errors
