TL/DR
Here is the Github repo for the transform script: zemzelett/focalboard-to-kanboard
Preface: Why?
I started using Focalboard last year around October when i started my time-tracker project.
Here is, in short, what bothered me with it:
- Over time the UI got slower the more tasks there where on the board. And we are speaking of around 50 tasks altogether…
- The UI is not mobile first nor is it bearable to use it there (not a KO criteria but nonetheless something i tend to do every now and then)
- The UI is super slow when transitioning tasks from column to column
- The UI is super slow when changing the order of tasks
- Focalboard has an overload of features which all seem somewhat incomplete
- There is a bug displaying a white page after not visiting a board for some time (which can be solved with a hard-reload, but that’s not what should be necessary)
All of these fed a desire to use something else.
When i first looked for this kind of software i found Kanboard as well and chose Focalboard over it for its nicer and seemingly more mature design.
I can’t say how well Kanboard will perform in the future but it can easily handle a fair amount of tasks on the board and the UI is much more fluid on Desktop and on Mobile! It doesn’t look as nice and fancy but that’s something one can customize if desired.
Preparation
I’ll save you some of the hiccups i’ve had via this short summary: At first i compared the export format for both softwares. At some point i had to realize that the Kanboard import format differs from its export format. This is something to consider when using Kanboard initially too!
Focalboard export files analysis
Focalboard has two types of export formats:
- archive Essentially a zip file containing some meta data and a JSONL file containing the complete board config and its content
- CSV A simple CSV containing all tasks of the board with all columns
currently chosen in the view where the export is triggered from.
It will never export the task description though!
The CSV seems fine if only the task titles are of interest otherwise it will fall short of the archive format.
Analysis of the archive format
The archive, in its essence, is a JSONL. JSONL is, simply put, a file with multiple JSON objects separated by newlines.
This is the first time i heard of this format; but i also don’t quite see the purpose in it to be honest. Why not just use a standard JSON-compliant array of objects to achieve basically the same but have it easily parseable by a JSON parser directly?
But i digress, here are the important observations in regards of this file:
- Of major interest are objects with the
type: "card"ortype: "text"if theirparentId’stypeis"card" - columns and tags are referenced via id’s
- definitions for these are in the very first JSON object (with
type: "board") inside ofdata.cardProperties
- definitions for these are in the very first JSON object (with
Kanboards import format
As mentioned before, the import format differs from the export format. Kanboard lets you download a template for it in its import dialog. It looks as follows:
|
|
An import has to have all of these columns present in that specific order or the import won’t work correctly!
Lucky me, it contains the Description column i longed for.
The migration
For the migration i wrote a Python script that takes a board’s archive and transforms its contents into an Kanboard import CSV.
So the migration steps for each board i had where as follows:
- Export Focalboard board in its archive format
- Run that file through the transform script
- Create a Kanboard board with the same columns (Important!)
- Import the CSV into a newly created Kanboard board
The script
The script is also available on Github: zemzelett/focalboard-to-kanboard
Parsing arguments
|
|
I’ll brush over these lines of code. These are just for convenience when running
the script. It should mostly speak for itself. If not,read
Python’s argparse documentation.
Extracting the boards JSONL
|
|
This section of code looks for the folder inside the zip archive, jumps into it and grabs hold of the reference to the first file in it. This is our JSONL file containing the board.
For reference look into
Python’s zipfile documentation.
Processing the Focalboard’s board content
|
|
Each line (line 48) in the JSONL is a valid JSON object we have to parse (line 49).
We are interested in objects of type board, card and text.
The board’s metadata is contained in the very first lines object with the type
board (line 51). In it we look for the cardProperties and extract Status
(the board columns) (line 53) and Tags (line 56).
Entries of type card are the actual tasks (lines 59-60).
Entries of type text are potentially descriptions for tasks (lines 61-62).
We’ll store all of them for now.
json.loads is able to deserialize a JSON string. Refer to
Python’s json documentation for
more insight.
Assembling the Kanboard import CSV
|
|
Before we can build all rows of the import CSV we have to connect the descriptions to their respective tasks. This happens in lines 84-86. We simply iterate over all “descriptions”. If the parentId is one of our tasks, we assign its title value to the task’s (now new) description property.
The loop at line 88 fills the rows with each task’s relevant properties.
Only line 108 might be of special interest here. It concatenates all of a task’s tags together separated by a comma.
For lines 112-114 refer to
Python’s csv documentation.