---
title: "Import a schedule from your ERP"
description: "Turn a CSV or Excel export from your ERP into scheduled jobs. The importer runs as a six-step wizard: Upload, Read file, Check columns, Match people, Preview and Import. Only Job / Order number and Task name have to be mapped; rows sharing an order number become one job, and each row becomes one task under it. Column detection, date parsing and status translation all run in your browser. Parts the file names but your organization does not have are created automatically, which is the main source of duplicate parts, so the preview and the post-import cleanup both matter."
category: "Schedule work"
source_url: "https://www.iotflows.com/docs/production/import-erp-schedule/"
---
# Import a schedule from your ERP

Turn a CSV or Excel export from your ERP into scheduled jobs.

The importer is a wizard that reads your export, shows you what it plans to create, and writes nothing until you press the button on the last step. It runs entirely against the file you upload: SAP, Epicor, Oracle and a hand-built spreadsheet all go through the same six steps.

Two of those steps are yours to get right. **Check columns** decides which of your columns feeds which field, and **Preview** is the last place to catch a part that is about to be created by mistake.

**Before you start**

- Production Scheduler is part of the Scheduling Engine module. The tab strip renders for every organization, so seeing the **Scheduler** tab is not proof you are entitled to it. See [Module entitlement](/docs/production/overview/#entitlement).
- A CSV or Excel export from your ERP, with one row per task.
- Owner or Administrator, because an import can create parts and operations for the whole organization. The **Import** button itself is not role-gated in the dashboard. See [Roles reference](/docs/admin/roles-reference/).

Diagram: A vertical list of the importer’s six steps, numbered top to bottom and joined by downward arrows, under the column headings Step and What you decide. 1. Upload: which file, and which sheet. 2. Read file: nothing, detection runs in your browser. 3. Check columns: which column feeds which field. 4. Match people: who each name in the file is, and the step appears only when an Assignee column is mapped. 5. Preview: what gets created, row by row. 6. Import: nothing, watch it run, then clean up. A dashed violet line runs between step 5 and step 6, labeled: nothing is written above this line.

*The six import steps and what you decide at each one.*

## Prepare the file
Export your schedule as CSV, `.xlsx` or `.xls`. Each row becomes one task, and **rows that share a value in the order column become one job**. That is the *row grouping*, and it is the behavior you want from a routing export: if your ERP writes one row per operation, leave the file as it is and let the importer group it.

Only two fields have to be mapped: **Job / Order number** and **Task name**. Everything else is optional, and a column you do not map is either ignored or kept in the task's notes.

**Map the smallest set that gets a usable schedule on the first import.** Order number, task name, quantity and due date are enough to see the week on the board. Add the part, operation and machine columns on the next import, once you trust what the matching is doing.

The header row does not have to be the first row. The parser scans the first ten rows of each sheet and picks the one that looks most like headers, so the title and filter rows that ERP exports put on top are fine. Two columns with the same header are read as `Resource` and `Resource (2)`; a column with no header at all becomes `Column 7`.

> **Info:**
> **Does the file leave my browser?**
>
> No. Parsing, column detection, date parsing and status translation all run locally, with no network call and no AI. The only thing sent to IoTFlows is the jobs and tasks you confirm on the last step.

If you would rather start from a file that already fits, press **Download sample template** on the upload step. It writes `schedule-import-template.csv`, one column per mappable field with an example value, and five rows across two order numbers so you can see the grouping.

## Upload the file
1. Open the **Scheduler** tab of the Production page. See [Open the board](/docs/production/scheduler/#open).
2. Press **Import** in the control bar, at the right of the row above the board.
3. Drag the export onto the drop zone, or click it to browse.

![The Import ERP Schedule dialog on its first step. A five-part step indicator across the top reads Upload, Read file, Check columns, Preview, Import, with Upload active; Match people is not listed yet, because no Assignee column has been mapped. Below it a large dashed drop zone, ringed in violet, carries an upload-cloud icon and the text Drag and drop your ERP schedule (CSV or Excel), or click to browse, and under that a smaller line reading Exported from your ERP: SAP, Epicor, or any order report. We will figure out the columns for you. A Download sample template link sits at the bottom left, and the footer reads Upload a file to continue.](/images/production/prod-import-02.webp)

*Uploading the export from your ERP. CSV and Excel are both accepted.*

A parsed file shows as a card reading the file name and `412 rows · 17 columns`. Press **Continue** on the card, or the button in the footer.

If the workbook has more than one sheet, the dialog lists every non-empty sheet with its row count and waits for you to pick one. It never guesses across sheets.

### If the file will not upload
| What you see | What happened |
|---|---|
| **Please upload a .csv, .xlsx or .xls file.** | The file has another extension. Re-export it. |
| **No data found in this workbook.** | Every sheet in the workbook is empty, or has headers with no rows under them. |
| **No columns detected. Make sure one of the first rows contains headers.** | The parser found no header-like row in the first ten rows. Delete the preamble above your headers. |
| **No data rows found in the file.** | The sheet has headers and nothing else. |

## Read file
The **Read file** step has nothing to decide. IoTFlows matches each of your headers to a field by keyword, guesses the date format from the values in your due-date column, seeds the ERP status codes, and loads your parts, operations, machines, members, priorities and existing production jobs so the later steps can match against them.

Detection is a header-keyword match, not a reading of your data. Each header is scored against every field's keyword list and assigned to its best match, and no column is used twice, so `Prodn Supervisor` lands on **Assignee** and `Confirmed Finish Date` lands on **Due date** without either being named in the field list.

If you have saved a setup whose column headers are exactly this file's headers, it is applied here and the dialog toasts **Applied your saved setup "SAP weekly export"**. See [Save the mapping for next time](#profiles).

The step ends on **Check columns** by itself.

## Check the columns
This is the step that decides what your file means. Each row names an IoTFlows field on the left and carries a **Column in your file** drop-down on the right. Choose **Not in my file** to leave a field unmapped.

A field the detector filled carries a green **✓ auto-matched** chip. A required field it could not fill is lifted into a **Please check these first** section at the top of the panel, and the footer button stays disabled until both required fields are mapped.

![The Check columns step of the Import ERP Schedule dialog, its title bar carrying an Auto-detected chip and its step indicator now showing six steps with Check columns active. Three numbered violet callouts mark the attention section and the two fields inside it. Callout 1 sits on the heading Please check these first, beside an amber warning triangle. Callout 2 and callout 3 sit on the two drop-downs under it, both reading Not in my file: the first belongs to Job / Order number, carrying a red asterisk and the note that rows sharing the same number become one Job, and the second to Task name, noting that each row becomes a task with this name. Above them a Saved setups panel holds a text box reading Save current setup as, for example SAP weekly export, with a Save button. Below them a date panel reads My file's dates look like Month/Day/Year, with a green result reading 09/24/2026 arrow 2026-09-24, and a collapsible section headed Everything else we matched opens on its first row, Part, carrying a green tick chip reading auto-matched against the file's own Material Number column. The footer button, Next - match people, is greyed out.](/images/production/prod-import-04.webp)

*Checking the column mapping. Only Job / Order number and Task name are required.*

### Import fields
| Field | Section | Required | Matches against | Example | Auto-detected from |
|---|---|---|---|---|---|
| **Job / Order number** | Required | Yes | Existing production jobs, by title | `PO-1001` | `order`, `job`, `work order`, `wo`, `production order`, `job num`, `job number` |
| **Task name** | Required | Yes | Your parts by name, when **Part** is unmapped or does not match | `Engrave Nameplate 2-1/2 x 1` | `task name`, `task`, `material description`, `item description`, `op desc`, `operation description` |
| **Part** | Common | No | Your parts, by part number first and then by name | `Y841070` | `material number`, `part number`, `part`, `item number`, `sku` |
| **Operation** | Common | No | Operations on the matched part, by name | `Engraving` | `operation`, `op`, `routing operation`, `routing`, `activity`, `process`, `op code`, `op seq` |
| **Quantity** | Common | No | Nothing. `1.000` is read as 1 | `25` | `order quantity`, `quantity`, `qty`, `order qty` |
| **Due date** | Common | No | Nothing. Parsed with the format you pick below | `11/04/2025` | `confirmed finish date`, `due date`, `due`, `finish date`, `need by`, `ship date` |
| **Status** | Common | No | The status table below | `Open` | `system status`, `status`, `order state`, `state` |
| **Assignee** | Common | No | Members, by username, email or full name | `jsmith` | `assignee`, `assigned to`, `owner`, `prodn supervisor`, `supervisor`, `operator` |
| **Asset / Machine** | Common | No | Machines, by custom name, custom ID, machine name or machine ID | `Laser-01` | `asset`, `machine`, `work center`, `workcenter`, `resource`, `equipment`, `resource id`, `resource desc` |
| **Priority** | Advanced | No | Your work order priorities, by name | `High` | `priority` |
| **Time estimate** | Advanced | No | Nothing. Reads `2h 30m`, `1.5h`, `90m`, `1d`, or a bare number as minutes | `2h 30m` | `time estimate`, `duration`, `estimate`, `run time`, `runtime`, `hours` |
| **Budget** | Advanced | No | Nothing. `$1,250.50` is read as 1250.50 | `500` | `budget`, `cost`, `amount` |
| **Description** | Advanced | No | Nothing | `Customer rush order` | `description`, `details`, `long description` |
| **Notes** | Advanced | No | Nothing | `Handle with care` | `notes`, `note`, `comment`, `comments`, `remarks` |
| **Setup hours** | Advanced | No | Nothing. Added to production hours | `0.5` | `est setup hours`, `setup hours`, `setup time`, `setup` |
| **Production hours** | Advanced | No | Nothing. Added to setup hours | `3.0` | `est prod hours`, `prod hours`, `production hours`, `run hours`, `labor hours` |

The **Section** column is how the importer classifies a field, not a heading you will see. The panel shows anything needing attention first and folds the rest under **Everything else we matched**.

**Setup and production hours beat Time estimate.** When either is mapped, their sum in hours becomes the task's time estimate and a mapped **Time estimate** column is ignored. When none of the three is mapped and the row matched an operation that has a cycle time, IoTFlows estimates the task from that cycle time and the quantity.

Columns with no field of their own are listed under **Extra columns** as chips. Click any you want to keep and its values are written into each task's notes as `Customer PO: 44812`, one line per column.

### Tell the importer how to read your dates
`11/4/25` is November 4 in one plant and March 11 in another, so the date format is a choice, not a guess. Pick one from **My file's dates look like** and the panel parses the first date in your file next to it: `"11/4/2025" → 2025-11-04` in green, or `"11/4/2025" doesn't match, try another option` in red.

The options are month/day/year with a four-digit or two-digit year, `4.11.2025`, ISO `2025-11-04`, compact `20251104`, an Excel date number, and **Auto-detect per value**. A two-digit year of 00 to 69 reads as 2000s, 70 to 99 as 1900s.

Dates land at noon in your organization's timezone, so a day-only date cannot drift across a date boundary. `00/00/0000` is read as a deliberate blank. Anything else that will not parse leaves that task with no due date and adds to the **Invalid dates** count on the preview.

### Status translation
When a **Status** column is mapped, the panel lists each ERP code it knows about next to what it will become. Change any of them, and the change travels with the saved setup.

| ERP token | Maps to |
|---|---|
| `CRTD` | Open |
| `REL` | Open |
| `PCNF` | In Progress |
| `CNF` | In Progress |
| `PRC` | In Progress |
| `TECO` | Completed |
| `CLSD` | Completed |
| `DLV` | Completed |

Plain English works with no codes at all. `open`, `new`, `todo` and `backlog` become Open; `in progress`, `started`, `active` and `running` become In Progress; `on hold`, `hold`, `paused` and `blocked` become On Hold; `closed`, `complete`, `done` and `finished` become Completed; `approved`, `accepted` and `signed off` become Approved. A value it does not recognize imports as Open.

> **Info:**
> **Why did a closed order import as closed when the cell says six things?**
>
> SAP writes every active status at once, for example `CLSD PRT CNF DLV PRC GMPS MACM SETC`. The importer does not take the first token: it takes the most advanced one it recognizes, in the order Approved, Completed, On Hold, In Progress, Open. A trailing `*` on a truncated token is stripped first.

## Match people
The **Match people** step appears only when an **Assignee** column is mapped. It lists every distinct value in that column with the number of tasks behind it, and what each one resolved to.

A value matches a member on their username, email address or full name. It does not match on the IoTFlows organization handle, which never appears in an ERP export.

Matching is *fuzzy*, meaning a value does not have to be written exactly the way IoTFlows has it. The importer tries the exact value, then the value with case and punctuation flattened, then a match on shared words, then a spelling that differs by no more than two characters. Approximate matches are worth a look: `J. Smith` and `jsmith` both land on Jane Smith, but so could `jsmyth`.

![The Match people step of the Import ERP Schedule dialog, its intro reading These are the people listed in your Prodn Supervisor column. Three stat tiles across the top read Matched 8, Saved for next time 2 and Still unmatched 2, the last in amber. The list below is scrolled to its end, so four of its ten rows show, each pairing a value from the file and its task count with what that value resolved to. M.REYES, 3 tasks, and S.DUVAL, 3 tasks, carry a green tick, a member name and the word saved, and their Assign to member drop-downs hold that member. J.OKAFOR, 2 tasks, and T.NAKAMURA, 1 task, are ringed together in violet: each carries an amber person icon and the text No match, imports unassigned, with a drop-down reading Match automatically. A collapsed Saved people panel below the list carries the count 2, and the footer button reads Continue - 2 left unmatched.](/images/production/prod-import-05.webp)

*Matching assignee names from your ERP to IoTFlows members.*

For anything left over, open **Assign to member** and pick the person, or pick **Leave unassigned** to say that the value is not a member at all. **Match automatically** puts the row back to whatever the fuzzy match found.

Every choice you make here is remembered for this organization, so the same supervisor code resolves by itself on every future import. Open **Saved people** at the bottom of the step to review or remove them. The list lives in this browser, not on the server, so a colleague importing from their own machine starts with an empty one.

Leaving a value unmatched is not an error. Those tasks import with no assignee, and the footer button says so: **Continue - 2 left unmatched**.

## Check the preview
The preview is exactly what will be created. Five tiles across the top count **New jobs**, **Added to existing**, **Tasks**, **New parts** and **Invalid dates**.

**An order number that already has a job is reused, not duplicated.** Its tasks are added under the existing job, and the tile counts it as **Added to existing**. The match is case-insensitive, against the first word of an existing job's title, which is what the importer titles a job it creates.

> **Warning:**
> **Importing the same file twice**
>
> Jobs are deduplicated; tasks are not. Re-importing a file you already imported attaches a second copy of every task to the same job. Narrow the export to the rows that are new, or hide the rows you already have before importing.

![The Preview step of the Import ERP Schedule dialog, scrolled to the opened What we matched to your system panel. A violet rectangle rings its Parts block, headed PARTS from Material Number, whose two lines read 9 matched to your existing parts and 4 not in your system yet, so they'll be created as new parts, each with the operation from your file. Blocks for assignees, machines and priorities follow, the assignee one amber: 2 no member matched yet. Below them the editable table shows three rows. Face flange, under order PO-1004, carries a blue tick and the word existing against the part FL-3308 Flange, and the two rows under PO-1005 each carry a green plus and the word new against the part Y-841070.](/images/production/prod-import-06.webp)

*The preview. Parts that do not match are auto-created, which is the main source of duplicates after an import.*

### What gets created alongside the tasks
A part your file names that your organization does not have is created rather than skipped, because a task with no part and no operation schedules nothing. Those creations are collected in a panel headed **We'll add 4 new items to your system**, where every name is editable.

- **A name that already exists wins over creating.** Before a part is slated for creation, its name is checked against your parts, and an exact match links the row to that part instead. The row is tagged **matched by name** in the table.
- **Each new part gets at least one operation**, because a part with none cannot carry a task. With an **Operation** column mapped, the operation takes the file's value: a bare `20` becomes `OP20`, a code like `MILL` stays `MILL`. With no operation column, they are named `OP10`, `OP20`, `OP30` by position.
- **Parts you already have that carry no operation get one too**, listed separately under **Existing parts that need an operation**.
- Every part the import creates is stamped with `[ERP import weekly.xlsx 2026-09-20]` in its description, so the whole set is findable afterwards.

On any row you can rename the part, choose **Link to existing…** to point it at a part you already have, or tick **Skip** to create nothing.

> **Warning:**
> **Auto-created parts are the main source of duplicates**
>
> A part number your ERP writes as `Y841070` and IoTFlows holds as `Y-841070` will not match, so the import creates a second part, and every count and cost against it lands on the wrong record. Read the new-items panel before importing, and link anything you recognize.

### Fix rows in the table
Open **What we matched to your system** for a field-by-field count of what matched and what did not: assignees, machines and priorities that found nothing import blank, and are listed here rather than left for you to find on the board.

Everything in the table is editable. Click a cell to change the task name, quantity, due date, time estimate, part, operation, machine, priority, assignee or status, and click the eye icon on a row to hide it. Hidden rows are struck through, tagged **won't import**, and dropped from the totals and from the creation plan.

A small amber dot next to a matched value means it was matched approximately rather than exactly. Use **Columns** to show a field your file did not map, or to hide one you do not want to read.

The footer button counts what you are about to create: **Import 12 jobs · 47 tasks**. Nothing has been written up to this point.

## Run the import
Press the button. The importer creates the new parts and operations first, then each job, then its tasks. A progress bar tracks each phase, and the **Parts & operations** bar appears only on a run that is creating parts.

![The Import step of the Import ERP Schedule dialog after a finished run, with all six steps in the indicator now complete. Three full progress bars read Parts and operations 4 of 4, Jobs 12 of 12 and Tasks 47 of 47, above a green line reading Import complete. Three tiles below count 47 created, 0 skipped and 0 failed. A green strip reads Created 4 new parts for this import. Tagged in each part's description so you can find them later, with an Archive created parts button at its right. A single Download report link sits under it: this run failed nothing, so no Download retry file link appears. The footer holds Back and Done.](/images/production/prod-import-07.webp)

*The Import step after it finishes: what was created, what did not fully go through, and the two downloads.*

Leave the dialog open while it runs. The close button is disabled until it finishes, and closing afterwards refreshes the board behind it.

When it is done you get **Import complete**, a count of tasks created, skipped and failed, and a toast reading **Imported 47 tasks · created 4 parts**. **Download report** writes a CSV with one row per imported row, carrying the result, the reason and the new work order's ID.

### When rows do not import
Anything that did not fully go through is listed under the counters, in red when the thing does not exist and amber when it exists but is incomplete.

| What you see | What happened |
|---|---|
| **Part "Y841070" could not be created** | The part was not created. Its tasks still import, without a part or an operation. |
| **Could not add operations to "Bracket"** | The part exists with no operation, so its tasks import with no operation. |
| **Job PO-1001 could not be created** | The job failed, and none of its tasks were imported. |
| **Task "Deburr & Inspect" (order PO-1002) could not be created** | That one task was not created. Everything else in the job was. |
| **Task "Anodize" (order PO-1002) was created, but its status did not save** | The task exists and reads Open. Set the status on the board. |
| **3 tasks failed. Download the retry file to re-send them** | A toast summarizing the red rows above. |
| **Imported with 2 warnings. Some details didn't save (retry file available)** | Everything was created, but a detail on two of them did not save. |

**Download retry file** writes a JSON file holding every failed call with its endpoint and payload. It is not something you can re-upload here: it exists so the failure can be diagnosed or replayed by IoTFlows. Send it with your support request. See [Get support](/docs/get-started/get-support/).

A generic **Something went wrong** or **An error occurred** means the request failed and nothing was saved.

## Save the mapping for next time
A *mapping profile*, called a **saved setup** on screen, is one named copy of everything you decided on the **Check columns** step: the column mapping, the extra columns you kept in notes, the status code map and the date format.

![A cropped detail of the Saved setups panel at the top of the Check columns step. A bookmark icon heads the panel, above a line reading A saved setup is reused automatically when its columns match your file. Load a different one, or save the current setup below. One saved row, tinted green, reads SAP weekly export with a green tick at its left and the word Applied, a Revert link and a trash icon at its right. Under it, ringed in violet, sits the save control: a text box reading Save current setup as, for example SAP weekly export, and a Save button beside it, greyed out because the box is empty.](/images/production/prod-import-08.webp)

*Saving the mapping so the next import from the same ERP skips the mapping step.*

To save one, type a name into **Save current setup as…** at the top of the **Check columns** step and press **Save**. Name it after the export, not the week: `SAP weekly export` rather than `Week 38`.

The next file whose headers are exactly the same, in the same order, picks it up on the **Read file** step by itself and the row reads **Applied**. Headers have to match exactly, which is deliberate: an SAP setup can never be applied to an Epicor file.

Change anything after a setup is applied and the row stops reading **Applied**. **Re-apply** puts the saved values back, and **Revert** goes back to what the detector suggested. The trash icon deletes a setup.

**You do not need a saved setup for a one-off file.** Detection fills most of the panel on its own, and a setup that only ever matches one file is a name to remember for nothing. Save one when the same export lands every week.

> **Warning:**
> **Saved setups live in your browser**
>
> Both saved setups and saved people are stored in this browser for this organization, not on the server. A colleague importing the same export from their own machine gets none of it, and clearing site data loses all of it. Two people who both run imports should each save their own.

## Clean up after an import
Work through this in the first few minutes, while the result screen is still open.

1. **Archive the parts you did not mean to create.** **Archive created parts** on the result screen archives every part that import created, in one click. It is the fastest way out of a run that matched nothing, and it is only on screen until you close the dialog.
2. **Find the rest by their tag.** Parts created by an import carry `[ERP import <file> <date>]` in their description, so an import you cleaned up a day later is still identifiable. Archiving a part cannot be undone from the dashboard, so read the name before you archive it. See [Edit or delete](/docs/production/parts-and-operations/#edit).
3. **Fill in the blanks the matching left.** Assignees, machines and priorities that matched nothing imported empty. The **What we matched to your system** panel counted them, and the board is where you set them: see [Edit in place](/docs/production/scheduler/#inline).
4. **Give the undated tasks dates.** Tasks that imported with an unparseable date have no due date, so they sit outside the Gantt's timeline. See [Schedule work that has no dates](/docs/production/scheduler/#unscheduled).
5. **Delete what should not be there.** **Jobs** in the scheduler control bar opens the job list, where a job can be renamed or deleted. Individual tasks are deleted from the board, and a deleted work order is recoverable on the server for 30 days: see [Undo and redo](/docs/production/scheduler/#undo).

Then read the board. An import of a few hundred tasks usually double-books something, and the issues panel is the fastest way to see what: [Find and fix problems in the schedule](/docs/production/schedule-issues/#open).

## See also
- [Plan jobs on the scheduler board](/docs/production/scheduler/)
- [Create and organize parts](/docs/production/parts-and-operations/)
- [Give people access to a scheduler board](/docs/production/scheduler-members/)
- [Job and scheduler fields](/docs/production/job-fields-reference/)
- [Find and fix problems in the schedule](/docs/production/schedule-issues/)
