Different topic. I work on many projects, and they come and go, sometimes daily.
When I have time (meaning less stress), I manage to create a project folder (group) in the DevonThink, which I link back in OF. There I put meeting notes, test data, and all files I get for the project.
Now I think about an alternative to simplify that:
Is it possible that I mirror just the Project Structure into the Filesystem.
That way I would always have a folder to organize everything. The problem is, can an automation access the file system?
Or better
2) A “plugin” that creates a button only for the selected item. Clicking on it should create or open a folder in the file system or a group in DevonThink (whatever is easier). So no full mirror, but would need the action to open or create and open that folder/ group.
Does somebody maybe already have a solution for my “problem” in place?
Or does one know, if this is technically doable or if there are any limitations in OF?
But the main problem I have is that OmniFocus does not support
const DT = Application('DEVONthink');
If that worked, I could let OmniFocus talk to DevonThink.
Problem with my current solution: The DevonThink URL commands are not smart enough to check if a group already exists or not. So every time I call the plugin again for the same task, a new group will be created. Has maybe someone had a DevonThink experience and an idea to solve that?
It’s sad that these two applications, both with scripting capabilities, are seemingly so difficult to connect.
Hi @bkuhn, I have returned home and took a quick look at your request. Here is a proof of concept about writing selected OmniFocus project to DEVONthink with a link back to OmniFocus. This draft checks whether a group with the same link as the selected project exists before creating it.
If you want, I can improve it in the future. Let me know if you have questions about the code or how it works.
P.S: This script assumes the configured database exists.
Source Code (JavaScript for Automation):
(() => {
"use strict";
// Copyright (c) 2026 Gabriel Scalise
// Email - gabriels8020@gmail.com
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// The above copyright notice and this permission notice
// shall be included in ALL copies
// or substantial portions of the Software.
// The author retains all rights. The contract covers the hours worked and the right to use, but not sell, a copy.
// CONFIGURATION ----------
// DATABASE NAME
const
dbName = "Corpus";
// jxaContext :: IO ()
const jxaContext = (dbName, projDct) => {
// main :: IO ()
const main = () => {
const
appDT = Application("DEVONthink");
const
db = appDT.databases.byName(dbName)
return groupFoundOrCreatedByURL(db)(
projDct
)
};
// groupFoundOrCreatedByURL :: DT Database -> JS Dict -> DT Record
const groupFoundOrCreatedByURL = db => dct => {
const
appDT = Application("DEVONthink"),
groups = db.records.whose({
"url": dct["URL"]
})
return groups.length > 0 ? (
groups.at(0)
) : appDT.createRecordWith(
Object.assign({}, dct, {
"record type": "group"
}), {
in: db.root()
});
}
// MAIN --
return main()
};
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => "Left" in e ?
fl(e.Left) :
fr(e.Right);
// OMNIAUTOMATION CONTEXT ---------------------------------------
const omniJSContext = () => {
// main :: IO ()
const main = () => {
const
seln = document.windows[0].selection.projects;
return seln.length === 0 ? (
Left("No projects selected.")
) : Right(({
"name": seln[0].name,
"URL": `omnifocus:///project/${seln[0].id.primaryKey}`
}))
};
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// MAIN --
return main();
};
// evalOmniJSWithArgs :: String -> Function ->
// [...OptionalArgs] -> a
const evalOmniJSWithArgs = (appName, f, ...args) =>
Application(appName).evaluateJavascript(
`(${f})(${args.map(JSON.stringify)})`
);
return either(
x => x
)(
dct => jxaContext(
dbName,
dct
)
)(
evalOmniJSWithArgs(
"OmniFocus",
omniJSContext
)
)
})();
Thank you very much for the time. With “Automations,” do you refer to the Automator app or to the Shortcuts app? Sorry if this is a silly question.
I tried with “Automator” and was able to make the action appear in the “Services” menu. It just seems that I don’t know which data needs to be passed in order that it works:
The code I shared is written in JavaScript for Automation. The easiest way to execute it would be Script Editor, setting language tab to JavaScript. Or you could use Keyboard Maestro, Alfred, etc…Let me know if that is sufficiently clear.
You can certainly execute it in Automator, but in this case we don’t need any data to be passed in. You are probably better with the options I described above as a first step.
Devonthink tags are really groups with some extra logic, and tags can be nested just like groups. The act of tagging a document is the same as creating a replicant in the tag, which is just a group. If the tag doesn’t exist, it’s created. If it exists, it’s used.
You can create a hierarchy of files in groups, or you could put every file in DT’s Inbox and tag them following the same hierarchy. Either way, you would have your taxonomy of documents.
Thanks for the help! It turned out DevonThink makes my life too difficult, at least for what I really need in the end.
So I solved that with Agenda. There, using url-callbacks, I either open or create and open a project.
That means I get the needed Project Folder on Demand. Without the need of any (external) script, just the OmnifocusPlugin.