Database

The heart of OmniFocus is a powerful database that stores and manages your personal tasks and schedule. The database is stored in a file that OmniFocus uses to hold all of the information that you add to the app. OmniFocus and its various perspectives act as windows onto your database, interpreting the data there in ways that help you get stuff done.

Typically, you’ll only interact with your primary default OmniFocus database; you can open multiple windows onto it at once to maintain different view states simultaneously. More rarely, you can open multiple database files at once to restore from a backup or archive, or view the contents of an OmniFocus database other than your own.

This topic page describes the properties and functions of the Database class, demonstrating how they are used to access and automate the processing of database data.

The Database defines the top-level global context for a JavaScript session (i.e., globalThis). Every function and property defined at this level can be referenced in code by simply referring to its name (e.g. library or inbox or moveTasks()).

Database Object Arrays

The component elements of the OmniFocus database, such as instances of the Tag, Task, Project, Folder classes, are often referenced as standard JavaScript arrays. You will find that the value of some of the properties of the Database class are returned as a specific subclass of Array.

For example, the value of the flattenedTags property of the Database class is an instance of the TagArray subclass of the standard JavaScript Array class.

These specialized array classes may have properties and functions associated with them. For reference, here are the array subclasses used when referencing database objects:

FolderArray
An Array containing Folder objects.
ProjectArray
An Array containing Project objects.
SectionArray
An Array containing Project and Folder objects.
TagArray
An Array containing Tag objects.
TaskArray
An Array containing Task objects.

Database Properties

Here are the scripting properties of the database:

NOTE: The Inbox, Library, and Tags classes that are the values of the their related Database class properties (inbox, library, tags) share the same set of array position indicator properties:

Inbox class:

Library class:

Tags class:

Using the positional insertion indicators:

Make New Items at Locations


