Ask a school why they are still running a system nobody likes and the answer is almost never “it works well”. It is that moving looks worse. Somewhere in the building is a person who remembers the last migration, and that memory is doing more to keep the contract alive than the software is.
That fear is reasonable. It is also mostly a fear of the wrong thing. Migrations rarely fail because data cannot be moved. They fail because nobody decided what had to move, the first attempt was also the only attempt, and errors surfaced after the write instead of before it.
Here is how to do it properly.
Decide what actually has to move
The instinct is to move everything. It is the wrong instinct, and acting on it is what turns a two-week job into a summer.
Split your data into three piles:
- Must move, live. The current roster: who is enrolled right now, in which grade and class, with the identifiers your school actually uses. Without this nothing else in the new student information system functions.
- Should move. Things the new system will keep generating and that benefit from continuity: your catalogs, your timetable, your subject and grading structure, families and contact details.
- Archive, do not migrate. Deep history. Five years of daily attendance marks, individual assignment scores from closed years, message threads. These are almost never queried again, and every hour spent forcing them into a new schema is an hour not spent on the roster.
For the third pile, export it from the old system in whatever format it offers, put it somewhere safe and readable, and write down where it is. A school that keeps a clean PDF and CSV archive of closed years has met its obligations and saved itself weeks. What you genuinely need forward-facing is the transcript-level summary, not every mark that produced it.
Be honest about this pile in particular. In SCL the student CSV importer moves the student record itself. It does not carry a decade of historical marks across from another vendor, and any vendor who tells you their importer does that without seeing your data first is guessing.
Start from an export, never a blank template
The most common migration mistake is building your import file from the vendor’s blank template. You end up guessing at column names, value formats and what counts as required, and you find out you guessed wrong after uploading.
Work the other way round. Get a handful of students into the new system by hand, correctly. Then export that, and you are holding a file with the exact columns, in the exact order, with real values in the right format. That becomes your template, and the guessing stops.
SCL is built for this loop directly. The student import screen has an Export control that downloads your current students as CSV with precisely the columns the importer reads back. Export, open it, paste your data underneath the real rows, upload. The round trip is the feature.
Match on names your staff use, not internal IDs
A migration file that requires internal database IDs for every grade, class and house is a migration file only a developer can produce. Every value your registrar has to look up is a value they can get wrong.
Check how the importer resolves relationships. SCL matches grade by its name, class by its name and house by its name, so a row reads “Grade 5” and “5A” the way your staff would write it. Nobody has to know that Grade 5 is internally 17.
The one identifier that does matter is the student code, because it is what makes a student unique and what every later update will match on. Settle your student code scheme before you import anything, not after. If you are changing the scheme as part of the move, change it in the file, once, before the first upload.
Let it validate before it writes
This is the question that separates a safe importer from a dangerous one: does it check the whole file before it writes a single row?
A dangerous importer writes as it reads. It gets to row 240, hits a malformed date, stops, and now you have 239 students in and 61 out and no clean way to work out which. Your only honest option is to delete all of them and start again, and by the third attempt people are editing the database directly, which is how migrations become disasters.
SCL validates first and writes second. Every row is checked against the real column types before anything is committed: whole numbers where the column takes a number, parseable dates where it takes a date, and a student code that is genuinely unique. If any row fails, nothing at all is written, and you get the failures back as a list with the row number, the column and the reason. You fix the file and upload it again.
That behaviour is worth stating plainly, because it changes how the work feels. A file either goes in completely or does not go in at all. There is no partial state to reconcile, so there is no way to end up somewhere you cannot get back from.
Understand the four things a row can do
Once a file passes validation, SCL sorts every row into one of four outcomes and tells you the count of each before you live with the result:
- Insert. The row has no id, so it is a new student.
- Update. The row has an id that matches an existing student, and only the columns that actually differ are written. Everything else is left alone.
- Ignored. The row has an id that is not in the current set. This is the safety net: it stops a stale file from resurrecting a student who has left.
- Unmodified. The row matches an existing student exactly. Nothing happens.
The practical value of this is that the same file is safe to upload twice. A migration you can re-run without fear is a migration you can rehearse, and rehearsal is the entire difference between a calm move and a bad one.
Two things to know before your first real run. Imports are scoped to the current academic year and to students who are actually enrolled, so leavers and alumni are not touched. And the number of students you can add is bounded by your licence, so if your file is larger than your licence allows the import stops and tells you rather than importing part of it.
Expect the system to do work after the import
Loading a roster is not the same as having a working school. Students need to land in the right subject groups, language streams and religion classes before a teacher can take an attendance register or open a gradebook.
Ask how much of that is automatic. In SCL, once students are imported their groups are assigned from their grade and class, and their second language and religion assignments are set from the values in the file. That is the step schools most often underestimate when they migrate by hand, because it does not look like data entry until you are doing it three hundred times.
Give the timetable its own rehearsal
The timetable is the highest-risk single object in the whole move. It touches every teacher and every student on the first morning, and it is the thing people notice immediately when it is wrong.
Treat it separately from the roster and demand a dry run. SCL imports from aSc TimeTables in three deliberate steps: the upload parses the file and stages it without writing anything, a preview shows exactly what would change, and only then does a commit write it. If the result is wrong, one click reverts that exact batch and restores what was there before.
The timetable then stays in sync both ways, which matters more than the import itself. A one-time import drifts from the day somebody makes the first change in the wrong place.
Move catalogs early, because they are easy
Catalogs are the reference data everything else points at: subjects, grading scales, houses, and for many schools the library. They are low-risk, they are quick, and having them in place before the roster arrives means students land into a school that already exists rather than an empty shell.
The library is a good example of why to do this early rather than late. SCL takes a CSV with the columns a librarian already recognises, including ISBN, title, authors, publisher, barcode, accession number and shelf location, and it looks up cover images in the background rather than making you wait for a few thousand of them. Start it, and go and do something else.
Run it twice
Everything above is in service of this one instruction, which is the only piece of migration advice that matters if you remember nothing else.
The first run is a rehearsal. Load the real file, look at the counts, open ten student records and check them against the old system, and get your registrar to look for the twenty students they know are awkward: the mid-year joiners, the siblings with similar names, the child whose name is spelled two different ways in the old system.
The second run is the real one. By then you know your file is clean, you know what the counts should be, and the whole thing is uneventful. Uneventful is what you are buying.
Schedule the rehearsal at least three weeks before you need the system live. Not because the import takes three weeks, but because the rehearsal is what tells you which data in your old system is worse than anyone admitted, and finding that out is the actual purpose of doing it.
A sequence that works
- Set up the school structure first: divisions, grades, classes, subjects, grading scales.
- Import catalogs and reference data.
- Add a few students by hand, correctly, then export to get your real template.
- Build the full student file from that export.
- Rehearse the import. Read the errors. Fix the file, not the system.
- Check the awkward twenty by hand.
- Import the timetable with its dry run, and review the preview properly.
- Run the real import.
- Open the parent app as a parent before you announce anything to families.
- Archive the old system’s history somewhere readable, and write down where.
None of this requires heroics. It requires deciding what moves, working from an export instead of a template, insisting on validation before writing, and running the whole thing once before it counts.
If you are still choosing a system rather than moving to one, the questions to ask are in our buyer’s checklist. If you want to talk through what moving your school would actually involve, with your data in front of us, get in touch.
