CSV / Excel import & export
TruePPM reads and writes CSV and Excel files so data can move between a spreadsheet and a schedule. This page tells you exactly what each surface does, what it does not do, and which file to use when CSV is the wrong tool — then covers the import wizard and the reference tables for anyone scripting against the endpoints directly.
Most teams that have never used dedicated project software still run a real schedule — in Excel, or in a Google Sheet shared over email. For them the barrier to adopting TruePPM is not features, it is getting the existing plan in without retyping it. CSV / Excel import removes that barrier: upload the sheet, confirm what each column means, and TruePPM builds a CPM-schedulable network from it — tasks, durations, dates, a WBS hierarchy, dependencies, and assignees. This is also the import path for the many tools that export CSV — Asana, Trello, Basecamp and others all produce a CSV you can feed directly to this endpoint.
What round-trips, and what doesn’t
Section titled “What round-trips, and what doesn’t”“Round-trips” means: export it, import it back, and get the same thing.
| Surface | Export | Import | Round-trips? |
|---|---|---|---|
| Risks | Yes | Yes | Yes — the two are counterparts |
| Tasks | Yes | Ships in 0.4 | No — see below |
| Labels | No | Yes | No — import-only; there is no label export to round-trip from |
| Sprints, resources, calendars, dependencies | No | No | — |
Why tasks don’t round-trip
Section titled “Why tasks don’t round-trip”The task CSV export is a snapshot of what’s on your screen. Three of its eight columns are values TruePPM calculated rather than values you entered:
| Column | Where it comes from |
|---|---|
WBS | your outline |
Name | you |
Start | calculated — the earliest the scheduler can start this task |
Finish | calculated — the earliest it can finish |
Duration (days) | you |
Progress (%) | you |
Status | you |
Critical | calculated — whether it’s on the critical path |
The task import that ships in 0.4 reads Start and Finish as dates you are
committing to. So if you export tasks to CSV and import that file back, you get
new tasks whose committed dates are yesterday’s calculated output — not a copy of
your project.
Use the export to hand someone a status table. Use a JSON seed to move the project.
Making the two into genuine counterparts is planned — see issue #2401.
The Risk Register’s Export CSV and Import CSV actions are symmetric: a file exported from one project imports cleanly into another. See Risk register for the workflow.
Columns: ID, Title, Status, Category, Response, P, I, Severity,
Owner, Mitigation Due Date, Trigger, Contingency, Description.
Only Title is required. On import:
IDandSeverityare ignored — IDs are assigned per project, and severity is always calculated from probability × impact.Owneris matched against the project’s own members by email, username, or ID. Someone who isn’t a member of the project can’t be assigned by a file; that row imports with the risk unassigned and a warning.- An unrecognized status, category, or response imports the row anyway and warns.
- A probability or impact outside 1–5 skips that row — a stray value there usually means the columns were mapped wrong, and guessing would be worse.
A larger file than the risk import limit is rejected outright rather than partially imported, so a truncated import can never look like a successful one.
Task CSV export is available today from the Table view’s CSV toolbar action. It exports the rows currently shown — your filters and sort apply.
Task CSV and Excel import ships in 0.4. Upload the sheet, confirm what each column means in the wizard below, and TruePPM builds a CPM-schedulable network from it — tasks, durations, dates, a WBS hierarchy, dependencies, and assignees.
What a good spreadsheet looks like
Section titled “What a good spreadsheet looks like”The importer expects a header row followed by task rows. Nothing else is required — every column below is optional except the task name. Download the known-good template if you want a target shape to paste into.
This is the single reference for every header the importer recognizes — there is only one alias table, so it can’t drift from itself:
| TruePPM field | Header spellings recognized |
|---|---|
| Task name (required) | Name, Task, Title, Task Name, Activity, Summary |
| ID | ID, Task ID, UID, No, Number, Ref, Key |
| WBS / phase | WBS, WBS Code, Phase, Level, Outline, Outline Number |
| Duration | Duration, Days, Effort, Estimate, Work Days |
| Start | Start, Begin, Start Date, Planned Start |
| Finish | Finish, End, Due, Due Date, Planned Finish |
| % complete | % Complete, Percent Complete, Done, Progress |
| Assignee | Assignee, Owner, Resource, Assigned To, Responsible |
| Predecessors | Predecessors, Depends On, Dependency, Blocked By, After |
| Milestone | Milestone |
| Notes | Notes, Description, Comment, Detail |
| Labels | Labels, Label, Tags, Tag, Category, Component, Stream, Workstream |
Matching is case-insensitive, punctuation-insensitive, and plural-aware, so
% Complete, %complete and Percent Completes all resolve to the same field.
Anything the importer cannot place is shown to you as unmapped rather than
silently dropped, and you can assign it yourself before committing.
Columns that can appear more than once
Section titled “Columns that can appear more than once”Most fields take exactly one column — two Name columns is a mistake, so the
second is reported as a duplicate and dropped rather than merged.
Labels and Predecessors are the exceptions. Both accept several source columns, and the values are unioned onto the task:
- A sheet carrying
Tags,ComponentandTeamgets all three as labels. - An MS Project export that spreads dependencies across
Predecessor 1andPredecessor 2gets both links. The same reference appearing in two columns is one relationship, not two.
If auto-detection places only one of them, map the others to the same field yourself in the column overrides.
Labels
Section titled “Labels”One cell can carry several labels, separated by , ; or / — the same
convention the assignee column uses. safety, rework becomes two labels.
Names are matched against your project’s existing label catalog,
case-insensitively, so importing a sheet that says Safety into a project
that already has safety reuses the existing label instead of creating a
near-duplicate you then have to merge. The catalog’s spelling wins. Labels that
do not exist yet are created and given distinct colors.
Labels are project-scoped: a label of the same name in another project is a different label and is not matched.
An import is capped at 100 distinct labels and 20 per task, and label names are shortened to 50 characters. Exceeding a cap does not fail the import — the excess is reported as a row warning and everything else is imported.
Rows that are skipped
Section titled “Rows that are skipped”- Blank rows — ignored entirely.
- Rows whose first cell starts with
#— treated as comments, so you can annotate the sheet without breaking the import.
How the hierarchy is read
Section titled “How the hierarchy is read”TruePPM builds a real WBS tree, and it recognizes the two ways spreadsheets actually encode one:
A WBS / outline column. Dotted codes map straight to the tree:
WBS Name1 Discovery1.1 Stakeholder interviews1.2 Requirements draft2 Build2.1 Data modelIndentation in the name column. Two spaces (or one tab, or one leading dot) per level — the convention most hand-built sheets use:
NamePhase One Design Build Backend FrontendBoth produce the same nested schedule.
How dependencies are read
Section titled “How dependencies are read”The predecessor column accepts the MS Project shorthand, comma- or semicolon-separated:
| Cell | Meaning |
|---|---|
3 | Finish-to-Start on task 3 |
3FS+2d | Finish-to-Start on task 3, with 2 days of lag |
5SS | Start-to-Start with task 5 |
9FS-1d | Finish-to-Start with 1 day of lead |
6,7 | Two Finish-to-Start predecessors |
References resolve against your ID column when the sheet has one, and against row position otherwise — so both an MS Project-style export and a hand-typed sheet work without changes.
Dates, durations and percentages
Section titled “Dates, durations and percentages”Durations may be written as 5, 5d, 5 days, 16 hours (converted on an
8-hour day) or 2 weeks. A duration of 0 marks the row as a milestone.
Percent complete accepts 50, 50%, or Excel’s native 0.5.
Dates are read as YYYY-MM-DD unambiguously. For slash dates the importer
scans the whole file before deciding: if any row is unambiguously
day-first (13/04/2026 cannot be a month), every slash date is read day-first.
With no evidence either way it assumes month/day/year and tells you which
convention it used in the import warnings — a silently-guessed date order is
a data-integrity bug, not a formatting detail.
The wizard
Section titled “The wizard”Getting to it
Section titled “Getting to it”There are two ways in, and which one you want depends on whether the project already exists.
Starting from a spreadsheet. Choose Create & import spreadsheet in the new-project flow. TruePPM creates the project and opens the wizard on arrival, so you go from “I have a plan in Excel” to mapping columns without stopping at an empty project in between. The same option is offered as Import a spreadsheet on the sidebar and the My Work screen when you have no projects yet — the two places you land as a brand-new user.
Importing into a project you already have. Open its Schedule, then Project actions → Import from spreadsheet (CSV/Excel)….
Both routes open the same wizard. The import always needs a project to import into, which is why the first route creates one for you rather than deferring it — but that is a detail of the plumbing, not a step you perform.
Importing requires the Scheduler role or above on the project. Creating a project does not require any project role (you become its Owner), so the create-and-import route is open to anyone who can create a project.
The three steps
Section titled “The three steps”The wizard walks the three steps below so you never have to call the API yourself.
Step 1 — Upload. Drag a .csv, .tsv, .txt, .xlsx, or .xlsm file onto
the drop zone, or pick one. Files over the size cap are rejected before the
upload runs, so you do not wait on a request that is certain to fail.
Download a template here if you want a known-good shape to paste into —
it is the same file the template endpoint serves.
Step 2 — Map columns. Every detected column is listed with the TruePPM field it will import into, already filled in from auto-detection — you confirm rather than map from scratch. Change any dropdown that is wrong, or set one to Don’t import to ignore that column. The first parsed rows sit underneath, so you can see what the mapping actually produces.
Columns the importer had to guess at are flagged Guessed — check this, and a
count above the table tells you how many are waiting on you. A column that lost a
tie for a field — two headers both matching Name, say — reads Not imported —
another column already uses this field, so it is never just mysteriously blank.
Columns matched exactly carry no note at all: silence is the confident state, and
badging all of them would bury the ones that need an eye.
Anything the parser decided about the file as a whole appears under How we read this file — which worksheet was read, how ambiguous dates were resolved, whether the row cap bit. These are not row problems; they are choices made on your behalf, and they are repeated on step 3 because that is the last screen before the import commits.
If a required field has no column mapped to it, Next is disabled and the wizard says which field is missing. This is deliberate: a spreadsheet with no recognizable task-name column imports zero tasks, and a silent no-op is a worse outcome than a blocked button.
Changing a mapping and pressing Re-check mapping re-runs the preview on the server, so what you confirm on step 3 is what the parser genuinely produced — not a client-side guess at what your change would do. Only the columns you changed are pinned; everything else is detected fresh, so a guess you never looked at still comes back flagged as a guess rather than quietly counting as reviewed.
Step 3 — Confirm and import. The wizard shows the row count, how many tasks and resources will be created, and names every column that will not be imported rather than dropping them quietly. Rows that will be lost and rows that will merely land with a field defaulted are counted separately, because they are different decisions.
If your file is over the row cap, the count reads 5,000 of 6,000 — never a
bare 5,000 that would look like the whole file arrived. What was dropped is
spelled out under How we read this file.
When it finishes, you get the number of tasks created and, if any rows had problems, a list of them by spreadsheet line number so you can fix them at source. View schedule takes you to the imported plan.
On a clean import — every row landed and the parser had nothing to report — that button is already focused, so Enter takes you straight to the schedule. When there is something to read, it is Close that is focused instead, so you are not walked past the line numbers on your way out.
The three endpoints
Section titled “The three endpoints”The wizard is a client of the same three endpoints, documented here for anyone scripting an import.
1. Preview — nothing is saved
Section titled “1. Preview — nothing is saved”POST /api/v1/projects/{id}/import/csv/preview/ parses the file and returns the
detected column mapping, the first ten parsed rows, the task and resource counts,
and any row-level problems. It writes nothing. Use it to confirm the mapping
before committing.
2. Override any column you disagree with
Section titled “2. Override any column you disagree with”Send a column_map alongside the file — a JSON object of
{"header": "field"} — to pin a column explicitly. An override always beats
auto-detection. Map a column to "" to have it ignored.
3. Commit
Section titled “3. Commit”POST /api/v1/projects/{id}/import/csv/ queues the import and returns
202 with an import_request_id. The import runs asynchronously; poll
GET /api/v1/projects/{id}/import/csv/{import_request_id}/ for its terminal
status and summary.
Partial success is a result, not a failure
Section titled “Partial success is a result, not a failure”A real spreadsheet has a few bad cells in it, and for spreadsheet data that’s the normal outcome — not an exceptional one. A row with an unreadable date, an unreadable duration, or a predecessor that matches nothing still imports, minus that one field, and the problem is reported against its row number — the same number you see in Excel’s row gutter.
{ "tasks_created": 5, "plan_tasks_created": 5, "parked_row_count": 0, "review_branch_name": "", "row_error_count": 2, "error_count": 0, "warning_count": 2, "row_errors": [ { "row": 3, "column": "Start", "code": "bad_date", "severity": "warning", "message": "Could not read 'not-a-date' as a date; the start was left unset." }, { "row": 5, "column": "Predecessors", "code": "unknown_predecessor", "severity": "warning", "message": "Predecessor '99' does not match any row; the link was skipped." } ]}Nothing is silently dropped: the Import review branch
Section titled “Nothing is silently dropped: the Import review branch”Some rows cannot become a task at all — today, a row with no name in the column you mapped to Name. Those rows are not discarded. They are imported into an Import review summary branch appended at the bottom of the outline, one task per row, each named for the spreadsheet row it came from and carrying that row’s original cell values in its notes.
1 Discovery 1.1 Stakeholder interviews 1.2 Requirements draft2 Build3 Import review ← added by the import 3.1 Row 7 — no task name 3.2 Row 9 — no task nameFixing one is an ordinary edit: rename it and it becomes a normal task, or delete it. Both are undoable and both broadcast to anyone else on the project, because they are the same operations you would use on any other task — the import creates no special repair mode. Once the branch is empty, delete it too.
The wizard says this before you commit, on the confirm step, so the branch is never something you discover afterwards.
Three summary fields describe it:
| Field | Meaning |
|---|---|
tasks_created | Every task row written, the review branch included. |
plan_tasks_created | Your plan alone — what “imported N tasks” is allowed to claim. |
parked_row_count | Rows that landed in the review branch instead. |
The preview endpoint reports the same split before you commit, as task_count
(the plan) and parked_row_count.
Undo an import
Section titled “Undo an import”The wizard’s result step carries an Undo import (⌘Z) action once a completed import has created or parked any rows. Applying it removes every row the import wrote — plan rows and Import review placeholders alike — except any you (or a teammate) have already edited since; those are left in place, and the confirmation names how many. Undo is a single step per import and is only offered for a completed import, never a failed or still-running one.
Severity: did the row become a task?
Section titled “Severity: did the row become a task?”Every diagnostic carries a severity, and it answers exactly one question:
| Severity | Meaning |
|---|---|
warning | The row imported, with one field dropped or defaulted — a bad date, an unreadable duration, a predecessor that matched nothing. |
error | The row did not join the plan. It is parked in the Import review branch instead, with its values intact. Today the only cause is a row with no task name. |
They are counted separately (error_count / warning_count) rather than totalled,
because the two are not the same event: seven warnings means seven tasks landed slightly
lossy, while one error means a row of your spreadsheet is waiting for you rather than
being part of the schedule. Both are returned by the preview endpoint too, so you can
see the split before committing.
The terminal screen groups the diagnostics by cause rather than listing one line per row — four hundred rows with the same wrong date format is one edit, not four hundred — and offers them as a CSV download so you can work through them next to your spreadsheet.
Only a structurally unusable file fails outright: no header row, no column that could be the task name, unreadable bytes, or a cyclic predecessor column — and in that case nothing is written.
Imported dates are a starting point, not gospel
Section titled “Imported dates are a starting point, not gospel”Dates you import are constraints the scheduling engine re-derives on the project’s calendar, so a date can move — a task starting on a non-working day will not stay there. The confirm step says so before you commit, whenever a column is mapped to a date field.
Who can import and export
Section titled “Who can import and export”- Importing tasks requires the Scheduler role or above (Scheduler, Admin, or Owner) on the destination project. Members and Viewers are denied. The preview endpoint is gated identically — it parses an uploaded file, so it is not a lighter-privilege surface just because it saves nothing.
- Importing risks requires the Member role or above (Member, Scheduler, Admin, or Owner).
- Exporting requires the same permission as the equivalent JSON export of the same data. A CSV export isn’t a lower-privilege action just because the file is smaller.
Limits
Section titled “Limits”| Limit | Default | Setting |
|---|---|---|
| Task import upload size | 10 MB | CSV_IMPORT_MAX_UPLOAD_MB |
| Task import data rows | 5 000 | CSV_IMPORT_MAX_ROWS |
Task import .xlsx uncompressed size | 100 MB | CSV_IMPORT_MAX_UNCOMPRESSED_MB |
| Risk import upload size | 2 MB | — |
| Risk import data rows | 500 | — |
Rows past the task-import cap are reported back to you as skipped, not silently dropped. A risk import over its cap is rejected outright rather than partially imported. See CSV / Excel import limits for operator configuration.
Excel specifics
Section titled “Excel specifics”- Only the first worksheet is imported. Extra sheets are ignored and you are told how many were skipped.
- Values, not formulas. The workbook is read with cached values, so a cell
containing
=SUM(...)imports as the number Excel last computed. .xlsxand.xlsmare accepted. The legacy.xlsformat is not — re-save it as.xlsxor export it as CSV.
Two more things will bite you when working with Excel, and neither is specific to TruePPM:
Your file opens as a single column
Section titled “Your file opens as a single column”If your regional settings use a semicolon as the list separator (common across much of Europe), Excel will open a comma-delimited file as one column. Fix it without changing the file:
- Open Excel, then Data → From Text/CSV (rather than double-clicking the file).
- Choose Comma as the delimiter.
TruePPM always writes comma-delimited files on purpose. Adapting the separator to each machine would mean the same file meant different things on different computers — which is exactly what disqualifies CSV as an authoritative format.
Excel changed your data
Section titled “Excel changed your data”Opening a CSV in Excel and saving it can silently alter the contents: leading zeros disappear, long numeric strings become scientific notation, and anything that looks like a date is rewritten in your machine’s local format.
TruePPM’s importers tolerate the common damage — including dates written back as
Jun 9, 2026. They can’t tolerate all of it. If an import produces unexpected
values, re-export from the source tool and import the file without opening it
in Excel first.
Download the template
Section titled “Download the template”Download a template on step 1 of the wizard saves a known-good CSV with the canonical headers and a worked example demonstrating nesting, a lagged dependency, a Start-to-Start link, a zero-duration milestone, and a multi-value Labels cell.
The same file is served by GET /api/v1/import-templates/csv/ for scripted use.
The endpoint requires authentication, so it needs your bearer token — pasting the
URL into a browser address bar will not work.
The file format
Section titled “The file format”Every CSV TruePPM writes or reads follows the same rules (RFC 4180):
| Separator | Comma |
| Encoding | UTF-8. A byte-order mark is accepted on import (Excel adds one) |
| Line endings | CRLF written; either accepted |
| Quoting | Fields containing a comma, quote, or newline are wrapped in "; embedded quotes are doubled |
| First row | Column headers. Their order doesn’t matter on import — only their names |
| Dates | YYYY-MM-DD |
| Blank rows | Skipped |
Rows starting with # | Skipped — use them for comments |
| Unrecognized columns | Ignored, with a warning. They never fail the import |
The full normative definition is the data interchange specification.
What CSV can’t do
Section titled “What CSV can’t do”Collected in one place, because these are properties of the format rather than things still to be built:
- One flat list per file. No nesting, no cross-references between entity types, no version marker.
- No identity, so no updates. Import always creates. There is no CSV path to modify a task or risk that already exists.
- No dependencies on export, and only simple predecessors on import — no lag, and not the four dependency types the scheduler supports.
- No comments, attachments, time entries, or history.
- Excel reformats data, so a file that has been opened and re-saved is no longer guaranteed to match what TruePPM wrote.
- Not a backup. No version, no manifest, no integrity check, and no way to represent most of a project.
- Calendars and working-time exceptions don’t import. Imported tasks use the project’s calendar.
- Baselines, sprints, and board columns don’t import. Set these up in TruePPM after import.
- Three-point (PERT) estimates don’t import. A single duration column maps to duration only.
- Cross-project dependencies don’t import. Import is scoped to one project.
- Resource rates, capacity, and calendars don’t import. Assignees are matched or created by name; everything else about them stays unset.
For anything on that list, use JSON export or an export bundle.
Related
Section titled “Related”- Data export — JSON seeds and full project bundles
- Risk register — the risk CSV workflow
- MS Project import & export — for schedules coming from Project
- Jira import — for issue sets coming from Jira Server / Data Center
- Bring your existing plan in — decision table for which importer to use
- Seed data schema — the canonical JSON format