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.
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.
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.
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.
CSV or Excel. The first row is the header.
Nothing is created yet. The preview shows what will be created, what will be updated, and what will be skipped or refused.
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.
| # | Column | Required? | Details |
|---|---|---|---|
| 1 | Name | Yes | Rows with no name are dropped silently before the file is even staged. |
| 2 | Type | No | Product, Material, Service, or Expense. Common spellings are normalised. Left blank on an update row, the existing type is kept. |
| 3 | SKU | No | The item's identity for matching. Two items can share a name; they cannot share a SKU. |
| 4 | Category | No | Matched by category name. |
| 5 | Selling Price | No | The default selling rate. |
| 6 | Cost Price | No | The default cost. |
| 7 | Description | No | Free text. |
| 8 | Is Update | No | Present in the template and accepted by the parser. See the note below — the import decides create-vs-update by identity, not by this flag. |
| 9 | Barcode | No | Leave 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. |
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:
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.
No SKU on the row? Match on Barcode.
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 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.
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:
| Outcome | Meaning |
|---|---|
| Created | A new item now exists, priced at every location you selected. |
| Updated | The row matched an existing item and changed its price at one of the selected locations. |
| Skipped | The 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. |
| Failed | The row was rejected. The reason is named. |
| Rejected before staging | The whole file failed validation — the message names the line numbers and the reason. Nothing was created. |
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.
Related
- Creating an item — the same fields, one at a time
- Opening stock — bringing existing quantities onto the books
- Item categories — the Category column's target
- Barcodes and labels — what the Barcode column feeds
- Pricing an item — per-location prices, which the import does not set