Export & Import Sync
Download records as XLSX, CSV, or JSON and sync edited files back with a full diff preview — no deletions, nothing lost.
The export/import round-trip lets teachers edit extracted records in Excel and push changes back into the database with a single file upload. The diff engine classifies every row before writing anything, so teachers can preview exactly what will change.
All utilities are exported from @nexa-ed/react and are browser-only — do not call them in a server context.
Export
buildExportHeaders(rows, columnOrder?)
Returns the final ordered column header array. __record_id is always first (required for round-trip sync), followed by columnOrder columns in order, then any remaining keys.
downloadAsXlsx(rows, fileName, columnOrder?)
Triggers a browser download as an .xlsx file.
- The
__record_idcolumn is written as column A but hidden (width 0) — teachers see a clean sheet while the ID is preserved for sync. - The first row is frozen.
- Auto-widths are applied to all visible columns.
downloadAsCsv(rows, fileName, columnOrder?)
Same column ordering as XLSX. __record_id appears as the first visible column in CSV (it cannot be hidden the way it is in XLSX).
downloadAsJson(rows, fileName)
Pretty-printed JSON array. __record_id is included in every object so the file can be used for round-trip sync.
The __record_id field
Every exported row contains a __record_id field — the database ID of the record. It is the key that makes round-trip sync possible. When the file is re-imported, each row is matched back to the original record using this ID.
Warning
Never delete or modify the __record_id column. In XLSX it is hidden (column A, width 0) — leave it alone. In CSV and JSON it is visible.
Import / sync
parseImportFile(file): Promise<ParsedImportRow[]>
Accepts a File object from an <input type="file"> or drag-and-drop. Handles .xlsx, .csv, and .json. All values are coerced to string | null.
Rows without a __record_id value — for example, rows the teacher added at the bottom of the Excel file — are kept in the output. buildImportDiff will classify these as new rows to create.
Screenshot
parseImportFile result — parsed rows array logged in DevTools, showing __record_id on matched rows and null on new rows
buildImportDiff(parsed, currentRecords): ImportDiff
Compares parsed rows against a Map<recordId, recordData> snapshot of the current records and returns a diff object safe to show as a preview before applying anything.
Merge strategy for updates: { ...existing, ...imported } — columns absent from the uploaded file are preserved from the existing record. Nothing is erased by omission.
ImportDiff type
| Field | Type | Description |
|---|---|---|
toUpdate | Array<{ id, recordData }> | Rows with a matching __record_id where at least one value changed. |
toCreate | Array<recordData> | Rows with no __record_id — new rows the teacher added. |
unchanged | number | Rows that matched but had no changed values. No DB write needed. |
skipped | number | Rows with a __record_id that doesn't exist in this document (e.g. exported from a different file). |
Screenshot
Sync from file dialog — file chip selected, diff preview showing new rows (green), updates (blue), unchanged (gray), skipped (amber)
Full round-trip example
Screenshot
Table after applying sync — green NEW badges on created rows, blue-tinted cells on updated rows, unchanged rows unaffected
Info
StudentRecordsTable handles the entire round-trip automatically — the Sync from file button
in its toolbar runs parse → diff → preview → apply with no extra code needed.