Vikunja Importer
I talked about 10+ Asana alternatives here. Most the solutions do not provide a direct import from Asana and one solution that I have been using a lot lately is Vikunja.
As no importer exists and Vikunja provides an extensive API, I decided to build an importer myself. The importer allows to import from Asana CSV exports or directly through the Asana API.
What about the CLI
I started with a CLI version last year, but quickly decided to make a small App out of it.
I will have to backport the latest features to the CLI-Version, as its a nice way to keep Asana and Vikunja in sync using a CRON.




Task mapping
Vikunja stores most of these separately from the task itself, so each is its own API call:
Asana | → Vikunja | Notes |
|---|---|---|
Task name / notes | title / description | Rich text ( html_notes) is preferred over plain notes; real line breaks are converted to without corrupting inline formatting, and Asana’s wrapper is stripped. |
Custom fields | Appended to the description | Vikunja has no custom fields, so they’re listed under an „Asana custom fields“ heading — except Priority, which maps to Vikunja’s own priority field (see below). |
Section / Column | A bucket (kanban column) | Buckets are created up front in source order ( position increases per section) so columns don’t come out reversed. Cards keep their source order inside the column too — a subtask sits directly under its parent, not at the bottom. |
Completed | done, gated by closedMode | Vikunja ties completion to the board: a done task lives in the Done column, and moving it back into a normal column re-opens it — so a closed task is either done or in its Asana section, never both. closedMode: 'done' (default) marks it done; 'section' keeps its Asana column and leaves it open. |
— subtasks of a closed task | closeSubtasks option | Asana allows a completed task to keep open subtasks; with this on (default, done mode only) its subtasks are closed too. |
Due / start date | due_date / start_date (RFC 3339) | |
Priority | Vikunja’s numeric priority (1–5) | Mapped from a custom field named Priority ( low/medium/normal/high/urgent/critical/do now/asap). |
Tags | Labels, including colour | An existing label with the same name is reused — and given Asana’s colour only if it doesn’t already have one, so a colour you picked in Vikunja is never overwritten. Both of Asana’s colour palettes (tags, and projects/portfolios) are mapped to hex. |
Assignee | Assignee, matched by exact email or username | Never guesses between ambiguous matches; an assignee without a matching Vikunja account is reported at the end and the task is left unassigned. |
Subtasks | Own tasks, linked to their parent | Vikunja stores parent/child as a task relation ( parenttask), applied in a second pass once every task exists (so both ends have a Vikunja id). |
Dependencies | A blocked relation | Asana „depends on“ → Vikunja „blocked by“, same second pass as subtasks. |
Team | A Vikunja team, shared onto the project (read & write) | Reused if a team with that name already exists. Vikunja doesn’t add the creator to a team made via the API, so the importer adds itself as admin; other members are matched by email like assignees. (API source only.) |
Comments (stories) | Comments | Vikunja always attributes a comment to the token owner (it can’t impersonate an author), so the original author and date are appended as text instead. (API source only.) |
Attachments (files & images) | Attachments | Uploaded via curl + CURLFile (PHP’s stream wrapper can’t produce the multipart body Vikunja needs). Files over 25 MB or without a download URL (external links) are reported and skipped. (API source only.) |
Project overview | The Vikunja project description | Same HTML/line-break handling as a task description. |
What’s not imported
Some Asana concepts have no Vikunja counterpart and are left behind:
- Portfolios, rules/automations, forms, approvals, status updates, reactions — no equivalent.
- Milestones — Vikunja has no milestone task type.
- Time tracking (estimated / actual time).
- Multi-homing — a task in several Asana projects is imported into the one project selected,
and takes its section from that project (not from another project it also belongs to). - Followers / collaborators — only the assignee is carried over.
- System activity („X assigned this to Y“) — only real comments are imported.
- External-link attachments (Google Drive, Dropbox, …) — no file bytes to download; reported as skipped.
The App
The app copies an Asana project into Vikunja — tasks, subtasks, sections, tags, assignees, comments and file attachments. Everything below explains exactly what is imported and how it ends up in Vikunja.








