Bulk Import via CSV

Bulk Import via CSV

If you already have your school's data in a spreadsheet, the Bulk Import page lets you paste or upload a CSV instead of entering everything one record at a time on each individual page.

Import order matters

There's one tab per entity: Teachers, Buildings, Rooms, Subjects, Classes, Lessons — in that order for a reason. Lessons reference Teachers, Rooms, Subjects, and Classes by name, so those have to exist first (either entered manually or imported) before a Lessons import can resolve them. Buildings comes before Rooms because a Room's building column references one by name.

Each tab has a download template option showing the expected columns and a couple of example rows — the fastest way to get the exact header names right.

Insert-only, not update

Importing a row that collides with an existing unique name (Rooms, Subjects, Classes, and Departments all require unique names within your school) skips that row and reports why, rather than updating the existing record. Re-running an import after fixing a few bad rows is safe — anything that already succeeded just gets skipped again as a duplicate on the second pass. But if you intentionally change a value in your spreadsheet and re-import, it will not update the existing record to match; you'd need to edit that record by hand or delete-and-reimport it.

Teachers are the one exception — their names have no uniqueness check (see Adding Teachers), so importing the same teacher name twice creates two separate teacher records rather than being skipped as a duplicate. The only thing that stops a teacher row is its optional employeeId: if that ID is already used by another teacher (ignoring upper/lower case), the row is skipped and reported. So giving your teachers Employee IDs makes a repeated Teachers import safe too.

Cross-references are matched by name, case-insensitively

A Lesson row's subject, teachers, and groups columns are looked up by name against what's already in your school's data (not by any internal ID, since a spreadsheet has no way to know those). Matching is case-insensitive. If a name in your CSV doesn't match anything, that row is skipped and reported (e.g. "Unknown teacher(s): ...") rather than creating a new record on the fly.

A Class row's parent column works the same way — the parent has to already exist, either from an earlier row in the same file or from a previous import/manual entry.

A Room row's building column is the one exception to "matched by name, skipped if no match" — if the name doesn't match an existing Building, one is created on the fly instead of the row being rejected. This is deliberate: unlike a Lesson's teacher/subject/group references (which must already exist as real records elsewhere), a Building is little more than a label, so there's no useful "unknown building" error to report. Import the Buildings tab first only if you want to set up cross-building travel penalties (on the Buildings page) before importing Rooms — otherwise it's optional, since Rooms import creates any building it references automatically.

Multiple values in one cell: use ;, not a comma

Where a column can hold more than one value — a Lesson's teachers or groups (for co-teaching or a combined session), a Room's tags — separate them with a semicolon, not a comma. This is deliberate: a comma is CSV's own field separator, so using it inside a cell would need quoting; semicolons avoid that entirely and match how these same multi-value fields are stored internally.

What each tab imports

TabRequired columnsNotes
Teachersnameemail is optional and does not send an invite email — bulk-importing shouldn't fire a wave of invite emails at once. Use Resend Invite on the Teachers page per person afterward if you want to invite them. employeeId is optional; when given it must be unique within your school (not case-sensitive), including against earlier rows in the same file — a row whose ID is taken is skipped. Unavailable periods can't be set via CSV; add those manually after import.
BuildingsnameNothing else to import — cross-building travel penalties are set on the Buildings page after import. Optional tab: importing Rooms with a building value creates any building it references automatically.
Roomsname, capacitytags optional, ;-separated. building optional — matched by name, created if it doesn't already exist (see note above).
SubjectsnameNothing else to import.
Classesname, sizeparent optional. Even a parent/Year-level row needs a positive size value in the file, even though that number is never actually used once the row has children — see Class Groups, Years & Subgroups. List a parent row before its children in the file.
Lessonssubject, teachers, groupsduration/count default to 1 if omitted. bunching defaults to SPREAD (valid values: SPREAD, CONSECUTIVE, NONE). requiredRoomTags/activityTags optional, ;-separated. Locked and Preferred time slots can't be imported — a Period is identified by day + time, not a stable name you can type into a spreadsheet cell, so those are set on the Lessons page after import.

After importing

Each row's outcome (created / skipped / error, with a reason) is reported per row, not just as a pass/fail for the whole file — check that list before assuming everything went in as expected, especially on a large file where a handful of rows commonly fail on a name typo or a missing prerequisite record.