QFlowLearn: QTI 3.0 authoring
OneRoster TypeScript: a practical base for open source ed products
June 30, 2026 Open Source

OneRoster TypeScript: a practical base for open source ed products

A first look at LongsightGroup/oneroster, an MIT licensed TypeScript package for OneRoster 1.2 CSV parsing, validation, diagnostics, and package writing.

@longsightgroup/oneroster gives TypeScript projects a OneRoster 1.2 toolkit: parse CSV ZIP packages, normalize rows, validate references, collect diagnostics, and write packages back out.

The package leaves product provisioning to the application. Local enrollment rules, account policy, and sync behavior belong in the SIS adapter, LMS adapter, or import job that owns them.

The package is on GitHub at LongsightGroup/oneroster, published under the MIT license, and available on npm as @longsightgroup/oneroster. Version 0.1.0 targets OneRoster 1.2 CSV today. The README lays out the rest of the path: REST bindings, gradebook, resources, assessment results, fixtures, and conformance-oriented tooling.

OneRoster is a concrete contract

OneRoster addresses a normal school integration problem: systems need to agree on people, courses, classes, enrollments, organizations, resources, and grades. Districts usually have a source system, often a SIS, and several consumer systems that need roster data. Some integrations move files, and others call APIs. Both patterns exist in real schools, so the spec supports CSV exchange and REST services.

The spec names the domain objects instead of leaving each vendor to invent its own roster vocabulary. The OneRoster 1.2 final release splits the work into services: Rostering, Gradebook, and Resources. The CSV binding defines a ZIP package, a manifest.csv, file names, headers, bulk and delta behavior, and the row-level data shape. The REST documents define service models and endpoints.

The spec separates those services, so implementers can build the piece they need. A small tool can start with CSV imports, a larger platform can add REST, and a gradebook can care about line items, results, categories, score scales, and assessment results without pretending that every integration starts from the same product shape.

Open source projects benefit when the contract sits outside any one vendor. If an LMS, SIS adapter, analytics pipeline, assessment tool, or content platform can speak the same roster format, each project can spend less time on custom import glue and more time on its own job.

The package keeps the spec visible

@longsightgroup/oneroster exposes the layers an importer needs: raw CSV package parsing, typed rostering parsing, full CSV parsing for gradebook and resources, reference validation, structured diagnostics, and package writing.

OneRoster failures are usually data problems. A teacher cannot fix “import failed.” An administrator might be able to fix users.csv, row 42, field primaryOrgSourcedId, expected orgs.csv.

The package also treats privacy as part of the diagnostic design. Its tests check that reference diagnostics do not expose raw IDs or user payload values in places where a safe location-based diagnostic is enough. Developer tools should help operators find the row without spraying student details into logs.

Read a rostering ZIP

The main API returns a small Result type. Expected failures are part of the return value, not thrown exceptions.

import { readFile } from "node:fs/promises";
import { parseAndValidateOneRosterCsvRosteringZip } from "@longsightgroup/oneroster";

const bytes = new Uint8Array(await readFile("oneroster.zip"));
const result = parseAndValidateOneRosterCsvRosteringZip(bytes);

if (result._tag === "err") {
  for (const diagnostic of result.error) {
    console.error(
      [
        diagnostic.code,
        diagnostic.fileName,
        diagnostic.rowNumber === undefined ? undefined : `row ${diagnostic.rowNumber}`,
        diagnostic.field,
        diagnostic.message,
      ]
        .filter(Boolean)
        .join(": "),
    );
  }

  process.exitCode = 1;
} else {
  const { rosteringPackage, indexes } = result.value;

  console.log(`users: ${rosteringPackage.users.length}`);
  console.log(`classes: ${rosteringPackage.classes.length}`);
  console.log(indexes.usersBySourcedId.get("student-123")?.username);
}

On success, the package gives you typed arrays for academicSessions, orgs, courses, classes, users, roles, enrollments, demographics, and userProfiles. It also builds lookup indexes keyed by sourcedId, which are usually what an importer needs after the first parse.

Stricter reference checks

OneRoster CSV packages can be bulk or delta. The package defaults reference validation to bulkOnly, which matches the common case: validate the full graph when the file is a full snapshot.

