The files your customers actually send
A district data coordinator is rolling your platform out to 14 schools before the first day of class. She has a student export from the district's student information system, a teacher roster from HR, a sections file that links students to classes, and a guardian contacts sheet that each school office maintains its own way. Some schools list two guardians in two columns, others put each guardian on a separate row.
Grade level arrives as "K", "KG", "00", "1st", and "Grade 1". Student IDs are unique within a school but repeat across the district, so student 1042 at the middle school is a different child from student 1042 at the high school. Birth dates are a mix of formats, and one has a year that would make the student four. Teacher emails use the district domain in one file and personal addresses in another. She needs all of it loaded and correct by Monday, and every district you sign will send its files differently.
Fields and rules that matter in EdTech
| Field | What goes wrong in real files | How Dromo catches it |
|---|---|---|
| Student ID | Repeats across schools in the same district | unique_with on student ID and school ID together |
| School ID | Names or abbreviations instead of codes | Select field of the district's schools, or a column hook that resolves names against your API |
| Grade level | "K", "KG", "00", "1st", "Grade 1" | Select field with allowed values; bulk fix maps every variant in one step |
| Date of birth | Mixed formats, impossible ages | Date parsing with a fixed output format; a row hook flags ages outside the grade's range |
| Teacher email | Personal addresses, duplicates | Email type with unique; regex_match can require the district domain |
| Section ID | Points to a class that is not in the file or your system | Async column hook checks each section against your API |
| Guardian email | Present without a guardian name, or the reverse | Email type with require_with on the guardian name |
| Enrollment status | "Active", "A", "Enrolled", "Y" | Select field with allowed values, remembered for the next upload |
A working roster schema
{
"fields": [
{
"label": "Student ID",
"key": "student_id",
"type": "string",
"validators": [
{ "validate": "required" },
{ "validate": "unique_with", "uniqueKey": "school_student", "errorMessage": "This student ID already appears for this school" }
]
},
{
"label": "School ID",
"key": "school_id",
"type": "string",
"validators": [
{ "validate": "required" },
{ "validate": "unique_with", "uniqueKey": "school_student" }
]
},
{ "label": "First name", "key": "first_name", "type": "string", "validators": [{ "validate": "required" }] },
{ "label": "Last name", "key": "last_name", "type": "string", "validators": [{ "validate": "required" }] },
{
"label": "Grade level",
"key": "grade_level",
"type": "select",
"selectOptions": [
{ "label": "Kindergarten", "value": "K" },
{ "label": "Grade 1", "value": "1" },
{ "label": "Grade 2", "value": "2" },
{ "label": "Grade 3", "value": "3" }
],
"validators": [{ "validate": "required" }]
},
{
"label": "Date of birth",
"key": "date_of_birth",
"type": ["date", { "outputFormat": "YYYY-MM-DD" }]
},
{ "label": "Guardian name", "key": "guardian_name", "type": "string" },
{
"label": "Guardian email",
"key": "guardian_email",
"type": "email",
"validators": [{ "validate": "require_with", "fields": ["guardian_name"] }]
}
]
}
The grade list above is shortened; extend it through Grade 12. The age check and the section lookup are hooks because they depend on the grade in the same row and on classes in your database. Schema Studio lets a product manager add a grade level, a school, or a new required field from a form, so a district with different needs does not wait on an engineer.
Student data privacy, FERPA, and COPPA
Districts ask every vendor the same question: where does student data go? FERPA and COPPA obligations sit with the school and with your product, and Dromo is designed so that it does not add another place where student records live.
In Private Mode, the default, the roster is parsed and validated in the coordinator's browser and the results go straight to your application. Dromo never receives the rows. AI column matching runs in a headers-only mode that shares column names and the distinct values of select fields, such as grade levels, never names, birth dates, or IDs. Dromo has completed a SOC 2 Type II audit, and the report is available through the Trust Center for district security reviews. On the Enterprise plan, Bring Your Own Storage writes import data directly to your own bucket, and the full stack can be self-hosted in your cloud. See how Private Mode works.
Automated rostering across districts
The first import is a person at a keyboard. After that, most districts want rosters to sync on their own, because students enroll, withdraw, and change sections all year. On the Enterprise plan, the headless data importer takes the nightly SIS export over the API or SFTP and runs it through the same schema the coordinator used in the browser.
Every district's export is a little different, and they change without warning. When a column is renamed or a batch of rows fails validation, the import stops in NEEDS_REVIEW with a review link. You send it to that district's coordinator, who fixes it in the same importer she used at launch, and the sync continues without your support team in the loop.
Proven at scale
Teamworks, the athlete engagement platform used by collegiate and professional sports organizations, uses Dromo for customer data onboarding and reports more than 99 percent import accuracy.
For a deeper look at roster and student record imports, see data onboarding for EdTech: student records, rosters, and compliance.

