Org-mode as an interface to taskjuggler
Written by Sebastian Dümcke on
Tags: emacs
Introduction to taskjuggler
Regular readers might be aware of my preference for text based interfaces. So when I was tasked with managing a large project, I was natural for me to search for project management tools based on text. Back then I landed on org-mode. Org-mode is ideal for task management (for me). Now project management is mostly task management, however in some cases, we want more advanced features: automatic scheduling, resource and budget management, Gantt charts (very important), critical paths and much more. When this became relevant for me I searched again, particularly for Gantt tools compatible with org-mode or text based. My search turned out a few attempts to generate Gantt charts (TODO list them here). It also turned out a jewel that I want to present here in detail: taskjuggler . Taskjuggler, like many open-source projects is the labour of love of a single person, who maintains it since 2006. It is written in ruby and converts a text based project specification into a fully fledged project plan including all the bells and whistles one could need. Here is an extract of how the text format looks like:
project nil "Have an amazing summer vacation" "1.0" 2025-07-01 - 2025-08-31 { timezone "Europe/Berlin" currency "EUR" scenario plan "Plan" { scenario reality "Reality" } weekstartsmonday workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 } include "globals.tji" resource ME "Me" { limits {dailymax 1h} email "me@sam-d.com" } resource WF "My wife" { limits {dailymax 1h} } resource TA "Our travel agent" { limits {dailymax 1h} email "me@sam-d.com" chargeset travelbudget } task have "Have an amazing summer vacation" { task dest "choose destination" { purge allocate allocate ME, WF effort 4h } task flights "book flights" { depends !dest, !vaca purge allocate allocate TA milestone } task book "book accomodation" { depends !dest purge allocate allocate TA milestone } task vaca "take vacation" { depends !dest task at "at my job" { purge allocate allocate ME milestone } task my "my wife's job" { purge allocate allocate WF milestone } } task pack "pack bags" { depends !flights milestone } task order "order taxi to airport" { depends !flights purge allocate allocate TA milestone } task check "check vaccination status" { purge allocate allocate WF milestone } } include "reports.tji"
It can then turn this into a website with a Gantt chart, resource allocation and much more:
org-mode as an interface to taskjuggler
Now, having to learn a new syntax just for a single task is a strong ask/barrier of entry. Fear not, Emacs org-mode has your back. There already exist an export package ox-taskjuggler
that can export an org-mode tree into a taskjuggler project file (usually ending in .tjp
). Best of all, the export framework makes use of many common org-mode features you are already familiar with: start and end date of tasks are indicated with SCHEDULED
and DEADLINE
cookies, most remaining elements through property drawers. This makes using taskjuggler a breeze. The tjp
file about was actually generated from the following org file
* Have an amazing summer vacation :taskjuggler_project: DEADLINE: <2025-08-31 Sun> SCHEDULED: <2025-07-01 Tue> :PROPERTIES: :currency: "EUR" :workinghours: mon - fri 19:00 - 21:00 :scenario: plan "Plan" { scenario reality "Reality" } :weekstartsmonday: :timingresolution: 15 min :chargeset: travelbudget :END: #+begin_src taskjuggler :tangle globals.tji account travelbudget "Travel expenses" account bank "Available Budget" { credits 2025-07-01 "Our travel budget" 2400} balance bank travelbudget #+end_src #+begin_src taskjuggler :tangle reports.tji textreport report "Plan" { formats html header -8<- == Our vacation project plan == ->8- center -8<- [#Plan Plan] | [#Resource_Allocation Resource Allocation] | [#Accounts Accounts] ---- === Plan === <[report id="plan"]> ---- === Resource Allocation === <[report id="resourceGraph"]> ->8- right -8<- === Accounts === Overview of Project Costs vs Cash on hand at beginning of project <[report id="pl"]> ->8- } # A traditional Gantt chart with a project overview. taskreport plan "" { headline "Gantt" columns bsi, name, start, end, effort, complete, gauge, chart loadunit shortauto #loadunit days hideresource 1 #hidetask (treelevel() > 3) | (treelevel() = 1) scenarios plan } # A graph showing resource allocation. It identifies whether each # resource is under- or over-allocated for. resourcereport resourceGraph "" { headline "Resource Allocation Graph" balance travelbudget bank columns no, name, effort, daily loadunit hours hidetask ~(isleaf() & isleaf_()) sorttasks plan.start.up scenarios plan } accountreport pl "ProfitAndLoss" { formats html balance travelbudget bank columns no, name, weekly } #+end_src ** DONE choose destination CLOSED: [2025-07-30 Wed 16:59] :PROPERTIES: :TASK_ID: dest :EFFORT: 3:00 :ALLOCATE: ME, WF :END: ** TODO book flights :PROPERTIES: :TASK_ID: flights :DEPENDS: dest, vaca :ALLOCATE: TA :EFFORT: 2h :charge: 1200 onend :END: ** TODO book accomodation :PROPERTIES: :DEPENDS: dest :ALLOCATE: TA :EFFORT: 2h :END: ** take vacation :PROPERTIES: :TASK_ID: vaca :DEPENDS: dest :END: *** TODO at my job :PROPERTIES: :ALLOCATE: ME :EFFORT: 0.5h :END: *** TODO my wife's job :PROPERTIES: :ALLOCATE: WF :EFFORT: 0.5h :END: ** TODO pack bags :PROPERTIES: :DEPENDS: flights :EFFORT: 6h :ALLOCATE: ME, WF :END: ** TODO order taxi to airport :PROPERTIES: :DEPENDS: flights :ALLOCATE: TA :EFFORT: 0.25h :END: ** TODO check vaccination status :PROPERTIES: :ALLOCATE: WF :EFFORT: 3.5h :END: ** fly off SCHEDULED: <2025-07-20 Sun> :PROPERTIES: :TASK_ID: fly :END: ** TODO enjoy vacation :PROPERTIES: :duration: 2w :TASK_ID: trip :DEPENDS: fly :END: ** return from vacation ** TODO review pictures :PROPERTIES: :DEPENDS: trip :EFFORT: 2h :ALLOCATE: ME, WF :END: * My amazing team :taskjuggler_resource: ** Me :PROPERTIES: :resource_id: ME :email: "me@sam-d.com" :limits: {dailymax 1h} :END: ** My wife :PROPERTIES: :resource_id: WF :limits: {dailymax 1h} :END: ** Our travel agent :PROPERTIES: :resource_id: TA :email: "me@sam-d.com" :rate: 400 :END:
Notice a few important tags: taskjuggler_project
and taskjuggler_resource
. The first one define the heading which corresponds the the actual project and will get exported. The second one tags the subtree containing all resource definition. The latter needs to be a level 1 heading, for some reason!
Using org-mode column view, one can easily edit common properties for each task such as the effort (property EFFORT
, in any time unit org understands), the allocated resource (property ALLOCATE
), the task id and dependencies (properties TASK_ID
and DEPENDS
). Other properties can be found in the very detailed manual.
ox-taskjuggler defines global project properties and the report definitions in variables the string content of which gets spliced into the output but I recommend to export the report definition and global project properties through a code block to keep the detail in-tree and out of the config. These blocks are then tangled (see the example org file above) to the files globals.tji
and reports.tji
.
Then the following config sets the relevant includes and attributes to export for each task.
(require 'ox-taskjuggler) (setq org-taskjuggler-default-global-properties "include \"globals.tji\"") (setq org-taskjuggler-default-reports '("include \"reports.tji\"")) (setq org-taskjuggler-valid-resource-attributes '(limits vacation email shift booking efficiency journalentry rate chargeset workinghours flags))
Conclusion
Creating a task structure in org-mode and exporting to a taskjuggler file allows us to use familiar interface and known keyboard short-cuts to rapidly create a detailed project plan. This is enabled by org-mode being a strong outliner to create the hierarchical work breakdown structure and its ability to visualise task attibures on the form a a table with the org-column-view overlay. The last part is difficult to describe with words. A video would do this more justice. I have used this setup to plan and communicate with my team in the past. The resulting self-contained html file produced by taskjuggler can easily be uploaded to a webhost (internal or external) and viewed by everybody. Thus each stakeholder is aware of the state of the project.
I hope it helps you too!