Here a copy of the help page.
1 · Connect to Vikunja
- In Vikunja, go to Settings → API Tokens and create a token. Give it read/write permission for projects, tasks, labels and users.
- In this app’s Settings tab, paste your Vikunja URL and the token.
- Press Test connection (bottom-right). It should say “Connected as <your user>”.
The URL can be either your site (https://vikunja.example) or the API base (https://vikunja.example/api/v1) — the missing /api/v1 is added automatically.
2 · Choose a source
SOURCE | WHAT IT CAN IMPORT |
|---|---|
Asana CSV file | Tasks, subtasks, sections, tags, assignees, dates, completed state. No comments and no files — an Asana CSV export simply doesn’t contain them. |
Asana API | Everything, including comments and file attachments. Needs an Asana personal access token (Settings). You then pick a workspace and a project. |
Get an Asana token at Asana → Settings → Apps → Developer apps → Personal access token.
3 · Choose the target
Under Vikunja project, either pick an existing project or leave it on ➕ Create a new project (it’ll be named after the Asana/CSV project, or the name you type).
View type decides which Vikunja view receives the columns. Asana sections become Vikunja buckets, and buckets are the columns of the Kanban view — so Kanban is the right choice for almost everyone. Your tasks appear in every view regardless.
4 · Dry run first
Leave Dry run ticked and press Run import. Nothing is written to Vikunja — the log lists every project, bucket, task, label, comment and file it would create, so you can check the mapping. Files are only listed (with their size); they are not downloaded during a dry run.
When it looks right, untick Dry run and run it for real. (A licence is required for real imports — see below.)
What gets imported, and how
ASANA | BECOMES IN VIKUNJA |
|---|---|
Task name | The task title. |
Notes | The task description, with the formatting and line breaks preserved. A “View in Asana” link back to the original task is appended, unless you turn that off. |
Section / Column | A bucket (Kanban column). Created once per section, then the task is moved into it. The columns — and the cards inside them — keep the order they had in Asana. |
Completed | Depends on Closed Asana tasks — see below. By default the task is marked done. |
Due / start date | The task’s due / start date, converted to Vikunja’s format. |
Priority | Vikunja has a real priority field; Asana doesn’t, so a custom field named Priority is mapped onto it (low, medium/normal, high, urgent/critical, do now/asap). |
Subtasks | Imported as their own tasks, then linked to their parent — Vikunja stores a parent/child link as a task relation. They’re placed directly below their parent on the board. |
Dependencies | Asana “depends on” becomes a Vikunja blocked by relation. Applied after all tasks exist, since both ends need to be there. |
Team | The Asana team owning the project becomes a Vikunja team (reused if one with that name exists). Its members are added, and the project is shared with the team (read & write). |
Tags | Labels, including the tag’s colour. An existing label with the same name is reused (and coloured only if it hasn’t got a colour yet, so one you picked yourself is never overwritten); a missing one is created, then attached to the task. |
Assignee | Matched to a Vikunja user by exact email (or username). |
Custom fields | Vikunja has none, so they’re appended to the task description as a small list. |
Comments | Vikunja comments, one per Asana comment. |
Attachments | Downloaded from Asana and uploaded as Vikunja attachments. |
Project overview | Becomes the Vikunja project description. |
Closed tasks: done, or in their section
Vikunja ties completion and the Kanban column together: marking a task done moves it into the Done column, and moving it back into a normal column re-opens it. A closed Asana task can therefore be done or in its Asana section — never both. Closed Asana tasks lets you pick which one matters more:
SETTING | RESULT |
|---|---|
Mark as done (default) | The task is done, and sits in Vikunja’s Done column. Its Asana section is not kept. |
Keep in their Asana section | The task stays in the column it had in Asana, but Vikunja shows it as open. |
Asana also allows a completed task to still have open subtasks. With Also close the subtasks of a closed task ticked (the default), those subtasks are closed too. Untick it to keep them exactly as Asana has them.
The subtask option only does anything in Mark as done mode — in “keep in their section” mode nothing is marked done in the first place.
Re-running an import
Every imported task carries a hidden reference to the Asana task it came from, so a second run into the same project knows exactly what it already created. Tasks already imported decides what happens to those:
SETTING | RESULT |
|---|---|
Skip them (default) | Left untouched. Only Asana tasks that are new since the last run are added. Nothing is duplicated. |
Update them from Asana | Brought back in line with Asana: title, description, dates, priority and completion are refreshed, and anything missing is added — labels, an assignee (if the task has none), new comments and new files. |
Import again | Every task is created a second time. Useful for a clean re-import, but it will duplicate anything already there. |
Update is additive. It never deletes: a label, comment or assignee you added in Vikunja is kept, and an assignee already on the task is never replaced — an import shouldn’t quietly undo work done in Vikunja. Comments are matched on their Asana id, so re-running never posts the same comment twice, and files are matched by name so they aren’t re-uploaded.
This only applies when you import into an existing project. Creating a new project each time will, of course, give you a fresh copy every time.
What Asana things are not imported
Some Asana concepts simply have no Vikunja counterpart, so they’re left behind:
- Portfolios, rules/automations, forms, approvals, status updates and reactions — no equivalent in Vikunja.
- Milestones — Vikunja has no milestone task type.
- Time tracking (estimated / actual time).
- Multi-homing — a task that lives in several Asana projects is imported into the one project you selected, and takes its section from that project.
- Followers / collaborators — only the assignee is carried over.
- System activity (“X assigned this to Y”) — only real comments are imported.
- Attachments that are external links (Google Drive, Dropbox, …) — there are no file bytes to download, so they’re reported as skipped.
Vikunja stores labels, assignees, comments and attachments separately from the task, so each one is imported with its own API call. That’s why they’re individually toggleable under Also import — turning one off simply skips those calls.
Comments and their authors
The original author cannot be preserved. Vikunja always attributes a comment to the account whose API token is performing the import — that’s you. Its API offers no way to post a comment “as” somebody else, so an imported comment can never appear under a colleague’s name.
Instead, the Asana author and date are appended to the comment text:
Looks good to me— Alex Gräf, 2026-02-01 (imported from Asana)
Assignees
An Asana assignee is matched to a Vikunja user by exact email address (and, failing that, an exact username). Near-matches are never used, so nobody is assigned to the wrong person.
If no Vikunja account matches, the task is still imported — just unassigned — and every unmatched person is listed at the end of the log so you know who to invite.
Vikunja only lets you find a user by email if that user enabled it in their own settings. So a perfectly valid colleague’s email can legitimately match nobody. Once they have an account (and are a member of the project), re-run the import to assign them.
Files & images
- Only available from the Asana API source — a CSV export contains no files.
- Each file is downloaded from Asana and re-uploaded to the Vikunja task as an attachment. Nothing is kept on your machine afterwards.
- Files larger than 25 MB are skipped (and reported in the log).
- Off by default — downloading every attachment is slow on large projects.
- A dry run lists each file with its size but downloads nothing.
Licence
Without a licence the app runs as a trial: it always works, but is forced into dry-run mode, so nothing is ever written to Vikunja. Press Activate in the banner and enter your key to unlock real imports.
A licence is bound to this machine and uses one seat. To move it to another computer, open System Check → Deactivate this machine (this frees the seat), then activate the same key on the other machine.
