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.
flattenedTags (TagArray r/o) • Returns a flat array of all tags in the database, sorted in their order in the database.
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.
byName(name: String) → (Folder or null) • Returns the first Folder contained directly in this array with the given name.
- ProjectArray
- An Array containing Project objects.
byName(name: String) → (Project or null) • Returns the first Project contained directly in this array with the given name.
- SectionArray
- An Array containing Project and Folder objects.
byName(name: String) → (Project or Folder or null) • Returns the first Project or Folder contained directly in this array with the given name.
- TagArray
- An Array containing Tag objects.
byName(name: String) → (Project or Folder or null) • Returns the first Tag contained directly in this array with the given name.
- TaskArray
- An Array containing Task objects.
byName(name: String) → (Project or Folder or null) • Returns the first Task contained directly in this array with the given name.
Database Properties
Here are the scripting properties of the database:
canRedo (Boolean r/o) • Returns true if there are redoable actions.
canUndo (Boolean r/o) • Returns true if there are undoable actions.
document (DatabaseDocument or null r/o) • Returns a reference to the document whose database content is displayed.
flattenedFolders (FolderArray r/o) • Returns a flat array of all folders in the database, sorted in their order in the database.
flattenedProjects (ProjectArray r/o) • Returns a flat array of all projects in the database, sorted in their order in the database.
flattenedSections (SectionArray r/o) • Returns a flat array of all folders and projects in the database, sorted by their order in the database.
flattenedTags (TagArray r/o) • Returns a flat array of all tags in the database, sorted in their order in the database.
flattenedTasks (TaskArray r/o) • Returns a flat array of all tasks in the database, including inbox items, root tasks for projects, task groups and individual tasks. Tasks are sorted in their order in the database, with the inbox preceeding the library.
folders (FolderArray r/o) • Returns references to the top-level folders in the database.
inbox (Inbox r/o) • Returns a copy of the Tasks currently in the inbox. Supports the use of the apply(…) method to iterate inbox content.
library (Library r/o) • Returns the top-level folders and projects in the database. Supports the use of the apply(…) method to iterate library content.
projects (ProjectArray r/o) • Returns the top-level proects in the database.
settings (Settings r/o) • The database preference settings.
tags (Tags r/o) • Returns the top-level tags in the database. Supports the use of the apply(…) method to iterate the entire tag heirarchy.
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:
beginning (Task.ChildInsertionLocation r/o) • A location that can be used when adding, duplicating, or moving tasks.
ending (Task.ChildInsertionLocation r/o) • A location that can be used when adding, duplicating, or moving tasks.
apply(function: Function) → (ApplyResult or null) • Calls the given function for each Task in the Inbox and recursively into any child tasks.
Library class:
beginning (Folder.ChildInsertionLocation r/o) • Returns a location refering to the beginning of the top-level projects and folders in the database.
ending (Folder.ChildInsertionLocation r/o) • Returns a location refering to the beginning of the top-level projects and folders in the database.
apply(function: Function) → (ApplyResult or null) • Calls the given function for each Folder and Project in the Library and recursively into any child folders. Note that the tasks in projects are not descended into.
Tags class:
beginning (Tag.ChildInsertionLocation r/o) • Returns a location refering to the beginning of the top-level tags in the database.
ending (Tag.ChildInsertionLocation r/o) • Returns a location refering to the beginning of the top-level tags in the database.
apply(function: Function) → (ApplyResult or null) • Calls the given function for each Tag in the Library and recursively into any child tags.
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:
objectForURL(url:URL) → ( DatabaseObject or null) • (v4.5+) Returns the DatabaseObject for the given URL, if it exists.
folderNamed(name:String) → (Folder or null) • Returns the first top-level Folder with the given name, or null.
projectNamed(name:String) → (Project or null) • Returns the first top-level Project with the given name, or null.
tagNamed(name:String) → (Tag or null) • Returns the first top-level Tag with the given name, or null.
taskNamed(name:String) → (Task or null) • Returns the first top-level Task in the inbox with the given name, or null.
foldersMatching(search: String) → (Array of Folder) • Returns each existing Folder that Smart Matches the given search. The result will be in the same order and have the same folders as would be found when searching this string in the Quick Open window.
projectsMatching(search: String) → (Array of Project) • Returns each existing Project that Smart Matches the given search. The result will be in the same order and have the same projects as would be found when searching this string in the Quick Open window.
tagsMatching(search: String) → (Array of Tag) • Returns each existing Tag that Smart Matches the search. The result will be in the same order and have the same tags as would be found when searching this string in the Quick Open window.
duplicateTags(tags:Array of Tag, position:Tag or Tag.ChildInsertionLocation) → (Array of Tag) • Makes copies of the tags and returns the new copies. The order of the inputs is not considered and the copies are returned in library order of the originals.
moveTags(tags:Array of Tag, position:Tag or Tag.ChildInsertionLocation) → (RESULTOBJ) • DESCRIPTION
duplicateSections(sections:Array of Project or Folder, position:Folder or Folder.ChildInsertionLocation) → (Array of Project or Folder) • Makes copies of the sections and returns the new copies. The order of the inputs is not considered and the copies are returned in library order of the originals.
moveSections(sections:Array of Project or Folder, position:Folder or Folder.ChildInsertionLocation) → (RESULTOBJ r/o) • DESCRIPTION
canPasteTasks(pasteboard: Pasteboard) → (Boolean) • Returns true if the pasteboard contains a type that can be imported as tasks.
copyTasksToPasteboard(tasks: Array of Task, pasteboard: Pasteboard) → ( ) • Copies the given tasks to the pasteboard in a variety of formats.
pasteTasksFromPasteboard(pasteboard: Pasteboard) → (Array of Task) • Reads the most relevant pasteboard type and imports them as tasks. The tasks should then be moved to the desired destination.
duplicateTasks(tasks:Array of Task, position:Project, Task, or Task.ChildInsertionLocation) → (Array of Task) • Makes copies of the tasks and returns the new copies. The order of the inputs is not considered and the copies are returned in library order of the originals.
moveTasks(tasks:Array of Task, position:Project, Task, or Task.ChildInsertionLocation) → (RESULTOBJ) • DESCRIPTION
save() → ( ) • Saves any unsaved changes to disk. If sync is enabled and there were unsaved changes, this also triggers a sync request.
cleanUp() → ( ) • Processes inbox items that have the required information to move into their proposed containers, performs any delayed filtering, and deletes empty items.
undo() → ( ) • Undoes the last undoable action, or throws an error if there are no undoable actions.
redo() → ( ) • Redoes the next redoable action, or throws an error if there are no redoable actions.
deleteObject(object:DatabaseObject) → ( ) • Removes the object from the Database.
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:
copyTasksToPasteboard(tasks: Array of Task, pasteboard: Pasteboard) → ( ) • Copies the given tasks to the pasteboard in a variety of formats.
canPasteTasks(pasteboard: Pasteboard) → (Boolean) • Returns true if the pasteboard contains a type that can be imported as tasks.
pasteTasksFromPasteboard(pasteboard: Pasteboard) → (Array of Task) • Reads the most relevant pasteboard type and imports them as tasks. The tasks should then be moved to the desired destination.
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()
}
})();
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
convertTasksToProjects(tasks: Array of Task, position: Folder or Folder.ChildInsertionLocation) → (Array of Project) • Converts tasks to new projects at the specified location.
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;
})();