If you are running a QA job, migration dry run, or vendor certification harness, you may want every row checked.

import { parseAndValidateOneRosterCsvRosteringZip } from "@longsightgroup/oneroster";

const result = parseAndValidateOneRosterCsvRosteringZip(bytes, {
  referenceMode: "allRows",
});

if (result._tag === "err") {
  const missingReferences = result.error.filter((diagnostic) =>
    diagnostic.code.startsWith("reference."),
  );

  console.table(
    missingReferences.map((diagnostic) => ({
      file: diagnostic.fileName,
      row: diagnostic.rowNumber,
      field: diagnostic.field,
      code: diagnostic.code,
    })),
  );
}

The option keeps strictness under caller control. Normal imports can validate bulk snapshots. QA jobs, migration checks, and certification work can inspect every row.

Full CSV parsing

Rostering is the first thing most people think about, but OneRoster 1.2 also covers gradebook and resources. The package already has full CSV parsing and validation entry points for supported record layers.

import { parseAndValidateOneRosterCsvFullZip } from "@longsightgroup/oneroster";

const result = parseAndValidateOneRosterCsvFullZip(bytes, {
  referenceMode: "allRows",
});

if (result._tag === "ok") {
  const { fullPackage } = result.value;

  const lineItems = fullPackage.gradebookPackage.lineItems;
  const results = fullPackage.gradebookPackage.results;
  const resources = fullPackage.resourcesPackage.resources;

  console.log({
    lineItems: lineItems.length,
    results: results.length,
    resources: resources.length,
  });
}

For an open source LMS or analytics project, typed records are the point. Import code can work with a normal TypeScript model while the package keeps the original OneRoster shape available for conformance work.

Write packages back out

The writer API lets a tool normalize a package, inspect or modify records, and then produce a deterministic ZIP again.

import { writeFile } from "node:fs/promises";
import {
  parseAndValidateOneRosterCsvFullZip,
  writeOneRosterCsvFullZip,
} from "@longsightgroup/oneroster";

const parsed = parseAndValidateOneRosterCsvFullZip(bytes);

if (parsed._tag === "err") {
  throw new Error(parsed.error.map((diagnostic) => diagnostic.message).join("; "));
}

const written = writeOneRosterCsvFullZip(parsed.value.fullPackage);

if (written._tag === "err") {
  throw new Error(written.error.map((diagnostic) => diagnostic.message).join("; "));
}

await writeFile("normalized-oneroster.zip", written.value);

The writer supports boring but necessary tools: package linting, fixture generation, import previews, data repair workflows, and test harnesses for downstream systems.

MIT licensing helps adoption

The MIT license fits this kind of library. School software lives in mixed environments: public projects, internal tools, vendor products, and district scripts that one person maintains because the work has to get done.

A permissive license lets those groups use the same package without turning every integration decision into a licensing meeting. License alone will not improve code quality, but it lowers the cost of adoption.

The package should stay close to the spec. The more product-specific behavior it absorbs, the less useful it becomes as shared infrastructure. Validation, diagnostics, typed records, conformance fixtures, CSV and REST bindings, and careful package writing belong here. Local provisioning rules belong in the application.

Useful now, still early

The package is still early. Version 0.1.0 supports CSV package parsing and normalization today, with REST bindings, gradebook, and resources continuing to grow. The README describes a phased path from foundation work through CSV conformance and REST runtime helpers.

CSV imports are where many schools still feel the pain. They are also a practical way to harden the OneRoster model before adding REST client and server helpers.

I would use @longsightgroup/oneroster as the base for:

  • LMS import jobs that need typed roster data and clear diagnostics
  • SIS export validation before files leave the source system
  • QA tools that compare vendor files against OneRoster expectations
  • fixtures for open source edtech projects that need realistic school data
  • migration scripts that normalize CSV packages before another system reads them

A TypeScript project should not need a private CSV parser to consume OneRoster. This package gives open source education products a practical shared base for the import, validation, and normalization work around the spec.

Related Articles

Work with Longsight

Bring us the problems that cross your LMS, identity, and compliance systems.

We can host the service, help you run it on campus, answer security review, integrate with your LMS, and work through accessibility requirements.