Auto-Categorize Files Moved Into A Folder

Note: best use BetterTouchTool 5.373 or higher for this.

There are probably many other ways to achieve the same, but I'd like to share how to setup auto-categorization of files when moving them to some folder.

In this example I want to categorize files moved into the ~/Downloads folder.
Pdfs shall be moved into a subfolder ~/Downloads/PDFS
Zip files shall be moved into ~/Downloads/Archives

You can of course extend / change this however you want.

You can download the configured example here:
downloads_folder_changed_example.bttpreset (5.4 KB)

1.) Create a "File or Folder Did Change" trigger in the "Automations & Named & Other Triggers" section. Make sure to set it to "File Renamed / File Moved"

2.) Assign a "Run Real Java Script" action to it and assign this simple script. Adapt the "moveFile" function to your needs (just change the paths and your username)

async function moveFile() {
    // adapt the username
    let yourUsername = "andi";

    // define which files should go into which subfolder
    let moveToFolder = {
        ".zip": `/Users/${yourUsername}/Downloads/Archives/`,
        ".pdf": `/Users/${yourUsername}/Downloads/PDFS/`,
        ".mov": `/Users/${yourUsername}/Downloads/Videos/`,
        ".txt": `/Users/${yourUsername}/Downloads/Text/`,
        ".docx": `/Users/${yourUsername}/Downloads/Text/`
        // you can add as many mappings as you want
    }

    let movedTo = await reallyMoveFileWithMapping(moveToFolder);
    return `moved to ${movedTo}`;
}

// you don't need to change this
async function reallyMoveFileWithMapping(moveToFolder) {
    let filePath = await get_string_variable("BTTLastChangedFileOrFolder");

    // get the file extension
    const idx = filePath.lastIndexOf('.');
    const ext = idx > -1 ? filePath.slice(idx).toLowerCase() : '';

    // get the target path for the extension
    const target = moveToFolder[ext];
    if (!target) {
        return `no mapping for extension “${ext}”`;
    }

    if (filePath.startsWith(target)) {
        return "already in target folder";
    }

    // make sure the target folder exists
    await runShellScript({ script: `mkdir -p "${target}"` });
    // move the file
    await runShellScript({ script: `mv "${filePath}" ${target}`});
 
    return target;
}

Thanks for sharing! Additional idea would be to combine this with an LLM (preferably running locally using Ollama or LM Studio) to add additional file organization functionality.