Tags

In OmniFocus, a tag represents an association that a task has to the world around it. A tag could represent a person, place, or thing most relevant to completion of a project, or it could represent a mindset most applicable to execution of an action item.

An item can have as many tags as you find useful, and there is no specific purpose assigned to them; use tags to assign priority, group items by energy level or required equipment, or don’t use them at all.

The Tags Perspective displays a list of your tags in the sidebar, and a list of all your actions grouped by the tags they belong to in the outline. Here is a script for setting the value of the perspective property of the Window class so that the Tags perspective is displayed:

omnifocus://localhost/omnijs-run?script=try%7Bdocument%2Ewindows%5B0%5D%2Eperspective%20%3D%20Perspective%2EBuiltIn%2ETags%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Show Tags Perspective
 

document.windows[0].perspective = Perspective.BuiltIn.Tags
To derive an object reference to a tag by name, use the flattenedTags property of the Database class in a conditional statement that creates a new tag with a specific name if an existing tag does not exist. The result will be an object reference to the named tag:
Tag Reference by Name


var tag = flattenedTags.byName("Conifer") || new Tag("Conifer")

Class Properties of the Tag Class

Here are the properties of the Tag class:

In the View Options panel for the Forecast perspective, you can assign an existing tag to used as the “forecast tag.” Once this tag is assigned to an item, the tagged item will appear in the Forecast view. Using Omni Automation, you can add the forecast tag to a task or project.

Assign Forecast Tag to Task
 

var task = inbox.byName("Car Repair") if(task && Tag.forecastTag){task.addTag(Tag.forecastTag)}

If no tag has been assigned to be the “forecast tag” the value of this property will be returned as: null. Also note that the forecastTag property is read-only and its value cannot be set via a script.

Instance Properties of the Tag Class

Here are the properties of an instance of the Tag class:

Properties of the Tag.Status class:

A script that gets the value of the availableTasks property of the selected tag:

Available Tasks of Selected Tag


var tgs = document.windows[0].selection.tags tsks = (tgs.length === 1) ? tgs[0].availableTasks : null

A script that gets the value of the projects property of the selected tag:

Projects of Selected Tag


var tgs = document.windows[0].selection.tags projects = (tgs.length === 1) ? tgs[0].projects : null

Iterating Tags

Tag Groups are tags that contain other tags. References to top-level tags are retrieved through the use of the tags property of the Database class, as in this example for processing the top-level tags. Since the OmniFocus is the implied topmost scripting element, the values of one of its property can be accessed by simply entering the property name (such as tags) in the console:

The “tags” Database Property


tags //--> [[object Tag: Ionian],[object Tag: Phrygian],[object Tag: Lydian],[object Tag: Mixolydian],[object Tag: Aeolian],[object Tag: Locrian]]

A script example using the JavaScript forEach() function to iterate the array of top-level tags:

Iterate Top-Level Tags


