Bulk upload

Import units, leases, residents, invoices, payments and more from spreadsheets — with validation, history and per-row outcomes.

Admin → Bulk Upload → /file-upload

Bulk upload imports data from a spreadsheet into the platform. It is how a portfolio gets onboarded, how a month of vendor invoices gets recorded, and how historical payments get migrated.

What you can import

TypeNotes
Utility ReadingsWater, electricity, gas and other meter readings.
PaymentsReceived payments, typically from a bank or mobile-money export.
ResidentsTenant and contact records.
InvoicesBills, including historical ones.
UnitsThe physical estate.
LeasesTenancies, with grouping and validation across related rows.
Recurring InvoicesStanding charges on leases.
Credit NotesAdjustments, for example reversing a mis-billed run.
ExpensesVendor bills.
Sales OpportunitiesPipeline records.
Promises to PayCollections commitments. Identified by unit name; the active lease and its tenant are resolved for you.

The wizard

  1. File information

    Choose what you are importing and download the sample template for that type. The sample matches the real column schema, so starting from it avoids most mapping problems.

  2. File upload

    Upload the spreadsheet. The file itself is retained, so what you actually submitted can be reviewed later.

  3. Data mapping

    Map your columns to the platform's fields. Mapping is remembered per type, so a recurring import gets faster.

    Where you map a date column, you are asked how those dates are written — year first, day first, month first, a month name, or an Excel date number. The question only appears where the dates genuinely read both ways: 13/07/2026 can only be day-first, so nothing is asked.

  4. Review & submit

    Validation runs before anything is written — on the server as well as in the browser, so a malformed file cannot slip past.

  5. Confirmation

    The result: how many rows were created, skipped and failed.

Dates the platform cannot read

The reading you choose is shown back to you against your own file, so you can check it before importing:

What you seeWhat it means
Reading 3 July 2026Settled. That is how your dates will be read.
"These dates could be read two ways…"Ambiguous. The reading shown is being used — change it if it is wrong.
"None of these read <your value>"Unreadable. Either it is the wrong column, or the dates need fixing in your file.

History

History lists every upload run — who ran it, when, which type, and the outcome (succeeded, failed, partial or still processing). Open a run to see:

  • four counters — total rows, created, skipped, failed;
  • per-row outcomes — created, updated, skipped or failed, each with a reason and, where a record was made, a link straight to it;
  • the original file, downloadable;
  • the option to retry the rows that did not land.

This is the difference between an import you can trust and one you cannot: after the fact, you can answer exactly what a run did to every row.

When an import stops part-way

Rows are written in small batches, so a run that is interrupted loses at most one batch rather than the whole file. If a run was left stranded, it is closed off automatically after a short grace period and marked as having unverified counts.

If the history itself cannot be read, you are told so and offered a retry. It will not show "no uploads yet" — that answer would invite you to re-upload a file that already landed.

Duplicates stop a row, not the run

A person the organisation already knows is refused — that refusal is correct. What changed is its blast radius: the duplicate now skips its own row and the import carries on. Previously the first already-known name in a sheet ended the entire run, so everybody listed behind them never got a chance, and no screen could say which row had stopped it. One property uploaded 63 residents over five attempts and created none: both duplicates sat in the first two rows.

One tenant, several units

A tenant renting more than one unit is listed once per unit, and those extra rows now add the unit to the person rather than being refused as a copy of somebody the same file created seconds earlier. Before this, a commercial sheet finished with shops that had no tenant on them, while the run called itself partial without naming a single one.

Other import rules worth knowing

  • A block is optional on a units sheet. Properties that do not use blocks are no longer forced to invent one.
  • Utility readings report a per-row reason for anything they did not bill, and the billing period is part of the duplicate check — so the same meter can be read in consecutive months without the second month looking like a repeat of the first.
  • You are asked how a date column is written rather than the importer guessing.

Importing from inside a module

The upload wizard is embeddable, so several modules launch their own import without sending you to /file-upload — for example importing expenses from the expenses list, or leases from the leases list. It is the same engine, the same validation and the same run history.

Getting an import right

  1. Start from the sample template

    Not from last year's spreadsheet.

  2. Import in dependency order

    Units before leases; residents before leases; leases before invoices; invoices before payments. Importing out of order is the main cause of mass row failures.

  3. Test with a small file first

    Ten rows. Check the outcomes, then run the rest.

  4. Check the branch

    Imports land in your active branch.

  5. Read the skipped rows, not just the failed ones

    Skipped rows are silent. A run reporting "500 created, 200 skipped" imported 500 records and quietly ignored 200.

  6. Reconcile after the run

    Count records in the app against rows in the file, and check a sample end to end.

Common problems

SymptomCause
Most rows failed with "not found"A dependency does not exist yet — usually the unit or lease. Import in order.
Rows silently skippedDuplicates against existing records, or rows failing a rule that skips rather than errors. Read the run detail.
Amounts imported wrong by a factorFormatting — thousands separators or currency symbols in a numeric column.
Dates shiftedSpreadsheet date format. Use the sample template's format.
Import created duplicatesThe file was run twice. Check history before re-running.

Next steps