Overview
Allow users to migrate their existing tasks into TaskDial from Todoist, Trello, or Microsoft To-Do via file upload.
Approach: file-based import (not OAuth). The user exports their data from the source app and uploads the file to TaskDial. Parsing happens entirely client-side in the browser — the raw import data never touches the server. Imported tasks go through the normal encrypted task creation API, preserving E2EE.
Benefits over OAuth:
- No access tokens to manage or store
- No external API maintenance or rate-limit concerns
- Works even if the source service changes its API
- Privacy-preserving: TaskDial never has credentials to read a user's third-party account
Export formats
Todoist → CSV
How to export: Settings → Backups → Export as CSV
Relevant columns:
| Todoist column |
TaskDial field |
Notes |
CONTENT |
title |
Direct |
DESCRIPTION |
details |
Direct |
DATE |
date |
Parse to YYYY-MM-DD; tasks with no date → today |
DATE (time part) |
fixedStartTime |
Extract HH:MM if a time is present |
PRIORITY |
important |
4 (p1 in Todoist) → important: true; others → false |
Section rows (TYPE=section) |
tag |
Tasks under a section inherit the section name as their tag |
| — |
durationMinutes |
Default: 25 |
| — |
completed |
Todoist CSV only exports incomplete tasks; set false |
Skip rows where TYPE is not task.
Trello → JSON
How to export: Board menu → More → Print and export → Export as JSON
Relevant fields from cards[]:
| Trello field |
TaskDial field |
Notes |
name |
title |
Direct |
desc |
details |
Direct |
due |
date + fixedStartTime |
ISO datetime → split into date + time |
dueComplete |
completed |
Direct |
labels[0].name |
tag |
Use first label name; ignore colour |
List name (resolved from lists[] via idList) |
tag |
Fallback if no label; list name used as tag |
closed |
— |
Skip archived cards (closed: true) |
| — |
durationMinutes |
Default: 25 |
pos |
sortOrder |
Preserve relative order within each list |
The board export contains a top-level lists array and cards array. Match card.idList to list.id to get the list name.
Microsoft To-Do → CSV
How to export: Settings → Export data → downloads a ZIP with one CSV per list
Relevant columns:
| To-Do column |
TaskDial field |
Notes |
TaskName |
title |
Direct |
Notes |
details |
Direct |
DueDate |
date |
Parse to YYYY-MM-DD; no date → today |
ReminderDate |
fixedStartTime |
Extract time component if present |
Importance |
important |
"High" → true; "Normal" → false |
IsCompleted |
completed |
"True" → true |
| List filename |
tag |
Derive from the CSV filename (the list name) |
| — |
durationMinutes |
Default: 25 |
If the user exports a ZIP, unzip client-side (using a lib like fflate) and process each CSV as a separate list. Alternatively, accept individual CSV files and let the user import one list at a time.
UI
Entry point: Settings panel → new "Import" section (below existing settings groups).
Flow:
- User selects source (Todoist / Trello / Microsoft To-Do) via segmented control or radio group
- Brief instruction: "Export your tasks from [service] and upload the file below." + a link to the relevant export help page for each service
- File picker (drag-and-drop + click): accepts
.csv for Todoist/To-Do, .json for Trello, .zip for To-Do multi-list
- On file select: parse client-side, show preview table of tasks to be imported (title, date, tag columns; paginated if >20 rows)
- User can set a target date for tasks with no due date (default: today)
- Import button → bulk POST to
/api/tasks/bulk (see bulk endpoint note below)
- Progress indicator (n of N created) → success summary: "47 tasks imported"
- Errors shown inline per-task (if any fail, the rest still succeed)
Implementation notes
Always use the frontend encryption path
Imported tasks must always go through the frontend: parse client-side → encrypt via encryptTask() → POST to the internal /api/tasks/bulk endpoint. Do not route import through the public API (/api/v1/) — tasks created via the public API in v1 plaintext mode are stored unencrypted (see #56). Identical data imported via the UI vs. the public API would have different security properties, which would be confusing and inconsistent. The import UI must use the cookie-authenticated internal endpoint only.
Bulk endpoint — coordinate with #56
A /api/tasks/bulk internal endpoint is needed to avoid N individual API calls for large imports (cap at 500 tasks per request, single DB transaction). The public API (#56) may also want to expose a bulk creation endpoint at /api/v1/tasks/bulk. Design the internal endpoint first; the public version can wrap it. Keeping them separate allows the internal endpoint to remain cookie-auth only while the public endpoint uses API key auth.
Client-side parsing
Create app/src/services/importParsers.ts with three pure functions:
parseTodoistCsv(csvText: string): ImportedTask[]
parseTrelloJson(jsonText: string): ImportedTask[]
parseMicrosoftTodoCsv(csvText: string, listName: string): ImportedTask[]
Where ImportedTask is a partial Task type (no id, createdAt, updatedAt, sortOrder — those are assigned on creation).
Use the browser's built-in FileReader API for file reading. For CSV parsing, a lightweight lib like papaparse avoids edge cases with quoted fields and line endings.
E2EE
Parsing and field mapping happen in the browser. The ImportedTask[] array is passed to the existing task creation flow, which encrypts each task before sending. No special handling needed — imports are indistinguishable from manual task creation at the API level.
Duplicate detection
Out of scope for v1. Add a note to the import summary: "Re-importing the same file will create duplicate tasks."
Out of scope (for now)
- OAuth-based live sync with any service
- Incremental / delta imports
- Exporting TaskDial tasks back to these services
- Notion, Asana, TickTick, OmniFocus (could be follow-up issues)
- Preserving sub-task hierarchy (TaskDial has no sub-tasks)
Overview
Allow users to migrate their existing tasks into TaskDial from Todoist, Trello, or Microsoft To-Do via file upload.
Approach: file-based import (not OAuth). The user exports their data from the source app and uploads the file to TaskDial. Parsing happens entirely client-side in the browser — the raw import data never touches the server. Imported tasks go through the normal encrypted task creation API, preserving E2EE.
Benefits over OAuth:
Export formats
Todoist → CSV
How to export: Settings → Backups → Export as CSV
Relevant columns:
CONTENTtitleDESCRIPTIONdetailsDATEdateYYYY-MM-DD; tasks with no date → todayDATE(time part)fixedStartTimeHH:MMif a time is presentPRIORITYimportant4(p1 in Todoist) →important: true; others → falsesection)tagdurationMinutescompletedfalseSkip rows where
TYPEis nottask.Trello → JSON
How to export: Board menu → More → Print and export → Export as JSON
Relevant fields from
cards[]:nametitledescdetailsduedate+fixedStartTimedueCompletecompletedlabels[0].nametaglists[]viaidList)tagclosedclosed: true)durationMinutespossortOrderThe board export contains a top-level
listsarray andcardsarray. Matchcard.idListtolist.idto get the list name.Microsoft To-Do → CSV
How to export: Settings → Export data → downloads a ZIP with one CSV per list
Relevant columns:
TaskNametitleNotesdetailsDueDatedateYYYY-MM-DD; no date → todayReminderDatefixedStartTimeImportanceimportant"High"→true;"Normal"→falseIsCompletedcompleted"True"→truetagdurationMinutesIf the user exports a ZIP, unzip client-side (using a lib like
fflate) and process each CSV as a separate list. Alternatively, accept individual CSV files and let the user import one list at a time.UI
Entry point: Settings panel → new "Import" section (below existing settings groups).
Flow:
.csvfor Todoist/To-Do,.jsonfor Trello,.zipfor To-Do multi-list/api/tasks/bulk(see bulk endpoint note below)Implementation notes
Always use the frontend encryption path
Imported tasks must always go through the frontend: parse client-side → encrypt via
encryptTask()→ POST to the internal/api/tasks/bulkendpoint. Do not route import through the public API (/api/v1/) — tasks created via the public API in v1 plaintext mode are stored unencrypted (see #56). Identical data imported via the UI vs. the public API would have different security properties, which would be confusing and inconsistent. The import UI must use the cookie-authenticated internal endpoint only.Bulk endpoint — coordinate with #56
A
/api/tasks/bulkinternal endpoint is needed to avoid N individual API calls for large imports (cap at 500 tasks per request, single DB transaction). The public API (#56) may also want to expose a bulk creation endpoint at/api/v1/tasks/bulk. Design the internal endpoint first; the public version can wrap it. Keeping them separate allows the internal endpoint to remain cookie-auth only while the public endpoint uses API key auth.Client-side parsing
Create
app/src/services/importParsers.tswith three pure functions:Where
ImportedTaskis a partialTasktype (noid,createdAt,updatedAt,sortOrder— those are assigned on creation).Use the browser's built-in
FileReaderAPI for file reading. For CSV parsing, a lightweight lib likepapaparseavoids edge cases with quoted fields and line endings.E2EE
Parsing and field mapping happen in the browser. The
ImportedTask[]array is passed to the existing task creation flow, which encrypts each task before sending. No special handling needed — imports are indistinguishable from manual task creation at the API level.Duplicate detection
Out of scope for v1. Add a note to the import summary: "Re-importing the same file will create duplicate tasks."
Out of scope (for now)