new Task("My New Task"", inbox.beginning) new Project("My New Project", library.ending) new Tag("My New Tag", tags.beginning)

In addition, the three Array classes (Inbox, Library, and Tags) also support the use of the apply(…) function for iterating their object heirarchies, which is described in detail on the Finding Items topic page.

Iterate All Tasks in Inbox Hierarchy


inbox.apply(task => { if (task.taskStatus === Task.Status.Available){ // processing statements go here } })

The “flattened” Database Properties

Using the “flattened” properties of the Database class (flattenedFolders, flattenedProjects, flattenedTasks, flattenedTags) scripts can iterate all instances of the specified class in the entire database. For example, the following script iterates every task in the database:

Iterate All Tasks in Database


flattenedTasks.forEach(task => { if (task.taskStatus === Task.Status.Available){ // processing statements go here } })

Here's an example of using the “flattened” properties with the JavaScript filter() method to generate an array of references to all tasks whose status is available:

Filter All Tasks in Database


matches = flattenedTasks.filter(item => { return item.taskStatus === Task.Status.Available })

And a variation of the previous script that filters all tasks for those that are available, and then processes each of the matched tasks:

Filter and Process Matched Taskss


flattenedTasks.filter(item => { return item.taskStatus === Task.Status.Available }).forEach(task => { // processing statements })

And here is a script example that filters and processes all tasks whose name begins with a specific word. (NOTE: The optional use of the toUpperCase() function is to ensure the string comparision is case-insensitive.)

Filter and Process All Tasks whose Name begins with Specified String


targetString = "three" flattenedTasks.filter(item => { return item.name.toUpperCase().startsWith(targetString.toUpperCase()) }).forEach(task => { // processing statements })

Database Functions

Here are the scripting functions of the Database class:

An example of getting the url of a DatabaseObject, and then using the objecctForURL(…) function of the Database class to derive an object reference to the object targeted by the URL:

Object’s URL


aTask = inbox[0] if (aTask){ taskURL = aTask.url //-> [object URL: omnifocus:///task/hX3u3oHdgEm] objectForURL(taskURL) //-> [object Task: Plan Party] }

An example using the pasteTasksFromPasteboard(…) function:

Paste Tasks from Clipboard


if(canPasteTasks(Pasteboard.general)){ win = document.windows[0] win.perspective = Perspective.BuiltIn.Inbox var pastedTasks = pasteTasksFromPasteboard() win.selectObjects(pastedTasks) }

Here’s an example script that references an Inbox task by name and creates a new task if the task does not exist at the top-level:

Reference Inbox Task by Name (create if needed)


task = taskNamed("My Task") || new Task("My Task") //--> [object Task: My Task]

And a version of the previous script that searches the entire database for an existing task by name:

Reference Task by Name (create if needed)


task = flattenedTasks.byName("My Task") || new Task("My Task") //--> [object Task: My Task]

Here is an exampale of moving one or more tags:

Move Tag to the End of Tags


moveTags([tagNamed('Northeast')],tags.ending)

Here's a script that duplicates the selected tasks to the inbox:

Duplicate Selected Tasks to Inbox


win = document.windows[0] tasks = win.selection.tasks if (tasks.length > 0){ duplicateTasks(tasks, inbox.beginning) win.perspective = Perspective.BuiltIn.Inbox }

Here’s a script that performs a database redo() function:

Redo


if (canRedo){redo()}

Here’s a script that uses the deleteObject() method to remove all items from the Inbox:

Clear Inbox


inbox.forEach(tsk => deleteObject(tsk))

Here’s a script that uses the deleteObject() method to remove all items from the database Projects directory:

Clear Projects


projects.forEach(project => deleteObject(project))

Here's an example using the generalized smart matching commands to find all tags matching “Auto-”:

Matched Searches
 

tagsMatching("Auto-") //--> [[object Tag: Auto-Open], [object Tag: Auto-Close], [object Tag: Auto-Update]]

Special Pasteboard Functions

The Database class includes special functions for copying and pasting tasks using the Pasteboard class:

omnifocus://localhost/omnijs-run?script=try%7Bvar%20tasks%20%3D%20document%2Ewindows%5B0%5D%2Eselection%2Etasks%0Aif%20%28tasks%2Elength%20%3E%200%29%7B%0A%09var%20clipboard%20%3D%20Pasteboard%2EmakeUnique%28%29%0A%09copyTasksToPasteboard%28tasks%2C%20clipboard%29%0A%09var%20duplicatedTasks%20%3D%20pasteTasksFromPasteboard%28clipboard%29%0A%09document%2Ewindows%5B0%5D%2Eperspective%20%3D%20Perspective%2EBuiltIn%2EInbox%0A%09moveTasks%28duplicatedTasks%2Cinbox%2Ebeginning%29%0A%7D%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Duplicate Selected Tasks to Inbox
 

win = document.windows[0] tasks = win.selection.tasks if (tasks.length > 0){ clipboard = Pasteboard.makeUnique() copyTasksToPasteboard(tasks, clipboard) duplicatedTasks = pasteTasksFromPasteboard(clipboard) win.perspective = Perspective.BuiltIn.Inbox moveTasks(duplicatedTasks, inbox.beginning) }

And here's a script example that will export all of the tasks of a specified project to a single TaskPaper file on disk:

Export Project Tasks to TaskPaper File


(async () => { try { project = flattenedProjects.byName("Target Project") if(project){ copyTasksToPasteboard(project.flattenedTasks, Pasteboard.general) data = Data.fromString(Pasteboard.general.string) wrapper = FileWrapper.withContents('Exported Tasks.taskpaper', data) filesaver = new FileSaver() urlObj = await filesaver.show(wrapper) console.log(urlObj.string) urlObj.open() } } catch(err){ new Alert(err.name, err.message).show() } })();
omnifocus://localhost/omnijs-run?script=try%7Bvar%20TaskpaperText%20%3D%20%60%2D%20PROJECT%20C%20%40parallel%28true%29%20%40autodone%28false%29%20%40due%28tomorrow%29%0A%09Project%20notes%2E%0A%09%2D%20TASK%20E%20%40parallel%28true%29%20%40autodone%28false%29%0A%09%2D%20TASK%20F%20%40parallel%28true%29%20%40autodone%28false%29%60%0APasteboard%2Egeneral%2Estring%20%3D%20TaskpaperText%0Avar%20newItems%20%3D%20pasteTasksFromPasteboard%28Pasteboard%2Egeneral%29%0Adocument%2Ewindows%5B0%5D%2Eperspective%20%3D%20Perspective%2EBuiltIn%2EInbox%0Adocument%2Ewindows%5B0%5D%2EselectObjects%28newItems%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Paste TaskPaper Content
 

TaskPaperText = `- PROJECT C @parallel(true) @autodone(false) @due(tomorrow) Project notes. - TASK E @parallel(true) @autodone(false) - TASK F @parallel(true) @autodone(false)` Pasteboard.general.string = TaskPaperText newItems = pasteTasksFromPasteboard(Pasteboard.general) document.windows[0].perspective = Perspective.BuiltIn.Inbox document.windows[0].selectObjects(newItems)

Here’s an example script that will log the selected tasks to the console as TaskPaper content:

Log Selected Tasks as TaskPaper


(async () => { try { selection = document.windows[0].selection if(!selection.tasks.length > 0){ throw { name: "Selection Issue", message: "Please select one or more tasks." } } tasks = selection.tasks clipboard = Pasteboard.makeUnique() copyTasksToPasteboard(tasks, clipboard) data = Data.fromString(clipboard.string) wrapper = FileWrapper.withContents('Pasteboard.txt', data) console.log( wrapper.contents.toString()) } catch(err) { new Alert(err.name, err.message).show() } })();

The convertTasksToProjects() Function

For example, to convert each top-level inbox item into a new project at the end of your library and capture the resulting projects:

Convert Inbox Tasks to Projects


newProjects = convertTasksToProjects(inbox, library.ending)

An example plug-in for converting selected tasks into projects can be found here.

Plug-In Examples

The following plug-ins incorporate the use of properties and functions of the Database class combined with Action Forms to automate multi-step processes with the database:

Move Selected Projects into New Folder
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.move-selected-projects-into-folder", "version": "1.8", "description": "Move the selected projects into a new top-level folder.", "label": "Move Selected Projects into New Folder", "shortLabel": "Move Projects", "paletteLabel": "Move Projects" "image": "plus.rectangle.on.folder.fill" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // CONSTRUCT THE FORM var inputForm = new Form() // CREATE FORM ELEMENTS: TEXT INPUT, CHECKBOX textField = new Form.Field.String("folderName", null, null) checkSwitchField = new Form.Field.Checkbox( "ensureUniqueName", "Ensure folder name is unique", false ) // ADD THE ELEMENTS TO THE FORM inputForm.addField(textField) inputForm.addField(checkSwitchField) // VALIDATE FORM CONTENT inputForm.validate = function(formObject){ // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT var textValue = formObject.values['folderName'] var checkboxValue = formObject.values['ensureUniqueName'] if (!textValue){return false} if (!checkboxValue){return true} if (checkboxValue){ folderNames.forEach(function(name){ if (name.toLowerCase() === textValue.toLowerCase()){ throw "ERROR: That folder name is in use." } }) return true } } // GET THE NAMES OF ALL FOLDERS folderNames = new Array() library.apply(item => { if (item instanceof Folder){folderNames.push(item.name)} }) // DIALOG PROMPT AND OK BUTTON TITLE formPrompt = "Enter name for new top-level folder:" buttonTitle = "Continue" // DISPLAY THE FORM DIALOG formObject = await inputForm.show(formPrompt, buttonTitle) // PERFORM PROCESSES USING FORM DATA textValue = formObject.values['folderName'] folder = new Folder(textValue) moveSections(selection.projects, folder) // SHOW THE FOLDER fldID = folder.id.primaryKey urlStr = "omnifocus:///folder/" + fldID URL.fromString(urlStr).open() }); action.validate = function(selection, sender){ return (selection.projects.length > 0) }; return action; })();
Move Selected Tasks into New Project
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.move-selected-tasks-into-project", "version": "1.3", "description": "Move the selected tasks into a new top-level project.", "label": "Move Selected Tasks into New Project", "shortLabel": "Move Tasks", "paletteLabel": "Move Tasks", "image": "square.and.arrow.down.fill" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ // CONSTRUCT THE FORM inputForm = new Form() // CREATE FORM ELEMENT: TEXT INPUT textField = new Form.Field.String("projectName", "Project Name", null) // CREATE FORM ELEMENT: OPTION MENU popupMenu = new Form.Field.Option( "projectType", "Project Type", [0, 1, 2], ["Parallel","Sequential","Single Actions"], 0 ) // ADD THE ELEMENTS TO THE FORM inputForm.addField(textField) inputForm.addField(popupMenu) // VALIDATE FORM CONTENT inputForm.validate = function(formObject){ // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT textValue = formObject.values['projectName'] return ((textValue) ? true:false) } // DIALOG PROMPT AND OK BUTTON TITLE formPrompt = "Enter the name for the new top-level project and select its project type:" buttonTitle = "Continue" // DISPLAY THE FORM DIALOG formObject = await inputForm.show(formPrompt, buttonTitle) // PERFORM PROCESSES USING FORM DATA textValue = formObject.values['projectName'] menuItemIndex = formObject.values['projectType'] // CREATE PROJECT AND MOVE TASKS project = new Project(textValue) moveTasks(selection.tasks, project) // SET THE PROJECT TYPE if (menuItemIndex === 1){ project.task.sequential = true } else if (menuItemIndex === 2){ project.containsSingletonActions = true } // SHOW THE PROJECT projID = project.id.primaryKey urlStr = "omnifocus:///task/" + projID URL.fromString(urlStr).open() }); action.validate = function(selection, sender){ return (selection.tasks.length > 0) }; return action; })();
Move Selected Tasks into New Action Group
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.move-selected-tasks-into-new-action", "version": "1.4", "description": "Move the selected tasks into a new top-level action group.", "label": "Move Selected Tasks into New Action Group", "shortLabel": "Move Tasks", "paletteLabel": "Move Tasks", "image": "archivebox.circle" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ // CONSTRUCT THE FORM inputForm = new Form() // CREATE FORM ELEMENTS: TEXT INPUT textField = new Form.Field.String("groupName", null, null) // CREATE FORM ELEMENT: OPTION MENU popupMenu = new Form.Field.Option( "actionType", "Action Type", [0, 1], ["Parallel","Sequential"], 0 ) // ADD THE ELEMENTS TO THE FORM inputForm.addField(textField) inputForm.addField(popupMenu) // VALIDATE FORM CONTENT inputForm.validate = function(formObject){ // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT textValue = formObject.values['groupName'] return ((textValue) ? true:false) } // DIALOG PROMPT AND OK BUTTON TITLE formPrompt = "Provide name and type for new top-level action group:" buttonTitle = "Continue" // DISPLAY THE FORM DIALOG formObject = await inputForm.show(formPrompt, buttonTitle) // PERFORM PROCESSES USING FORM DATA textValue = formObject.values['groupName'] menuItemIndex = formObject.values['actionType'] taskGroup = new Task(textValue) moveTasks(selection.tasks, taskGroup) // SET THE PROJECT TYPE if (menuItemIndex === 1){ taskGroup.sequential = true } // SHOW THE ACTION taskID = taskGroup.id.primaryKey urlStr = "omnifocus:///task/" + taskID URL.fromString(urlStr).open() }); action.validate = function(selection, sender){ return (selection.tasks.length > 0) }; return action; })();
Duplicate Selected Tasks
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.duplicate-selected-tasks", "version": "1.1", "description": "This action will duplicate the selected tasks in their parent container.", "label": "Duplicate Selected Tasks", "shortLabel": "Duplicate Tasks" "paletteLabel": "Duplicate Tasks" "image": "doc.on.doc.fill" }*/ (() => { const action = new PlugIn.Action(function(selection, sender){ duplicatedTasks = new Array() selection.tasks.forEach(function(task){ insertionLocation = task.containingProject if(insertionLocation === null){insertionLocation = inbox.ending} dupTasks = duplicateTasks([task], insertionLocation) duplicatedTasks.push(dupTasks[0].id.primaryKey) }) idStr = duplicatedTasks.join(",") URL.fromString("omnifocus:///task/" + idStr).open() }); action.validate = function(selection, sender){ return (selection.tasks.length > 0) }; return action; })();
Duplicate Selected Tasks and Set New Due Date
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.dup-sel-tasks-set-due-date", "version": "1.1", "description": "This action will duplicate the selected tasks in their parent container, and assign the user-provided date as the due date.", "label": "Duplicate Selected Tasks with New Due Date", "shortLabel": "Duplicate with Due Date", "paletteLabel": "Duplicate with Due Date", "image": "square.and.arrow.down.on.square.fill" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ today = new Date(new Date().setHours(0,0,0,0)) tomorrow = new Date(today.setDate(today.getDate() + 1)) dateInputField = new Form.Field.Date( "dateInput", null, null ) inputForm = new Form() inputForm.addField(dateInputField) inputForm.validate = function(formObject){ dateInput = formObject.values["dateInput"] status = (dateInput && dateInput >= tomorrow) ? true:false console.log(status) return status } formPrompt = "Enter a due date/time after today:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) dateInput = formObject.values["dateInput"] console.log(dateInput) duplicatedTasks = new Array() selection.tasks.forEach(function(task){ insertionLocation = task.containingProject if(insertionLocation === null){insertionLocation = inbox.ending} dupTasks = duplicateTasks([task], insertionLocation) dupTask = dupTasks[0] dupTask.dueDate = dateInput duplicatedTasks.push(dupTask.id.primaryKey) }) idStr = duplicatedTasks.join(",") URL.fromString("omnifocus:///task/" + idStr).open() }); action.validate = function(selection, sender){ return (selection.tasks.length > 0) }; return action; })();