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:

document.windows[0].perspective = Perspective.BuiltIn.Tags

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.

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:

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

Iterating 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:

A script example using the JavaScript forEach() function to iterate the array of 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:

tagNames = tags.map((tag)=>{return tag.name})

Since tags may be contained within other tags (Tag Groups), a recursive routine is necessary to iterate the entire tree of database tags, like this script for retrieving the name of every tag, which uses the apply(…) function whose use is detailed in the topic section Finding Items.

tagNames = new Array() tags.apply(tag => tagNames.push(tag.name)) console.log(tagNames)

And here’s a verson of the previous script that uses creates an instance of the Set class to remove all duplicates from the tag name list:

tagNames = new Array() tags.apply(tag => tagNames.push(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:

// Top-Level Not Existing tagNamed("Zebra") //--> null // Top-Level Exists tagNamed("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]

Here’s a script example that uses the apply(…) function to iterate the entire tag heirarchy to locate the first tag whose name matches the specified name. If no tag is found, one with the specified name is created.

targetTagName = "Michigan" var targetTag = null tags.apply(function(tag){ if(tag.name == targetTagName){ targetTag = tag return ApplyResult.Stop } }) tag = targetTag || new Tag(targetTagName)

Create New Tag

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

tag = new Tag("LAST BY DEFAULT")
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.

tag = tagNamed("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:

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) })

 01  A Javascript logical operator is used to reference a tag which will contain the tags of the tag group.

 02  An array (list) of names for the new tags.

 03-05  Using the JavaScript forEach() function to iterate the array of tag names, passing into its function each of the tag names in sequence.

 04  Using a Javascript logical operator to ensure that a new tag is created if only one with the passed tag name does not exist. Note that the positional property beginning is used to indicate where in the tag group the new tag should be placed.

Here’s how to generate an array of tag references to tags whose names are in a list of tag names. If an existing tag is not matched to a name in the list, a new tag is created using the unmatched name.

tagNames = ["Bow","Stern","Port","Starboard"] var tagRefs = new Array() tagNames.forEach(tagName => { var targetTag = null tags.apply(function(tag){ if(tag.name === tagName){ targetTag = tag return ApplyResult.Stop } }) tagRef = targetTag || new Tag(tagName) tagRefs.push(tagRef) }) console.log(tagRefs)

Here’s a variation of the previous script written as a function that returns object references to tags whose names are included in the input array of tag names. If a tag name is not matched, a new tag with the unmatched name is created.

function getTagsWithNames(tagNames){ var tagRefs = new Array() tagNames.forEach(tagName => { var targetTag = null tags.apply(function(tag){ if(tag.name === tagName){ targetTag = tag return ApplyResult.Stop } }) tagRef = targetTag || new Tag(tagName) tagRefs.push(tagRef) }) return tagRefs }

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.

var regions = ["Northeast", "Southeast", "Midwest", "Southwest", "West"] var 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 = tagNamed(region) || new Tag(region) states = stateGroups[index] states.forEach((state)=>{ tagGroup.tagNamed(state) || new Tag(state, tagGroup.ending) }) })

 01  An array of names the for tag groups.

 02  An array of arrays containing the content for the tag groups. Note the order of the content array items matches the order of the tag group names.

 05-11  Using a JavaScript forEach() function to iterate the array of the names of the tag groups. Note the passed-in parameters for the function include a group name and the its position (index) in the array of tag group names.

 06  Using a JavaScript logical operator to reference a tag group matching the passed-in name.

 07  Get the corresponding array of tag names to be used for the group items.

 08-10  Iterate the array of tag names and adding a newtag to the group if it doesn’t already exist.

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

