Operations

Importing Items

Load a catalog from a spreadsheet — the nine columns, how updates work, and why opening stock is a separate job.

Typing a few hundred items one modal at a time is not a plan. The Items screen imports a catalog from a spreadsheet, creating and updating in the same pass, and it tells you exactly which rows it refused and why.

Items → Import: the nine-column template, the location picker the import requires, and the staged preview before anything commits.

Starting an import

Navigate to: Operations → Items → Import (in the table toolbar)

You can also land here directly with ?action=import on the Items URL — useful for a link in a runbook.

1
Download the sample template

The modal offers a template with the exact nine columns in the exact order the parser expects. Start from it rather than building a sheet from memory.

2
Choose the locations

The import declares that it needs locations before it will stage anything. Every imported item is set up at each location you select. This is required — the import cannot proceed without at least one.

3
Upload the file

CSV or Excel. The first row is the header.

4
Review the staged preview

Nothing is created yet. The preview shows what will be created, what will be updated, and what will be skipped or refused.

5
Commit

The result is reported as a count — created, updated, skipped, failed — with the first few reasons named for anything that did not go through.

The nine columns

Column order matters — the parser reads by position, not by header text.

Item import — the template columns, in order
#ColumnRequired?Details
1NameYesRows with no name are dropped silently before the file is even staged.
2TypeNoProduct, Material, Service, or Expense. Common spellings are normalised. Left blank on an update row, the existing type is kept.
3SKUNoThe item's identity for matching. Two items can share a name; they cannot share a SKU.
4CategoryNoMatched by category name.
5Selling PriceNoThe default selling rate.
6Cost PriceNoThe default cost.
7DescriptionNoFree text.
8Is UpdateNoPresent in the template and accepted by the parser. See the note below — the import decides create-vs-update by identity, not by this flag.
9BarcodeNoLeave blank and the server generates an EAN-13. Supply one and it is stored exactly as typed, so a scanner already in use keeps matching.
⚠️Format the Barcode column as Text before you paste

Spreadsheets turn a 13-digit number into scientific notation the moment it lands in a General cell — 5901234123457 becomes 5.90123E+12, and that is what gets imported. Set the column to Text first.

How the import decides create vs. update

The import never creates a duplicate. Every row is matched against your existing catalog in a fixed order:

1
SKU

If the row has a SKU and an item already carries it, that is the item. If the SKU is new but the row's Barcode belongs to a different item, the row is refused — a barcode cannot be moved onto a second item by accident.

2
Barcode

No SKU on the row? Match on Barcode.

3
Name

No SKU and no barcode? Match on name. If two items already share that name the row is refused with Multiple items are named 'X' — add a SKU or Barcode, because the import will not guess.

A row that matches an existing item does not rewrite that item's master data. Its name, type, category, and accounts are left exactly as they are. What the row does is set that item's price at the locations you selected — so a matched row is the mechanism for rolling out prices, not for renaming things.

A row that matches nothing creates a new item, at every location you picked, with the type, category, prices, and barcode from the row. A category named in the file that does not exist yet is created for you.

⚠️The Is Update column does not change what happens

The column is in the template and the parser accepts it, but the import path in use resolves create-vs-update purely from the identity chain above. Setting it to TRUE changes nothing; leaving it blank does not cause a duplicate. Treat it as a leftover.

ℹ️To change master data in bulk, that is a different job

Because a matched row only moves prices, this import cannot be used to re-type items, re-map their ledger accounts, or rename them en masse. Those changes are made on the item itself.

Opening stock is deliberately not part of this

There is no quantity column, and that is on purpose. Item import creates master data — the catalog entry, its type, its price, its accounts. It posts no journal and moves no stock.

Bringing existing stock onto the books is a separate, ledger-posting operation with its own wizard, because it debits your inventory account and credits opening balance. Doing it as a side effect of a catalog upload would put an accounting entry inside what looks like a spreadsheet paste. See Opening stock.

What "skipped" means

The result line distinguishes four outcomes, and it is worth reading rather than dismissing:

Import outcomes
OutcomeMeaning
CreatedA new item now exists, priced at every location you selected.
UpdatedThe row matched an existing item and changed its price at one of the selected locations.
SkippedThe row matched an existing item and changed nothing — the price was already what the file says. The first few reasons are named in the message.
FailedThe row was rejected. The reason is named.
Rejected before stagingThe whole file failed validation — the message names the line numbers and the reason. Nothing was created.
ℹ️Imports are reversible — until the items are used

Every import is staged as a batch and kept on record, so a wrong file can be reversed rather than unpicked item by item. An item that has already been put to work is excluded from the reversal: if it appears on a document, on a production batch's inputs or outputs, on a stock transfer, or if it has been edited since the import, it stays. The reversal names each one rather than silently leaving it behind.