tags.forEach(tag =>{ // processing statements go here })

A script example using the JavaScript map() function to retrieve the names of the top-level tags:

omnifocus://localhost/omnijs-run?script=var%20tagNames%20%3D%20tags%2Emap%28tag%20%3D%3E%20tag%2Ename%29%0Aconsole%2Elog%28tagNames%29
Names of Top-Level Tags
 

tagNames = tags.map(tag => tag.name) console.log(tagNames)
To return the name of all tags in the database, iterate the value of the flattenedTags property of the Database class;
omnifocus://localhost/omnijs-run?script=var%20tagNames%20%3D%20flattenedTags%2Emap%28tag%20%3D%3E%20tag%2Ename%29%0Aconsole%2Elog%28tagNames%29
Names of All Tags
 

tagNames = flattenedTags.map(tag => tag.name) console.log(tagNames)

Since the existence of multiple tags sharing the same name is allowed in OmniFocus, here’s a script that creates an instance of the Set class to remove all duplicates from the tag name list. NOTE: this does not remove duplicate tags from the database, just duplicate tag tiltes in the generated name list:

omnifocus://localhost/omnijs-run?script=try%7BtagNames%20%3D%20new%20Array%28%29%0Atags%2Eapply%28tag%20%3D%3E%20tagNames%2Epush%28tag%2Ename%29%29%0AtagNames%20%20%3D%20Array%2Efrom%28new%20Set%28tagNames%29%29%20%0Aconsole%2Elog%28tagNames%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Get All Unique Tag Names
 

tagNames = flattenedTags.map(tag => {return tag.name}) tagNames = Array.from(new Set(tagNames)) console.log(tagNames)

Reference Tag by Name

To provide the ability to reference a specific tag by name, the tagNamed(…) function is included with both the OmniFocus Database class and the Tag class:

Referencing Tags by Name


// Top-Level Not Existing tagNamed("Zebra") //--> null // Top-Level Exists tagNamed("Locrian") //--> [object Tag: Locrian] // 1st Tag in Database Matching Name flattenedTags.byName("Locrian") //--> [object Tag: Locrian] // Tags within Tag Group tagNamed("Locrian").children //--> [[object Tag: Minor],[object Tag: Major]] // Tag within Tag Group tagNamed("Locrian").tagNamed("Major") //--> [object Tag: Major] // 1st Matching Tag within Tag Group tagNamed("Locrian").flattenedTags.byName("Major")

Create New Tag

Instances of the Tag class are created using the standard JavaScript new item constructor.

omnifocus://localhost/omnijs-run?script=try%7Btag%20%3D%20new%20Tag%28%22LAST%20BY%20DEFAULT%22%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
New Top-Level Tag at End of Tags
 

tag = new Tag("LAST BY DEFAULT")
omnifocus://localhost/omnijs-run?script=try%7Btag%20%3D%20new%20Tag%28%22FIRST%20TAG%22%2C%20tags%2Ebeginning%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
New Top-Level Tag at Beginning of Tags
 

tag = new Tag("FIRST TAG", tags.beginning)

Using a JavaScript logical operator (||) to reference a top-level tag by name, or creating it if it does not exist. As explanation, A || B returns the value A if A can be coerced into true; otherwise, it returns B. Therefore, in the example below, if the tag “ZEBRA” exists, a reference to it will be returned, otherwise a new tag named “ZEBRA” will be created and a reference to the created tag will be the result that is stored in the variable.

omnifocus://localhost/omnijs-run?script=try%7Btag%20%3D%20tagNamed%28%22ZEBRA%22%29%20%7C%7C%20new%20Tag%28%22ZEBRA%22%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Reference to Tag Either Existing or Created
 

tag = tagNamed("ZEBRA") || new Tag("ZEBRA")

And a variation of the previous script that searches the entire database for an existing tag, creating a new one if a match is not found:

Reference to Tag Either Existing or Created


tag = flattenedTags.byName("ZEBRA") || new Tag("ZEBRA")

Using the previous technique to add tags to either an existing named tag or a newly created tag with the target name:

omnifocus://localhost/omnijs-run?script=try%7BtagGroup%20%3D%20tagNamed%28%22Fall%20Colors%22%29%20%7C%7C%20new%20Tag%28%22Fall%20Colors%22%29%0Acolors%20%3D%20%5B%22Pale%20Blue%22%2C%22Harvest%20Gold%22%2C%22Autumn%20Gray%22%5D%0Acolors%2EforEach%28%28color%29%3D%3E%7B%0A%09tagGroup%2EtagNamed%28color%29%20%7C%7C%20new%20Tag%28color%2C%20tagGroup%2Ebeginning%29%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Adding Tags to a Unique Top-Level Tag Group
 

tagGroup = tagNamed("Fall Colors") || new Tag("Fall Colors") colors = ["Pale Blue","Harvest Gold","Autumn Gray"] colors.forEach(color => { tagGroup.tagNamed(color) || new Tag(color, tagGroup.beginning) })

And here’s a related script that uses the moveTags() function of the Database class to sort the tags in the previously created tag group by name:

Sort Tags in Tag Group by Name


var tagGroup = tagNamed("Fall Colors") if(tagGroup){ tgs = tagGroup.children if (tgs.length > 1){ tgs.sort((a, b) => { var x = a.name.toLowerCase(); var y = b.name.toLowerCase(); if (x < y) {return -1;} if (x > y) {return 1;} return 0; }) moveTags(tgs, tagGroup) } }

The following script demonstrates how to generate an array of tag object references to the tags corresponding to an array of tag titles:

Object References for Tags


tagNames = ["Bow","Stern","Port","Starboard"] tagObjects = new Array() tagNames.forEach(title => { tag = flattenedTags.byName(title) || new Tag(title) tagObjects.push(tag) }) console.log(tagObjects)

Assigned/Unassigned Tags

“Assigned” tags are those that have a positive value for the remainingTasks property.

Assigned Tags


assignedTags = flattenedTags.filter(tag => { if(tag.remainingTasks.length > 0){ return tag } })
Unassigned Tags


unassignedTags = flattenedTags.filter(tag => { if(tag.remainingTasks.length === 0){ return tag } })

Creating Tag Groups

A Tag Group is a tag that contains other tags. Omni Automation is useful for quickly creating one or more tag groups. In the example below, tag groups are created for regions of the United States, and the appropriate states are added to their respective region tag group.

omnifocus://localhost/omnijs-run?script=try%7Bvar%20regions%20%3D%20%5B%22Northeast%22%2C%20%22Southeast%22%2C%20%22Midwest%22%2C%20%22Southwest%22%2C%20%22West%22%5D%0A%0Avar%20stateGroups%20%3D%20%5B%5B%22Maine%22%2C%20%22Massachusetts%22%2C%20%22Rhode%20Island%22%2C%20%22Connecticut%22%2C%20%22New%20Hampshire%22%2C%20%22Vermont%22%2C%20%22New%20York%22%2C%20%22Pennsylvania%22%2C%20%22New%20Jersey%22%2C%20%22Delaware%22%2C%20%22Maryland%22%5D%2C%5B%22West%20Virginia%22%2C%20%22Virginia%22%2C%20%22Kentucky%22%2C%20%22Tennessee%22%2C%20%22North%20Carolina%22%2C%20%22South%20Carolina%22%2C%20%22Georgia%22%2C%20%22Alabama%22%2C%20%22Mississippi%22%2C%20%22Arkansas%22%2C%20%22Louisiana%22%2C%20%22Florida%22%5D%2C%5B%22Ohio%22%2C%20%22Indiana%22%2C%20%22Michigan%22%2C%20%22Illinois%22%2C%20%22Missouri%22%2C%20%22Wisconsin%22%2C%20%22Minnesota%22%2C%20%22Iowa%22%2C%20%22Kansas%22%2C%20%22Nebraska%22%2C%20%22South%20Dakota%22%2C%20%22North%20Dakota%22%5D%2C%5B%22Texas%22%2C%20%22Oklahoma%22%2C%20%22New%20Mexico%22%2C%20%22Arizona%22%5D%2C%5B%22Colorado%22%2C%20%22Wyoming%22%2C%20%22Montana%22%2C%20%22Idaho%22%2C%20%22Washington%22%2C%20%22Oregon%22%2C%20%22Utah%22%2C%20%22Nevada%22%2C%20%22California%22%2C%20%22Alaska%22%2C%20%22Hawaii%22%5D%5D%0A%0Aregions%2EforEach%28%28region%2C%20index%29%3D%3E%7B%0A%09tagGroup%20%3D%20tagNamed%28region%29%20%7C%7C%20new%20Tag%28region%29%0A%09states%20%3D%20stateGroups%5Bindex%5D%0A%09states%2EforEach%28%28state%29%3D%3E%7B%0A%09%09tagGroup%2EtagNamed%28state%29%20%7C%7C%20new%20Tag%28state%2C%20tagGroup%2Eending%29%0A%09%7D%29%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Creating Tag Groups
 

regions = ["Northeast", "Southeast", "Midwest", "Southwest", "West"] stateGroups = [["Maine", "Massachusetts", "Rhode Island", "Connecticut", "New Hampshire", "Vermont", "New York", "Pennsylvania", "New Jersey", "Delaware", "Maryland"],["West Virginia", "Virginia", "Kentucky", "Tennessee", "North Carolina", "South Carolina", "Georgia", "Alabama", "Mississippi", "Arkansas", "Louisiana", "Florida"],["Ohio", "Indiana", "Michigan", "Illinois", "Missouri", "Wisconsin", "Minnesota", "Iowa", "Kansas", "Nebraska", "South Dakota", "North Dakota"],["Texas", "Oklahoma", "New Mexico", "Arizona"],["Colorado", "Wyoming", "Montana", "Idaho", "Washington", "Oregon", "Utah", "Nevada", "California", "Alaska", "Hawaii"]] regions.forEach((region, index)=>{ tagGroup = flattenedTags.byName(region) || new Tag(region) states = stateGroups[index] states.forEach((state)=>{ tagGroup.flattenedTags.byName(state) || new Tag(state, tagGroup.ending) }) })

Here's a variation of the previous script that sorts the tags within groups:

omnifocus://localhost/omnijs-run?script=try%7Bvar%20regions%20%3D%20%5B%22Northeast%22%2C%20%22Southeast%22%2C%20%22Midwest%22%2C%20%22Southwest%22%2C%20%22West%22%5D%0Avar%20stateGroups%20%3D%20%5B%5B%22Maine%22%2C%20%22Massachusetts%22%2C%20%22Rhode%20Island%22%2C%20%22Connecticut%22%2C%20%22New%20Hampshire%22%2C%20%22Vermont%22%2C%20%22New%20York%22%2C%20%22Pennsylvania%22%2C%20%22New%20Jersey%22%2C%20%22Delaware%22%2C%20%22Maryland%22%5D%2C%5B%22West%20Virginia%22%2C%20%22Virginia%22%2C%20%22Kentucky%22%2C%20%22Tennessee%22%2C%20%22North%20Carolina%22%2C%20%22South%20Carolina%22%2C%20%22Georgia%22%2C%20%22Alabama%22%2C%20%22Mississippi%22%2C%20%22Arkansas%22%2C%20%22Louisiana%22%2C%20%22Florida%22%5D%2C%5B%22Ohio%22%2C%20%22Indiana%22%2C%20%22Michigan%22%2C%20%22Illinois%22%2C%20%22Missouri%22%2C%20%22Wisconsin%22%2C%20%22Minnesota%22%2C%20%22Iowa%22%2C%20%22Kansas%22%2C%20%22Nebraska%22%2C%20%22South%20Dakota%22%2C%20%22North%20Dakota%22%5D%2C%5B%22Texas%22%2C%20%22Oklahoma%22%2C%20%22New%20Mexico%22%2C%20%22Arizona%22%5D%2C%5B%22Colorado%22%2C%20%22Wyoming%22%2C%20%22Montana%22%2C%20%22Idaho%22%2C%20%22Washington%22%2C%20%22Oregon%22%2C%20%22Utah%22%2C%20%22Nevada%22%2C%20%22California%22%2C%20%22Alaska%22%2C%20%22Hawaii%22%5D%5D%0Aregions%2EforEach%28%28region%2C%20index%29%3D%3E%7B%0A%09tagGroup%20%3D%20flattenedTags%2EbyName%28region%29%20%7C%7C%20new%20Tag%28region%29%0A%09states%20%3D%20stateGroups%5Bindex%5D%0A%09states%2EforEach%28%28state%29%3D%3E%7B%0A%09%09tagGroup%2EflattenedTags%2EbyName%28state%29%20%7C%7C%20new%20Tag%28state%2C%20tagGroup%2Eending%29%0A%09%7D%29%0A%09var%20tgs%20%3D%20tagGroup%2Echildren%0A%09if%20%28tgs%2Elength%20%3E%201%29%7B%0A%09%09tgs%2Esort%28%28a%2C%20b%29%20%3D%3E%20%7B%0A%09%09%20%20var%20x%20%3D%20a%2Ename%2EtoLowerCase%28%29%3B%0A%09%09%20%20var%20y%20%3D%20b%2Ename%2EtoLowerCase%28%29%3B%0A%09%09%20%20if%20%28x%20%3C%20y%29%20%7Breturn%20%2D1%3B%7D%0A%09%09%20%20if%20%28x%20%3E%20y%29%20%7Breturn%201%3B%7D%0A%09%09%20%20return%200%3B%0A%09%09%7D%29%0A%09%09moveTags%28tgs%2C%20tagGroup%29%0A%09%7D%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Creating Sorted Tag Groups
 

regions = ["Northeast", "Southeast", "Midwest", "Southwest", "West"] stateGroups = [["Maine", "Massachusetts", "Rhode Island", "Connecticut", "New Hampshire", "Vermont", "New York", "Pennsylvania", "New Jersey", "Delaware", "Maryland"],["West Virginia", "Virginia", "Kentucky", "Tennessee", "North Carolina", "South Carolina", "Georgia", "Alabama", "Mississippi", "Arkansas", "Louisiana", "Florida"],["Ohio", "Indiana", "Michigan", "Illinois", "Missouri", "Wisconsin", "Minnesota", "Iowa", "Kansas", "Nebraska", "South Dakota", "North Dakota"],["Texas", "Oklahoma", "New Mexico", "Arizona"],["Colorado", "Wyoming", "Montana", "Idaho", "Washington", "Oregon", "Utah", "Nevada", "California", "Alaska", "Hawaii"]] regions.forEach((region, index)=>{ tagGroup = flattenedTags.byName(region) || new Tag(region) states = stateGroups[index] states.forEach((state)=>{ tagGroup.flattenedTags.byName(state) || new Tag(state, tagGroup.ending) }) tgs = tagGroup.children if (tgs.length > 1){ tgs.sort((a, b) => { var x = a.name.toLowerCase(); var y = b.name.toLowerCase(); if (x < y) {return -1;} if (x > y) {return 1;} return 0; }) moveTags(tgs, tagGroup) } })

Here’s an example script that creates a unique tag set, and warns the user if the main tag already exists:

omnifocus://localhost/omnijs-run?script=var%20targetTag%20%3D%20flattenedTags%2EbyName%28%22Seasons%22%29%0Aif%28targetTag%29%7B%0A%09var%20title%20%3D%20%22Item%20Exists%22%0A%09var%20msg%20%3D%20%22A%20tag%20titled%20%E2%80%9CSeasons%E2%80%9D%20already%20exists%2E%22%0A%09var%20tagID%20%3D%20targetTag%2Eid%2EprimaryKey%0A%09new%20Alert%28title%2C%20msg%29%2Eshow%28result%20%3D%3E%20%7B%0A%09%09URL%2EfromString%28%22omnifocus%3A%2F%2F%2Ftag%2F%22%20%2B%20tagID%29%2Eopen%28%29%0A%09%7D%29%0A%09console%2Eerror%28msg%29%0A%7D%20else%20%7B%0A%09var%20SeasonsTag%20%3D%20new%20Tag%28%22Seasons%22%29%0A%09var%20tagID%20%3D%20SeasonsTag%2Eid%2EprimaryKey%0A%09var%20tagTitles%20%3D%20%5B%22Spring%22%2C%20%22Summer%22%2C%20%22Winter%22%2C%20%22Fall%22%5D%0A%09tagTitles%2EforEach%28title%20%3D%3E%20%7B%0A%09%09new%20Tag%28title%2C%20SeasonsTag%29%0A%09%7D%29%0A%09URL%2EfromString%28%22omnifocus%3A%2F%2F%2Ftag%2F%22%20%2B%20tagID%29%2Eopen%28%29%0A%7D
Create Unique Tag Set
 

targetTag = flattenedTags.byName("Seasons") if(targetTag){ title = "Item Exists" msg = "A tag titled “Seasons” already exists." tagID = targetTag.id.primaryKey new Alert(title, msg).show(result => { URL.fromString("omnifocus:///tag/" + tagID).open() }) console.error(msg) } else { SeasonsTag = new Tag("Seasons") tagID = SeasonsTag.id.primaryKey tagTitles = ["Spring", "Summer", "Winter", "Fall"] tagTitles.forEach(title => { new Tag(title, SeasonsTag) }) URL.fromString("omnifocus:///tag/" + tagID).open() }

And a related script for switching to the Tags perspective and selecting the children of the tag set:

omnifocus://localhost/omnijs-run?script=var%20targetTag%20%3D%20flattenedTags%2EbyName%28%22Seasons%22%29%0Aif%28targetTag%29%7B%0A%09var%20tagIDs%20%3D%20targetTag%2Echildren%2Emap%28tag%20%3D%3E%20tag%2Eid%2EprimaryKey%29%0A%09var%20tagIDsString%20%3D%20%20tagIDs%2Ejoin%28%22%2C%22%29%0A%09URL%2EfromString%28%22omnifocus%3A%2F%2F%2Ftag%2F%22%20%2B%20tagIDsString%29%2Eopen%28%29%0A%7D
Select Tag Set Tags
 

targetTag = flattenedTags.byName("Seasons") if(targetTag){ tagIDs = targetTag.children.map(tag => tag.id.primaryKey) tagIDsString = tagIDs.join(",") URL.fromString("omnifocus:///tag/" + tagIDsString).open() }

Example Plug-Ins

This action plug-in will “adopt” the tags contained in tag groups that are assigned to the selected task or project:

Adopt Tags from Tag Groups
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.adopt-child-tags", "version": "1.2", "description": "This action will assign the child tags of the tag groups of the selected task|project, to the selected task|project.", "label": "Adopt Tags from Tag Groups", "shortLabel": "Adopt Child Tags", "paletteLabel": "Adopt Child Tags", "image": "tag.fill" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ if(selection.tasks[0]){ var task = selection.tasks[0] } else { var task = selection.projects[0].task } task.tags.forEach((tag)=>{ task.addTags(tag.children) }) }); action.validate = function(selection, sender){ return ( selection.tasks.length === 1 || selection.projects.length === 1 ) }; return action; })();

The following action plug-in will select all tags whose name matches the title entered in the form. If the tag is not found, an option to create and reveal it will be offered:

Tag Check
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.tag-check", "description": "This action will select all of the tags sharing the provided title.", "version": "1.6", "label": "Tag Check", "shortLabel": "Tag Check", "paletteLabel": "Tag Check", "image":"tag" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ try { inputForm = new Form() textField = new Form.Field.String("tagName", null, null) inputForm.addField(textField) formPrompt = "Enter the title of the tag to check:" buttonTitle = "Continue" inputForm.validate = function(formObject){ textValue = formObject.values['tagName'] return (!textValue) ? false:true } formObject = await inputForm.show(formPrompt, buttonTitle) targetTagName = formObject.values['tagName'] console.log("targetTagName", targetTagName) foundTag = flattenedTags.find(tag => {return tag.name === targetTagName}) if(foundTag !== undefined){ tagID = foundTag.id.primaryKey URL.fromString("omnifocus:///tag/" + tagID).open() } else { alert = new Alert("Tag Not Found", "The tag “" + targetTagName + "” was not found.\n\nDo you want to create it?") alert.addOption("Create") alert.addOption("Stop") alert.show(buttonIndex => { console.log("buttonIndex", buttonIndex) if(buttonIndex === 0){ newTag = new Tag(targetTagName) tagID = newTag.id.primaryKey URL.fromString("omnifocus:///tag/" + tagID).open() } }) } } catch(err){ new Alert(err.name, err.message).show() } }); return action; })();

Deleting Tags

To delete one or more tags from the database, the deleteObject() function of the Database class is used, as in this script that deletes all tags from the database:

omnifocus://localhost/omnijs-run?script=try%7Btags%2EforEach%28%28tag%29%20%3D%3E%20%7BdeleteObject%28tag%29%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Deleting All Tags
 

tags.forEach(tag => {deleteObject(tag)})

And here’s a script that removes duplicate tags from the entire tag heirarchy:

omnifocus://localhost/omnijs-run?script=try%7Bvar%20tagNames%20%3D%20new%20Array%28%29%0Atags%2Eapply%28function%28tag%29%7B%0A%09if%20%28tagNames%2Eincludes%28tag%2Ename%29%29%7B%0A%09%09deleteObject%28tag%29%20%0A%09%7D%20else%20%7B%0A%09%09tagNames%2Epush%28tag%2Ename%29%0A%09%7D%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Remove Duplicate Tags
 

var tagNames = new Array() tags.apply(function(tag){ if (tagNames.includes(tag.name)){ deleteObject(tag) } else { tagNames.push(tag.name) } })

NOTE: The previous script isn’t designed to examine any task relationships of the tags, it simply removes duplicate tags based upon the order they are encountered.

The following plug-in will delete all tags that have not been associated with items. Parent tags of tags who have been assigned will be retained.

Delete Unassigned Tags
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of-remove-unused-tags", "version": "2.1", "description": "This action will delete all tags that have not been associated with items. NOTE: Because of possible tag hierarchies, there may be a difference between the number of unassigned tags and the number of deleted tags.", "label": "Delete Unassigned Tags", "shortLabel": "Delete Unassigned Tags", "paletteLabel": "Delete Unassigned Tags", "image":"tag.slash.fill" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ try { unassignedTags = flattenedTags.filter(tag => { if(tag.remainingTasks.length === 0){ return tag } }) unassignedCount = unassignedTags.length if (unassignedCount === 0){ throw { name: "No Matches", message: "There are no unassigned tags." } } isAre = (unassignedCount === 1) ? "is":"are" tagTags = (unassignedCount === 1) ? "tag":"tags" thisThose = (unassignedCount === 1) ? "this":"those" alertTitle = "Confirmation Required" alertMessage = `There ${isAre} ${unassignedCount} unassigned ${tagTags}.\nDelete or log ${thisThose} ${tagTags}?` alert = new Alert(alertTitle, alertMessage) alert.addOption("Delete Tags") alert.addOption("Log Tags") alert.addOption("Stop") buttonIndex = await alert.show() if (buttonIndex === 0){ var deletionCount = 0 var deletedTagTitles = [] do { tagsToDelete = flattenedTags.filter(tag => { if(tag.flattenedTags.length === 0 && tag.remainingTasks.length === 0){ return tag } }) if (tagsToDelete.length > 0){ deletionCount = deletionCount + tagsToDelete.length tagTitles = tagsToDelete.map(tag => tag.name) deletedTagTitles = deletedTagTitles.concat(tagTitles) tagsToDelete.forEach(tag => deleteObject(tag)) } } while (tagsToDelete.length > 0); responseMessage = `${deletionCount} deleted tags.\n\nThe names of the deleted tags have been logged to the Automation Console.` console.clear() console.log(deletedTagTitles.join("\n")) new Alert("Completed", responseMessage).show() } else if (buttonIndex === 1){ unusedTags = flattenedTags.filter(tag => { if(tag.remainingTasks.length === 0){ return tag } }) console.clear() console.log("Tag Count: ", flattenedTags.length) console.log("Unassigned Tag Count: ", unusedTags.length) tagNames = unusedTags.map(tag => tag.name) console.log("Unassigned Tags:", "\n" + tagNames.join("\n")) } } catch(err){ new Alert(err.name, err.message).show() } }); action.validate = function(selection, sender){ return (tags.length > 0) }; return action; })();

Clearing Tags from Tasks

To remove an assigned tag from its host task, use the removeTag(…) or removeTags(…) functions of the Task class.

As an alternative, you can use the clearTags() function of both the Project and Task classes to remove applied tags:

Clear Tags from Selected Tasks
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.clear-task-tags", "version": "1.6", "description": "This action will clear all tags assigned to the selected tasks.", "label": "Clear Tags from Tasks", "shortLabel": "Clear Task Tags", "paletteLabel": "Clear Task Tags", "image": "tag.slash" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ checkboxSwitch = new Form.Field.Checkbox( "shouldClearFromChildren", "Also clear tags from tasks’s children", false ) inputForm = new Form() inputForm.addField(checkboxSwitch) formObject = await inputForm.show("Clear Tasks’s Tags:","Continue") shouldClearFromChildren = formObject.values['shouldClearFromChildren'] if (shouldClearFromChildren){ selection.tasks.forEach(task => { task.clearTags() task.flattenedTasks.forEach(child => child.clearTags()) }) } else { selection.tasks.forEach(task => task.clearTags()) } }); action.validate = function(selection, sender){ return (selection.tasks.length > 0) }; return action; })();

For example, here is a plug-in that uses the clearTags() function to remove assigned tags from the selecte projects:

Clear Tags from Selected Projects
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.clear-project-tags", "version": "1.6", "description": "This action will clear all tags assigned to the selected projects.", "label": "Clear Tags from Projects", "shortLabel": "Clear Project Tags", "paletteLabel": "Clear Project Tags", "image": "tag.slash" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ checkboxSwitch = new Form.Field.Checkbox( "shouldClearFromTasks", "Also clear tags from project’s tasks", false ) inputForm = new Form() inputForm.addField(checkboxSwitch) formObject = await inputForm.show("Clear Project’s Tags:","Continue") shouldClearFromTasks = formObject.values['shouldClearFromTasks'] if (shouldClearFromTasks){ selection.projects.forEach(project => { project.clearTags() project.flattenedTasks.forEach(task => task.clearTags()) }) } else { selection.projects.forEach(project => project.clearTags()) } }); action.validate = function(selection, sender){ return (selection.projects.length > 0) }; return action; })();

Importing Tags from Text File

An important aspect of the Omni Automation support in OmniFocus is in its ability to import data from sources outside of the application.

In the following example action plug-in, the user is prompted to choose a text file whose paragraphs comprise a list of tags to be added to the database. The chosen file is parsed, and each paragraph of the imported text is used to create a new top-level tag if an existing matching one does not exist.

Import Tags from File
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.import-text-to-tags", "version": "1.5", "description": "This action will convert the paragraphs of the chosen text file into OmniFocus tags.", "label": "Import Text to Tags", "paletteLabel": "Import Tags", "image": "square.and.arrow.down.on.square" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ // get the names of existing tags tagTitles = flattenedTags.map(tag => {return tag.name}) // show the file picker picker = new FilePicker() picker.folders = false picker.multiple = false picker.types = [FileType.plainText] urlsArray = await picker.show() // process the results of the file picker aURL = urlsArray[0] aURL.fetch(data => { textArray = data.toString().split('\n') newTagObjs = new Array() textArray.forEach(item =>{ if(item != "" && !tagTitles.includes(item)){ tag = new Tag(item) tagTitles.push(item) newTagObjs.push(tag) } }) document.windows[0].perspective = Perspective.BuiltIn.Tags document.windows[0].selectObjects(newTagObjs) }) }); action.validate = function(selection, sender){ return true }; return action; })();

Copy Tags Between Selected Tasks

The following Omni Automaton plug-in uses the addTags(…) function of the Task class to copy the tags of a chosen task to the other tasks selected in the OmniFocus window.

To use, select both the tasks to receive the tags, and the task containing the set of tags to be copied, and run the action.

Copy Tags Between Selected Tasks
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.copy-tags-between-tasks", "version": "2.0", "description": "This plug-in will copy the tags of the chosen task to the other selected tasks.", "label": "Copy Tags between Selected Tasks", "shortLabel": "Copy Tags", "paletteLabel": "Copy Tags", "image": "tag.square" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ tasks = selection.tasks // sort the task objects by name tasks.sort((a, b) => { var x = a.name.toLowerCase(); var y = b.name.toLowerCase(); if (x < y) {return -1;} if (x > y) {return 1;} return 0; }) // generate a list of task names menuItems = tasks.map(task => {return task.name}) // generate a list of matching indexes menuIndexes = menuItems.map((item, index) => index) menuLabel = "Selected Tasks" multiOptionMenu = new Form.Field.MultipleOptions( "menuKey", menuLabel, menuIndexes, menuItems, [] ) checkboxSwitch = new Form.Field.Checkbox( "shouldReplaceTags", "Replace existing tags", false ) inputForm = new Form() inputForm.addField(multiOptionMenu) inputForm.addField(checkboxSwitch) inputForm.validate = function(formObject){ var indexes = formObject.values["menuKey"] // ensure at least one item is selected return (indexes.length === 1)?true:false } formPrompt = "Select task to copy tags from:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) indexes = formObject.values["menuKey"] shouldReplaceTags = formObject.values["shouldReplaceTags"] sourceTask = tasks[indexes[0]] tagsToCopy = sourceTask.tags tasks.forEach(task => { if (shouldReplaceTags){task.clearTags()} task.addTags(tagsToCopy) }) }); action.validate = function(selection, sender){ // validation code return (selection.tasks.length > 1) }; return action; })();

Assigning Tags to Selected Tasks

This action will display a list of all tags (sorted), any or all of which may selected to be assigned to the selected tasks. The plug-in is designed to work only on iPadOS and iOS as their form interfaces scroll and allow for the display of dozens of tags.

Assign Tags to Selected Tasks (iPadOS/iOS)
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.assign-tags-ipados-ios", "version": "1.7", "description": "(iPadOS/iOS) This action will display a list of all tags (sorted), any or all of which may selected to be assigned to the selected tasks.", "label": "Assign Tags", "shortLabel": "Assign Tags", "paletteLabel": "Assign Tags", "image": "tag.circle.fill" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ allTags = flattenedTags if (allTags.length > 1){ allTags.sort((a, b) => { var x = a.name.toLowerCase(); var y = b.name.toLowerCase(); if (x < y) {return -1;} if (x > y) {return 1;} return 0; }) } menuItems = allTags.map(tag => {return tag.name}) menuIndexes = menuItems.map((item, index) => index) menuLabel = "Assign Tags" multiOptionMenu = new Form.Field.MultipleOptions( "menuKey", menuLabel, menuIndexes, menuItems, [] ) inputForm = new Form() inputForm.addField(multiOptionMenu) inputForm.validate = function(formObject){ var indexes = formObject.values["menuKey"] return (indexes.length > 0)?true:false } formPrompt = "Select one or more tags to apply to selected tasks:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) indexes = formObject.values["menuKey"] tags = new Array() indexes.forEach(index => { tags.push(allTags[index]) }) selection.tasks.forEach(function(task){ task.addTags(tags) }) }); action.validate = function(selection, sender){ return ( Device.current.iOS && selection.tasks.length > 0 && tags.length > 0 ) }; return action; })();

