Omni Automation: Prerequisites & Task Dependencies

Please note that Omni Automation for OmniFocus is still in development and details are subject to change before it officially ships. If you have questions, please refer to Omni's Slack #automation channel.


Though OmniFocus has options for sequential and parallel action groups which provide some capacity for dealing with tasks which are dependant on each other, on occasion these are insufficient for my needs.


I generally run up against two general scenarios where I find this set of automations useful:

  • Sometimes a task in one project can’t be started until a task in a second project has been completed; but the projects are nevertheless (at least in my mind, and often for the purpose of other workflows) distinct. (An example of this: accounting work for two related (but distinct) parties.
  • Sometimes the “dependency” relationship could be reflected using the built-in sequential and parallel task action group structure, but the level of complexity is such that the nesting of tasks becomes difficult to manage or just unwieldy to look at. (As an example: university coursework, which is often broken down into weekly components that should be completed sequentially. There are often also assignments attached to this that shouldn’t block further coursework. Sometimes there are optional readings, or only part of the coursework needs to be done before you can complete a particular part of an assignment.)

In addition, there are some scenarios (usually overlapping with the above) where the way I am thinking about a task is in terms of “sequential projects”. Again, university coursework is a good example here: to my way of thinking, each week’s content is its own project. But they still need to be done in order i.e. each week is dependant on the previous week.


I have written a plugin that contains a series of Omni Automation Scripts that use tags to designate one task as being dependant on another.

To achieve this, in my OmniFocus database I use three tags which are set up as subtags under a parent ‘Dependency’ tag:

  • Make Prerequisite: This is a temporary tag. Refer to the ‘Usage Notes’ below for details of how this is used.
  • 🔑: This tag denotes a task that is required to be completed before another (dependant) task becomes available.
  • 🔒 Dependant: This tag denotes a task that is currently unavailable because it is waiting for another task to be completed. This tag has an “on hold” status.

In addition, the ‘notes’ field is used to track more specific details of the prerequisite/dependant tasks:

  • A prerequisite task will have a note similar to [DEPENDANT: omnifocus:///task/elnhPEQEJzq] Task 2
  • A dependant task will have a note similar to [PREREQUISITE: omnifocus:///task/mEur73AEC07] Task 1

This means that a task can have more than one dependant or prerequisite task, which allows for greater complexity.

This solution also requires two Omni Automation scripts:

  • Add Prerequisite: This adds the ‘🔒 Dependant’ tag and adds the ‘🔑’ tag to the task that has been marked ‘Make Prerequisite’. It also prepends the details above to the notes of both tags. Then, it removes the ‘Make Prerequisite’ tag.
  • Complete For Prerequisite: This action (designed to be run on tasks once they have been completed) marks the task as done, finds any dependant tasks included in the note, removes the prerequisite from the dependant tasks’ note, and, if no other prerequisites remain for that task, removes the ‘🔒 Dependant’ tag. It also checks any parents of the task that might be completed at the same time, and does the same for those.

Future improvement: I am hopeful that in the future Omni Automation will allow us to automatically run a script every time a task is completed. That would eliminate the need to remember to run the ‘Complete For Prerequisite’ script, which would be great. In the meantime, I have also written a ‘Check Prerequisites’ script as a back-up. I run this daily as part of a ‘New Day’ script. It checks all the tasks that are waiting and, if all their dependent tasks have been completed, makes them available.

The Scripts

I have written this as an OmniFocus plugin which can be downloaded from here. In case you are curious I have included the scripts below, but if you like you can skip these and head down to ‘Set-Up’ below.

Add Prerequisite

(() => {
  var action = new PlugIn.Action(function (selection, sender) {
    config = this.dependencyConfig;

    // configure tags
    markerTag = config.markerTag();
    prerequisiteTag = config.prerequisiteTag();
    dependantTag = config.dependantTag();

    task = selection.tasks[0] || selection.projects[0].task;

    function makeDependant(task, prereqTask) {
      prereqTaskId =;
      task.addTag(dependantTag); // add waiting tag to selected note
      task.note =
        "[ PREREQUISITE: omnifocus:///task/" +
        prereqTaskId +
        " ] " + +
        "\n\n" +
        task.note; // prepend prerequisite details to selected note

      if (task.project !== null) {
        task.project.status = Project.Status.OnHold;

      // if dependant task has children:
      if (task.hasChildren) {
        if (task.sequential) {
          makeDependant(task.children[0], prereqTask);
        } else {
          task.children.forEach((child) => {
            makeDependant(child, prereqTask);

    // get all tasks tagged with 'prerequisite'
    prereqTasks = markerTag.tasks;

    prereqTasks.forEach((prereqTask) => {
      makeDependant(task, prereqTask);

      prereqTask.addTag(prerequisiteTag); // add tag to prerequisite
      prereqTask.note =
        "[ DEPENDANT: omnifocus:///task/" + +
        " ] " + +
        "\n\n" +
        prereqTask.note; // prepend dependant details to prerequisite note
      prereqTask.removeTag(markerTag); // remove marker tag used for processing;

  action.validate = function (selection, sender) {
    return (
      (selection.tasks.length === 1 || selection.projects.length == 1) &&
      this.dependencyConfig.markerTag().tasks.length >= 1

  return action;

Complete For Prerequisite

(() => {
  var action = new PlugIn.Action(function (selection, sender) {
    // if called externally (from script) generate selection object
    if (typeof selection == "undefined") {
      selection =[0].selection;

    task = selection.tasks[0] || selection.projects[0].task;

    // mark the task as complete

    // check dependants

  action.validate = function (selection, sender) {
    return selection.tasks.length === 1 || selection.projects.length === 1;

  return action;

Check Prerequisites

(() => {
  var action = new PlugIn.Action(function (selection, sender) {
    // config
    config = this.dependencyConfig;
    dependantTag = config.dependantTag();
    prerequisiteTag = config.prerequisiteTag();

    dependencyLibrary = this.dependencyLibrary;

    // get all remaining tasks that are waiting on prerequisites
    remainingTasks = [];
    dependantTag.tasks.forEach(function (task) {
      if (task.taskStatus === Task.Status.Blocked) {

    // for each task that is waiting:
    remainingTasks.forEach(function (dependentTask) {
      // use regex to find [PREREQUISITE: taskid] matches in the notes and capture task IDs
      regex = /\[ ?PREREQUISITE: omnifocus:\/\/\/task\/(.*?) ?\]/g;
      var regexArray = [];
      prerequisiteTasksArray = [];
      while ((regexArray = regex.exec(dependentTask.note)) !== null) {
        // for each captured task ID
        prerequisiteTaskId = regexArray[1];
        // get the task with that ID and push to array
        prerequisiteTag.tasks.forEach(function (task) {
          if ( == prerequisiteTaskId) {
            return ApplyResult.Stop;

      // or each prerequsite task that has been captured
      prerequisiteTasksArray.forEach((prerequisiteTask) => {

  action.validate = function (selection, sender) {
    // only valid if nothing is selected - so does not show in share menu
    return selection.tasks.length == 0 && selection.projects.length == 0;

  return action;

Functions Library

This is used to perform the checks in the ’Completed For Prerequisite’ and ‘Check Prerequisites’ scripts.

(() => {
  var dependencyLibrary = new PlugIn.Library(new Version("1.0"));

  dependencyLibrary.dependantTag = function () {
    return PlugIn.find("com.KaitlinSalzke.DependencyForOmniFocus")

  dependencyLibrary.checkDependants = (task) => {
    dependantTag = dependencyLibrary.dependantTag();

    var prerequisiteTask = task;

    if (prerequisiteTask.completed) {
      // use regex to find [DEPENDANT: taskid] matches in the notes and capture task IDs
      regex = /\[ ?DEPENDANT: omnifocus:\/\/\/task\/(.*?) ?\]/g;
      var regexArray = [];
      while ((regexArray = regex.exec(prerequisiteTask.note)) !== null) {
        // for each captured task ID
        dependantTaskId = regexArray[1];
        // get the task with that ID
        var dependantTask = null;
        dependantTag.tasks.forEach(function (task) {
          if ( == dependantTaskId) {
            dependantTask = task;
            return ApplyResult.Stop;

        function removeDependant(dependant, prerequisiteTask) {
          //get task ID of selected task
          prerequisiteTaskId =;
          // remove the prerequisite tag from the dependant task
          regexString =
            "[ ?PREREQUISITE: omnifocus:///task/" +
            prerequisiteTaskId +
            " ?].+";
          RegExp.quote = function (str) {
            return str.replace(/([*^$[\]\\(){}|-])/g, "\\$1");
          regexForNoteSearch = new RegExp(RegExp.quote(regexString));
          dependant.note = dependant.note.replace(regexForNoteSearch, "");
          // check whether any remaining prerequisite tasks listed in the note
          // (i.e. whether all prerequisites completed) - and if so
          if (!/\[ ?PREREQUISITE:/.test(dependant.note)) {
            // if no remaining prerequisites, remove 'Waiting' tag from dependant task
            // (and if project set to Active)
            if (dependant.project !== null) {
              dependant.project.status = Project.Status.Active;

          // if dependant task has children:
          if (dependant.hasChildren) {
            if (dependant.sequential) {
              removeDependant(dependant.children[0], prerequisiteTask);
            } else {
              dependant.children.forEach((child) => {
                removeDependant(child, prerequisiteTask);

        removeDependant(dependantTask, prerequisiteTask);

  dependencyLibrary.checkDependantsForTaskAndAncestors = (task) => {
    functionLib = PlugIn.find("com.KaitlinSalzke.functionLibrary").library(

    // get list of all "parent" tasks (up to project level)
    listOfTasks = [task];
    parent = functionLib.getParent(task);
    while (parent !== null) {
      parent = functionLib.getParent(parent);

    // check this task, and any parent tasks, for dependants
    listOfTasks.forEach((task) => {

  return dependencyLibrary;


  1. Create the three tags identified above, as sub tasks of a tag named ‘Dependency’ (in the ‘Solution’ section of this post). The dependant task tag should be set to ‘on hold’ to reflect the fact that you can’t make any progress on these items until the prerequisite task has been completed.
  2. Add the plugin file linked above to your OmniFocus plugin directory. If you need some more instructions on doing this, you can refer to this post.
  3. If your tag set-up is different to mine, you will need to alter the config script to reflect that. To do so, right click on the plugin file and click ‘Show Package Contents’, then navigate to ‘Resources/dependencyConfig.js’. Open the file in a text editor to update the tag names as indicated by the comments in the file. (To refer to a top-level tag, use tagNamed("Name"). If your tag is nested other other tags, you chain these together for as many levels as you need e.g. tagNamed("Activity Type").tagNamed("Activity Type: ⏳ Waiting").tagNamed("🔒 Other task")

Usage Notes

To make one task a prerequisite of another:

  1. Tag the prerequisite task with ‘Make Prerequisite’.
  2. Select the dependant task.
  3. Run the ‘Add Prerequisite’ Omni Automation Script.

Instead of a task complete, you can run the ‘Complete For Prerequisite’ script. (I have assigned a keyboard shortcut to make this easier)

If you would like to check for any dependant tasks at any other time, you can use the functions checkDependants or checkDependantsForTaskAndAncestors from the library file in your scripts.

Optionally, run the ‘Check Prerequisites’ script as a backup, perhaps as part of a daily or weekly review.

Update 2019-09-23: I have now “upgraded” these series of scripts into a single plugin for easier use. In addition, I have fixed a number of small issues and added some functionality: the actions now work with projects (not just tasks) and the “Complete For Prerequisite” script now also checks the action groups and project the task is contained in to see if they are prerequisites themselves and then determine whether there are any new dependant tasks available. The tags used have also been changed slightly. The information above has been updated to reflect these changes.

12 thoughts on “Omni Automation: Prerequisites & Task Dependencies”

  1. Great script ideas! I look forward to using such approaches to run automation on macOS and iOS.

    I have two thoughts, with a pardon up front because I am not conversant in the syntax or best practices of the scripting language. First, I see that the tags that are used are exposed at the start of the function calls. Would they be better placed as globals external to the function call? I am used to seeing such globals in pubic-distributions of the AppleScripts for OmniFocus. Secondly, how do the scripts handle cases where the note field already has content? For example, I have an AppleScript that populates the note field with a URL callback. This allows me to link projects or tasks to external content (in the case, figures associated with the project that are located on Kanban boards in Curio). Will your script respect the pre-existing note field content?

    My second question prompts me to think over my own approach in using the note field to hold URL callbacks. I have to say that at some point, I must wonder whether we will have to request that OmniGroup include user-defined meta fields for projects or tags. That way, we can avoid programming to handle cases where different scripts set/reset/delete the entire note field because they think it is entirely their own private meta-container. Alternatively, this question may give pause to think about the utility and eventual need for <meta=taskdependencies> container designations to surround the script content within the note field itself so as to restrict the potential destructive actions of scripts to their own boxes.

    1. Thanks Jeffrey! I am very excited by the possibilities that Omni Automation affords. It’s already very powerful and I suspect the Omni team still have a lot they would like to add. (I hope so!)

      In response to your first question: you are possibly right. I am a Javascript amateur myself (accountant by day; wannabe coder by night), so it’s more than likely that the code is imperfect. It may be the case that it would be best practice to place them outside the function call. I should do some experimentation and research on this and adjust if needed. (I actually would ideally put these in a config file separately too but I’m experiencing some inconsistency with getting that to work, so I need to play around with that a bit more.)

      Re the second question: I also like to use URLs (and occasionally actual notes) in the notes field, so the scripts add the [DEPENDANT:] or [PREREQUISITE:] tags to the beginning of the note and should leave everything else in tact. (You might, if anything, end up with some extra white space when a prerequisite task is marked as completed and the tag deleted from the dependent task—-the regular expressions might need a little tweaking in this regard.)

      I think that would be an interesting addition to OmniFocus and I think you make a valid point regarding the potentially destructive nature of using the notes field in this way and the possible options. For me I have chosen this format and will probably stick with it—-because at this stage I think it is unlikely to conflict with other scripts. [PREREQUISITE: omnifocus:///task/taskID] (or similar) is not something I expect to come up naturally in any of my notes in the near future!

      1. I find that using a header-type configuration is generally the easiest approach to maintain in scripts that I know may end up mostly in the hands of technically-knowledgeable users. It does however open the door to the potential for non-tech-inclined users to munge the code when all they wanted was to change a preference. The balance is that it avoids having to manage yet another file in addition to the files that are purely the code base.

        I like your approach to use [META-NAME: …] as a container for script variables in the notes field in OmniFocus. I will have to “borrow” it. I see an immediate benefit in my AppleScript that links Curio OmniFocus with URLs in the OF note field. It will allow me to avoid having to wipe the note field clean each time I make a link.

        As a colleague in another forum said, you have a text-parsing problem and decide to use REGEX to solve it. Congratulations, now you have two problems.

        1. I think I will still need to have configuration in the header that links to the config file, so I think as a compromise I will likely end up using this but with some configuration information/example settings commented out. I’m starting to use the same tags between scripts and I’d like to minimise fiddling if I decide to change the nesting or the name or something in my OF database.

          Borrow away!

    2. Incidentally, I’ve just done some quick testing and moving the tags used outside of the function calls throws an error and breaks the script. So, I think I’ll stick with leaving them where they are for now!

  2. Hi Kaitlin,

    This is great. I am a complete rookie when it comes to OF Automation but this has been a very helpful introduction. I would love to take this one step further and have the dependent task become DUE when the prerequisite action is completed as opposed to just available. Any help you can provide on how that might work would be much appreciated.

    1. Hi David!

      I believe if you add a line inside dependencyLibrary.js before dependantTask.removeTag(dependantTag); (i.e. line 46 on Github) that says:

      dependantTask.dueDate = new Date();

      That should achieve what you are aiming for!

      1. Awesome, thanks so much! Is there a way for me to access the code within depedencyLibrary.js to add that line?

        1. Hi David! Sorry for leaving you hanging! The post above links to an old version of the plugin (I need to update it and also write a proper README file), so I would recommend heading to the repository on Github (here:, downloading all the files – once unzipped you’ll be able to make the change in that file and then you will just need to make sure all the files are in a folder and rename that folder with the extension ‘.omnifocusjs’. Let me know if that explanation is unclear though!

          1. Hi Kaitlin, I have so been loving your dependency shortcut – life changing!! Unfortunately, for some reason it just stopped working for me all of a sudden. When I add the ‘make prerequisite’ tag to something and then go to another task to ‘add prerequisite’ via the plugin ‘add prerequisite’ is grayed out and not available to choose – any idea what could have caused this problem?

          2. Hi David! So glad you’re finding it useful! Not very helpful, I know, but this is still working for me, at least on OmniFocus 3.9.2 on macOS. If the menu item is greyed out it sounds like it is failing validation, which is unusual because the only conditions should be a) one project or task is selected and b) there is more than one task tagged with the ‘make prerequisite’ tag. It sounds like that should be the case! The only thing I might suggest is re-downloading from Github (and following the same steps in my last comment) to see if that fixes the issue for you? Sorry that answer is not more useful!

  3. Thanks for the response – yes, ultimately I ended up reinstalling it and got it to work again! Thanks again – it really is life changing!!

Leave a Reply