Skip to content

Bulk CSV Import

c0dewhacker edited this page Apr 22, 2026 · 2 revisions

Bulk CSV Import

There are two CSV importers in Roomer:

  • Global import — Admin → Import. Creates buildings, floors, zones and assets from one flat sheet. Use this for initial seeding or moving in a whole floorplan from a spreadsheet.
  • Per-floor import — inside the floor editor. Creates assets on an existing floor and places them into existing zones.

They have different column names and different semantics. Pick the one that matches what you already have.

Global import (Admin → Import)

Use when you have a single sheet that describes the whole layout: "Building Sydney HQ has two floors, each with a Window zone and an Open zone, and here are the 200 desks."

Endpoint

POST /api/v1/import/bulk — SUPER_ADMIN only. Max 2000 rows per request.

Columns

Column Required? Accepted values
building_name ✓ Any non-empty string. Buildings are looked up by name; if a building with that name exists it's reused.
building_address Free text. Only applied when the building is created; ignored on reuse.
floor_name ✓ Any non-empty string. Scoped to the building — two floors with the same name in different buildings are OK.
floor_level Integer. Controls sort order in the sidebar (lower first). Blank → 0.
zone_name ✓ Any non-empty string. Scoped to the floor.
zone_colour 6-digit hex like #6366f1. Blank → palette is auto-assigned from a rotating 10-colour list, so you can leave it out entirely.
asset_name ✓ Display name on the marker.
asset_category ✓ Any string. If the category doesn't exist it's created on the fly with defaultIsBookable inferred from the row.
asset_status OPEN (default), RESTRICTED, ASSIGNED, or DISABLED. See Zones and Assets.
asset_amenities Semicolon-separated list: monitor;docking-station;adjustable-desk.
is_bookable true (default) or false/0 to create an inventory-only asset.
serial_number Free text.
asset_tag Free text. Must be unique across all assets.

How repetition works

One row creates one asset. Buildings / floors / zones / categories are upserted by name — the importer tracks what it has already created within the same request so repeating a building/floor/zone across rows is fine:

building_name,floor_name,zone_name,asset_name,asset_category,asset_status
Sydney HQ,Level 1,Window Row,A-01,Desk,OPEN
Sydney HQ,Level 1,Window Row,A-02,Desk,OPEN
Sydney HQ,Level 1,Open Plan,B-01,Desk,OPEN
Sydney HQ,Level 2,Window Row,C-01,Desk,OPEN

Result: 1 building, 2 floors, 3 zones (Window Row on L1, Open Plan on L1, Window Row on L2), 4 assets.

Placement

Every imported asset is dropped at (x=50, y=50) with width=3, height=2, rotation=0. After import, open the floor editor to drag assets into position. (Keeping layout information in CSV is deliberately out-of-scope — you end up in a GIS-y rabbit hole. Use the canvas once, not a spreadsheet.)

Errors

Rows are validated individually. Valid rows are imported in a transaction; invalid rows are returned to the UI with row numbers (offset by +2 so row 1 is the header, row 2 is the first data row). If all rows are invalid the endpoint returns 422 with no changes applied.

The import dialog in Admin → Import shows the success/error count and lists every error, making it easy to fix the sheet and re-upload. Re-running with partial overlap is safe — existing buildings/floors/zones/categories get reused.

Template

building_name,building_address,floor_name,floor_level,zone_name,zone_colour,asset_name,asset_category,asset_status,asset_amenities,is_bookable,serial_number,asset_tag
Sydney HQ,1 Example St Sydney,Level 1,1,Window Row,#6366f1,A-01,Desk,OPEN,monitor;docking-station,true,,A-01
Sydney HQ,,Level 1,1,Window Row,,A-02,Desk,OPEN,,true,,A-02
Sydney HQ,,Level 1,1,Open Plan,#10b981,B-01,Desk,OPEN,,true,,B-01
Sydney HQ,,Level 1,1,Open Plan,,B-02,Desk,DISABLED,,true,,
Sydney HQ,,Level 2,2,Window Row,,C-01,Desk,RESTRICTED,,true,,C-01

You can download this template from Admin → Import → Template CSV.

Per-floor import (floor editor)

Use when the building, floor and zones already exist and you just want to populate (or add to) a floor with assets.

Endpoint

POST /api/v1/assets/bulk-import — body { floorId, assets: [...] }, SUPER_ADMIN only. Max 500 rows per request.

Columns

Column Required? Accepted values
name ✓ Display name.
category ✓ Category name. Auto-created if missing, with defaultIsBookable=true.
bookingStatus OPEN (default), RESTRICTED, ASSIGNED, DISABLED.
bookingLabel Defaults to Desk. Shown in the booking dialog and email subject.
amenities Semicolon-separated list.
serialNumber Free text.
assetTag Free text. Must be unique.
notes Free text.
zoneName Name of an existing zone on this floor (case-insensitive). If no match or omitted, the asset is placed in the floor's first zone — create zones before importing.

Template

name,category,bookingStatus,bookingLabel,amenities,serialNumber,assetTag,notes,zoneName
A-01,Desk,OPEN,Desk,monitor;docking-station,,,,Window Row
A-02,Desk,OPEN,Desk,,,,,Window Row
MR-Jupiter,Meeting Room,OPEN,Room,whiteboard;tv,,,,Rooms

Errors

Each row is attempted independently; failures are returned with the row number and an error message. Successful rows are not rolled back when others fail — fix the broken rows and re-run.

User assignment import

Use this when you want to bulk-assign users to existing bookable assets without editing each one individually.

Where to find it:

  • Admin → Buildings → [building] → Bulk assign users — pre-loaded with the building's assets; download a pre-populated CSV to start.
  • Admin → Assets → Bulk assign users — no building filter; download the blank template and fill in asset IDs.

Endpoint

POST /api/v1/assets/user-assignments/bulk — body { rows: [{ assetId, userEmail, isPrimary? }] }, SUPER_ADMIN only. Max 5000 rows per request.

Columns

Column Required Accepted values
ASSET_ID ✓ UUID of an existing Roomer asset.
USER_EMAIL ✓ Email of an existing Roomer user.
IS_PRIMARY true / false / 1 / yes. Defaults to false. Setting a new primary clears the previous one for that asset.

Template

ASSET_ID,USER_EMAIL,IS_PRIMARY
<asset-uuid>,jane.smith@example.com,true
<asset-uuid>,john.doe@example.com,false

Download [building] assets in the dialog to get a pre-populated version with asset IDs and current assignments already filled in.

Behaviour

  • Each row is attempted independently; rows that fail (unknown asset, unknown user) are returned with a row number and reason.
  • Successful rows upsert — running the same assignment twice is safe.
  • Removing a user is not supported via this CSV; use the per-asset UI or the DELETE /api/v1/assets/:id/user-assignments/:userId endpoint.

Clearing all assignments on a floor

To wipe a floor's assignments in bulk, use the Clear assignments button (user-minus icon) on each floor card in Admin → Buildings → [building]. This calls DELETE /api/v1/assets/user-assignments/by-floor/:floorId.


General tips

  • Excel will mangle leading zeros (001, 02-A) — if you use those, save the sheet as CSV with quoted fields or use Google Sheets / LibreOffice.
  • Both importers treat the first CSV row as headers — case-insensitive, but the exact names listed above must be used (no synonyms).
  • Use UTF-8. Non-ASCII zone/asset names work fine.
  • If you need to re-upload after fixing errors, you can — duplicate rows with the same name will hit uniqueness on asset_tag (if set) or create a second asset (if not). Clean up first, or only include the missing rows.

Clone this wiki locally