Toggle Status of Selected Tag

This plug-in will toggle the status of the selected tag between active and on hold. If the selected tag is a tag group, the status of all of the group tags will changed accordingly.

NOTE: this plug-in uses the sender parameter of the action handlers to toggle the text of the plug-in menu item.

Toggle Status of Selected Tag
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.toggle-tag-status", "version": "1.1", "description": "This plug-in will toggle the status of the selected tag between active and on hold. If the selected tag is a tag group, the status of all of the group tags will changed accordingly.", "label": "Toggle Tag Status", "shortLabel": "Toggle Tag", "paletteLabel": "Toggle Tag", "image": "tag" }*/ (() => { const action = new PlugIn.Action(function(selection, sender){ tag = selection.tags[0] if(tag.status === Tag.Status.Active){ tag.status = Tag.Status.OnHold tag.flattenedTags.forEach(tg => { tg.status = Tag.Status.OnHold }) } else { tag.status = Tag.Status.Active tag.flattenedTags.forEach(tg => { tg.status = Tag.Status.Active }) } }); action.validate = function(selection, sender){ // sender can be: MenuItem, ToolbarItem, or undefined (remote script) if (selection.tags.length === 1){ if (sender instanceof MenuItem){ menuText = (selection.tags[0].status === Tag.Status.Active)?'Set Status to On Hold':'Set Status to Active' sender.label = menuText } return true } if (sender instanceof MenuItem){sender.label = 'Toggle Tag Status'} return false }; return action; })();