/*{ "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" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags 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){ // validation code // selection options: tasks, projects, folders, tags 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:

/*{ "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.4", "label": "Tag Check", "shortLabel": "Tag Check" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // CONSTRUCT THE FORM var inputForm = new Form() // CREATE FORM ELEMENTS: TEXT INPUT, DATE INPUT, AND CHECKBOX textField = new Form.Field.String("tagName", null, null) // ADD THE ELEMENTS TO THE FORM inputForm.addField(textField) // DIALOG PROMPT AND OK BUTTON TITLE let formPrompt = "Enter the title of the tag to check:" let buttonTitle = "Continue" // DISPLAY THE FORM DIALOG formPromise = inputForm.show(formPrompt, buttonTitle) // VALIDATE FORM CONTENT inputForm.validate = function(formObject){ // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT textValue = formObject.values['tagName'] // RETURN VALIDATION BOOLEAN return (!textValue) ? false:true } // PERFORM PROCESSES USING FORM DATA formPromise.then(function(formObject){ try { var targetTagName = formObject.values['tagName'] var targetTags = new Array() tags.apply(function(tag){ if(tag.name === targetTagName){ targetTags.push(tag) } }) if(targetTags.length > 0){ Timer.once(1,function(timer){ // wait for sheet/dialog document.windows[0].perspective = Perspective.BuiltIn.Tags Timer.once(1,function(timer){ // wait for perspective document.windows[0].selectObjects(targetTags) }) }) } else { var alert = new Alert("Tag Not Found", "The tag “" + targetTagName + "” was not found.\n\nDo you want to create it?") alert.addOption("Stop") alert.addOption("Create") alert.show(function(result){ if (result == 0){ console.log("Done") } else { Timer.once(1,function(timer){ // wait for sheet/dialog var tag = new Tag(targetTagName) document.windows[0].perspective = Perspective.BuiltIn.Tags Timer.once(1,function(timer){ // wait for perspective document.windows[0].selectObjects([tag]) }) }) } }) } } catch(err){ console.error(err) } }) // PROCESS FORM CANCELLATION formPromise.catch(function(err){ console.log("form cancelled", err.message) }) }); 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:

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

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

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.

Clearing Tags from Tasks

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

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.clear-task-tags", "version": "1.1", "description": "This action will clear all tags assigned to the selected tasks.", "label": "Clear Tags from Tasks", "shortLabel": "Clear Task Tags" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags selection.tasks.forEach(function(task){ task.removeTags(task.tags) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags return (selection.tasks.length > 0) }; return action; }(); _;
/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.clear-project-tags", "version": "1.1", "description": "This action will clear all tags assigned to the selected projects.", "label": "Clear Tags from Projects", "paletteLabel": "Clear Project Tags" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags selection.projects.forEach(function(project){ project.task.removeTags(project.task.tags) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags 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.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.import-text-to-tags", "version": "1.2", "description": "This action will convert the paragraphs of the chosen text file into OmniFocus tags.", "label": "Import Text to Tags", "paletteLabel": "Import Tags" }*/ var _ = function(){ var action = new PlugIn.Action(function(selection, sender){ // get the names of existing tags var tagTitles = new Array() tags.apply(tag => tagTitles.push(tag.name)) // show the file picker var picker = new FilePicker() picker.folders = false picker.multiple = false picker.types = [FileType.plainText] pickerPromise = picker.show() // process the results of the file picker pickerPromise.then(function(urlsArray){ aURL = urlsArray[0] aURL.fetch(function(data){ textArray = data.toString().split('\n') var newTagObjs = new Array() textArray.forEach((item)=>{ if(item != "" && !tagTitles.includes(item)){ var tag = new Tag(item) tagTitles.push(item) newTagObjs.push(tag) } }) document.windows[0].perspective = Perspective.BuiltIn.Tags Timer.once(1,function(timer){ document.windows[0].selectObjects(newTagObjs) }) }) }) // if user cancels pickerPromise.catch(function(err){ console.log("picker cancelled", err.message) }) }); action.validate = function(selection, sender){ return true }; return action; }(); _;

 01-10  The plug-in metadata keys and corresponding values.

 11-49  The main function that contains the statements for creating and validating the action object.

 12-42  The action function instantiates a new action object and is passed the current selection object as input. In this action, the selection is not used and instead the user is prompted to choose the file containing the data to import.

 15-16  Use the apply(…) method to generate a list of all tag titles, which be used to ensure that imported tags are not duplicate of the existing tags.

 19-22  Create a new instance of the FilePicker class and set its properties to only allow the user-selection of a single text file.

 23  Use the show() method to display the file picker and return a JavaScript promise to handle the picker results.

 26-37  Process the result of the picker dialog using the then() method whose callback function is passed an array of file URLs representing the files chosen by the user in the file picker dialog.

 27  The passed parameter is an array of file URLs representing the chosen items. In this action, an array containing a single URL is returned.

 28-36  Call the fetch() method of the URL class to extract the data from the chosen file.

 29  Convert the file data to text using the toString() method and call the split(…) function on the resulting string to split the paragraphs into an array of strings.

 30-36  Iterate the arrays of strings to create new tags.

 31-34  Check to see if there is an existing tag matching the string. If not, then create a new tag and add the tag title to teh list of tag titles.

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.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.copy-tags-between-selected-tasks", "version": "1.6", "description": "Append the tags of the chosen task to the other selected tasks.", "label": "Copy Tags between Selected Tasks", "shortLabel": "Copy Tags" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags var selectedTasks = selection.tasks var taskNames = selectedTasks.map(function(task){ return task.name }) var inputForm = new Form() taskNames.forEach(function(taskName,index){ checkboxSwitch = new Form.Field.Checkbox( String(index), taskName, false ) inputForm.addField(checkboxSwitch) }) formPrompt = "Select the single task to copy tags from:" formPromise = inputForm.show(formPrompt,"Continue") inputForm.validate = function(formObject){ resultArray = Object.values(formObject.values) var count = 0; for (var i = 0; i < resultArray.length; i++) { if (resultArray[i] == true){count = count + 1} } return ((count == 1) ? true : false) } formPromise.then(function(formObject){ keys = Object.keys(formObject.values) for (var i = 0; i < keys.length; i++) { k = keys[i] if (formObject.values[k] == true){ var chosenTask = selectedTasks[Number(k)] var chosenTaskName = taskNames[Number(k)] } } str1 = "Copy the tags from “" + chosenTaskName + "” to the other selected tasks?" str2 = "Tags will be added to those tags already applied to the other selected tasks." alertMsg = str1.concat("\n\n",str2) var alert = new Alert("CONFIRMATION:", alertMsg) alert.addOption("Continue") alert.addOption("Cancel") var alertPromise = alert.show() alertPromise.then(function(result){ if (result == 0){ sourceTags = chosenTask.tags selectedTasks.forEach(function(task){ task.addTags(sourceTags) }) } }) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags return (selection.tasks.length > 1) }; return action; })();

This webpage is in the process of being developed. Any content may change and may not be accurate or complete at this time.