Tag-Based Kanban Board

A plug-in for creating and maintaining a tag-based Kanban Board in OmniFocus. Based upon a creative concept and project by Serena (www.sleepyowl.ink) where the Kanban Board is OmniFocus tag-based and displayed as stack of horizontal elements (categories).

TAP|CLICK for full description and download of plug-in.

Kanban Board Interface

Show Tasks Tagged with Indicated Tags

Here's a script that simulates a “focus” for tasks by tagging tasks that have been already tagged with all of the indicated tags, and then showing the “focus” tag:. You can delete the “focus” tag when done.

“Sudo-Focus” Tasks Tagged with Specified Tags


tag1 = flattenedTags.byName("Weekend") || new Tag("Weekend") tag2 = flattenedTags.byName("Hiking") || new Tag("Hiking") focusTag = flattenedTags.byName("Focus") || new Tag("Focus") matched = flattenedTasks.filter(task => { tgs = task.tags return tgs.includes(tag1) && tgs.includes(tag2) }) if(matched.length > 0){ matched.forEach(task => {task.addTag(focusTag)}) id = focusTag.id.primaryKey URL.fromString("omnifocus:///tag/" + id).open() }

Sorting a Tag’s Tasks

Omni Automation provides a special set of positional indicators for working with the tasks belonging to a tag group.

Tag.TaskInsertionLocation: A location specifying the order of a Task within a Tag. These cannot be instantiated directly, rather they are returned from properties like Tag.beforeTask() or Tag.endingOfTasks. (For a complete list of locations, open the navigation sidebar and use its filter to search for Tag.TaskInsertionLocation.)

For an instance of the Tag class:

Instance Function

Instance Properties

Here's an example script that sorts a tag’s tasks by due date:

Sorting a Tag’s Tasks


tag = flattenedTags.byName("Green") || new Tag("Green") tagTasks = tag.availableTasks if (tagTasks.length > 1){ tagTasks.sort((a, b) => { var x = a.dueDate var y = b.dueDate if (x < y) {return -1;} if (x > y) {return 1;} return 0; }) tag.moveTasks(tagTasks, tag.beginningOfTasks) }