Disabling groups via action

Hi. Is it posible to disable and enable a folder (⌘D) with an action? I am using multiple presets for that, but I wonder if there is a way to do it with an action.

Reason is that I have a growing collection of presets, and it is cumbersome to export all of them to keep current backups of everything.

There's probably a simpler way than my proposed solution, but I'll share never the less – I'm more comfortable scripting BTT than using the GUI.

Running this JavaScript code in a Run Real JavaScript action will toggle a folder based on its UUID which you can obtain by right clicking on the folder and selecting Copy Selected Item UUID.

(async () => {
    // Define the trigger/folder UUID as a variable.
    // ⚠️ Replace this with your actual folder UUID
    let folderUUID = "B5C3E953-EE53-481C-86BA-C562B5D80022";

    // Retrieve the folder configuration using its UUID.
    let folder_config = await get_trigger({ uuid: folderUUID });

    // Parse the JSON string into an object.
    let folder_config_obj = JSON.parse(folder_config);

    // Get 'BTTEnabled2' to determine if the folder is currently enabled (1) or disabled (0).
    let BTTEnabled2 = folder_config_obj.BTTEnabled2;

    // Toggle the state: if enabled (1) then disable it (0), and vice versa.
    let newState = (BTTEnabled2 === 1) ? 0 : 1;

    // Replace the old value of BTTEnabled2 with the new state.
    folder_config_obj.BTTEnabled2 = newState;

    // Prepare the update definition object.
    let updateDefinition = JSON.stringify(folder_config_obj);

    // Update the trigger with the new configuration.
    let updateResponse = await update_trigger({ uuid: folderUUID, json: updateDefinition });

    // Return the update response to BetterTouchTool.
    returnToBTT(updateResponse);
})();

Thanks fortred2. I have tried your solution but it is not working in my setup.
The script puts an icon (disabled eye with a triangle warning sign) in all the triggers inside the folder but they are executed normally.

Any other ideas? Any way to get the same behaviour as with the "Enable/Diasble selected trigger" right click option?

what kind of triggers do you have in that group?

fortred2 solution works when replacing 'BTTEnabled2' by 'BTTEnabled' in his code. (What is the difference between them?)

The problem now is that when using the same code in an app or conditional activation group, the folders and triggers created there also appear in the section For All Apps. I have made a very minimal example preset to show this. Hope this helps me explaining this.

These are the steps to recreate the preset that reproduce the problem every time:

  1. Create "folder 1 All" (enabled) and "folder 2 All" (disabled) in For All Apps and a keyboard shortcut (command + T) to toggle the enable state of both folders.

  2. Create an app (TextEdit) and repeat the process, adding folders "folder 1 TexEdit" (enabled) and "folder 2 Texedit" (disabled)

  3. After testing command + T in All Apps enabling and disabling works ok but when testing in Textedit, the folders created in TextEdit group now appear also in For All Apps. Deleting from For All Apps also deletes them from TextEdit.

Enable:Disable triggers example.bttpreset.bttpreset (39.5 KB)

Hi @sazun, thanks for sharing the preset.

That's interesting. When I first shared with you the original script, I remember that I was only able to get the script to work using BTTEnabled2 and not BTTEnabled. Surprisingly, when I try to use the script now, it indeed only works with BTTEnabled and not BTTEnabled2. Why is this the case? I do not know. What is the difference between them? I also do not know. The only reference I can find is from a comment from @Andreas_Hegenberg from 2018 where he states that BTTEnabled and BTTEnabled2 are there for "legacy" reasons. Provide a list of valid properties for JSON representation of BTT actions - #6 by Andreas_Hegenberg


I also experience the unexpected behavior of the folders from the "TextEdit" app group being created/duplicated into the "For All Apps" group when triggering when "TextEdit" is active.

At first, I thought the cause of this might be due to your current implementation which has a lot of duplicate code which may be interfering with with itself, causing the observed unexpected behavior. Specifically, your current implementation involves four separate Run Real JavaScript Actions and two separate cmd + T Triggers.. Two of the four Run Real JavaScript Actions are defined to run sequentially, one after another, and are assigned to the cmd + T Trigger in the "For All Apps" group. The other two Run Real JavaScript actions also are defined to run sequentially, one after another, and are assigned to a different cmd + T Trigger defined in the "TextEdit" app group.

I decided to consolidate all four of these "Run Real JavaScript" Actions into a single script to see if this would resolve the unexpected behavior. Additionally, this updated and consolidated script also displays a notification to help understand if the "folder All" folders enabled state got toggled or if the "TextEdit Folders" folders enabled state got toggled. It also communicates if they got toggled ENABLED (:white_check_mark:) or DISABLED (:x:).

@sazun Migration notes:

  1. Delete your "cmd T" Trigger in the "TextEdit" app group (this includes the two Run Real JavaScript" Actions assigned to it).
  2. For the "cmd T" Trigger in the "For All Apps" group, replace the two "Run Real JavaScript" Actions with a single "Run Real JavaScript" Action. The script for this Action is below:
/*
 * Toggle Script for BetterTouchTool Folders
 *
 * Purpose:
 *   - Reads the current "BTTEnabled" state from each folder's configuration.
 *   - Toggles the state: if enabled (1) it becomes disabled (0), and vice versa.
 *   - Displays a notification summarizing the state transition for each folder,
 *     using emojis (❌ for OFF and ✅ for ON).
 *
 * Instructions:
 *   - Update the folderUUIDs object below with your folder names and UUIDs.
 *   - Map this script to a keyboard shortcut in BetterTouchTool.
 */

(async () => {
  // Define an object with arrays of folder objects.
  // Update the folder names and UUIDs below to match your configuration.
  let folderUUIDs = {
    folder_All: [
      { name: "folder 1 All", uuid: "6547CAE3-9405-450E-A31C-71498E9AE6EC" }, // folder 1 All
      { name: "folder 2 All", uuid: "E2330D40-A4EB-4968-AB0E-1690588CE146" }  // folder 2 All
    ],
    folder_TextEdit: [
      { name: "folder 1 TextEdit", uuid: "1543AB41-97D1-4F4C-95EC-B473EA4491FA" }, // folder 1 TextEdit
      { name: "folder 2 TextEdit", uuid: "0F67E44C-EB89-4B1D-8958-EBFC5B087B16" }  // folder 2 TextEdit
    ]
  };

  // Retrieve the active app name from BetterTouchTool variables.
  let active_app_name = await get_string_variable({
    variable_name: "active_app_name",
  });

  // Choose the folder set based on the active app.
  let folderKey = active_app_name === "TextEdit" ? "folder_TextEdit" : "folder_All";

  // Get the array of folder objects for iteration.
  let folderArray = folderUUIDs[folderKey];

  let updateResponses = [];
  let toggleMessages = []; // Collect messages to display in the notification.

  // Iterate over each folder object.
  for (let folder of folderArray) {
    try {
      let folderUUID = folder.uuid;
      let folderName = folder.name;

      // Retrieve the folder configuration using its UUID.
      let folder_config = await get_trigger({ uuid: folderUUID });

      // Parse the JSON configuration into an object.
      let folder_config_obj = JSON.parse(folder_config);

      // Get 'BTTEnabled' to determine the current state (enabled = 1, disabled = 0).
      let BTTEnabled = folder_config_obj.BTTEnabled;

      // Toggle the state: if enabled then disable; if disabled then enable.
      let newState = BTTEnabled === 1 ? 0 : 1;

      // Determine the previous and new states using emojis.
      let previousEmoji = BTTEnabled === 1 ? "✅" : "❌";
      let newEmoji = newState === 1 ? "✅" : "❌";

      // Add a message for this folder showing the state transition.
      toggleMessages.push(`${folderName}: ${previousEmoji} → ${newEmoji}`);

      // Update the folder configuration with the new state.
      folder_config_obj.BTTEnabled = newState;

      // Convert the updated configuration object back into a JSON string.
      let updateDefinition = JSON.stringify(folder_config_obj);

      // Update the trigger with the new configuration.
      let updateResponse = await update_trigger({
        uuid: folderUUID,
        json: updateDefinition,
      });

      // Store the update response for debugging purposes.
      updateResponses.push({ uuid: folderUUID, response: updateResponse });
    } catch (error) {
      // In case of error, add an error message for this folder.
      updateResponses.push({ uuid: folder.uuid, error: error.message });
    }
  }

  // Create and display a notification summarizing the toggle actions.
  let notificationMessage = toggleMessages.join("; ");
  await display_notification({
    title: folderKey, // The title shows the folder set being updated.
    subTitle: notificationMessage
  });

  // Return the array of update responses to BetterTouchTool.
  returnToBTT(updateResponses);
})();

Unfortunately, even with this updated script, I still observe the unexpected behavior of the folders in the "TextEdit" group being duplicated into the "For All Apps" group when triggering when TextEdit is the active app.

Lastly, I also created another script to help debug this situation. This script simply displays a notification of the current BTTEnabled state for the "folder All" folders or the "folder TextEdit" folders.

/*
 * State Display Script for BetterTouchTool Folders
 *
 * Purpose:
 *   - Reads the current "BTTEnabled" state from each folder's configuration.
 *   - Displays a notification showing each folder's name along with an emoji representing its state:
 *       ✅ means the folder is enabled.
 *       ❌ means the folder is disabled.
 *
 * Instructions:
 *   - Update the folderUUIDs object below with your folder names and UUIDs if needed.
 *   - Use this script for debugging to verify the current state without toggling.
 *   - Map this script to a different keyboard shortcut in BetterTouchTool.
 */

(async () => {
  // Define an object with arrays of folder objects.
  // Update the folder names and UUIDs below as necessary.
  let folderUUIDs = {
    folder_All: [
      { name: "folder 1 All", uuid: "6547CAE3-9405-450E-A31C-71498E9AE6EC" }, // folder 1 All
      { name: "folder 2 All", uuid: "E2330D40-A4EB-4968-AB0E-1690588CE146" }  // folder 2 All
    ],
    folder_TextEdit: [
      { name: "folder 1 TextEdit", uuid: "1543AB41-97D1-4F4C-95EC-B473EA4491FA" }, // folder 1 TextEdit
      { name: "folder 2 TextEdit", uuid: "0F67E44C-EB89-4B1D-8958-EBFC5B087B16" }  // folder 2 TextEdit
    ]
  };

  // Retrieve the active app name from BetterTouchTool variables.
  let active_app_name = await get_string_variable({
    variable_name: "active_app_name",
  });

  // Choose the folder set based on the active app.
  let folderKey = active_app_name === "TextEdit" ? "folder_TextEdit" : "folder_All";

  // Get the array of folder objects for iteration.
  let folderArray = folderUUIDs[folderKey];

  let stateMessages = [];

  // Iterate over each folder object.
  for (let folder of folderArray) {
    try {
      let folderUUID = folder.uuid;
      let folderName = folder.name;

      // Retrieve the folder configuration using its UUID.
      let folder_config = await get_trigger({ uuid: folderUUID });

      // Parse the JSON configuration into an object.
      let folder_config_obj = JSON.parse(folder_config);

      // Get 'BTTEnabled' to determine if the folder is enabled (1) or disabled (0).
      let BTTEnabled = folder_config_obj.BTTEnabled;

      // Determine the emoji representing the current state.
      let stateEmoji = BTTEnabled === 1 ? "✅" : "❌";

      // Add a message for this folder.
      stateMessages.push(`${folderName}: ${stateEmoji}`);
    } catch (error) {
      // In case of error, record an error message for this folder.
      stateMessages.push(`${folder.name}: Error - ${error.message}`);
    }
  }

  // Create and display a notification summarizing the current states.
  let notificationMessage = stateMessages.join("; ");
  await display_notification({
    title: folderKey, // The title reflects the folder set being checked.
    subTitle: notificationMessage
  });

  // Return the notification message to BetterTouchTool.
  returnToBTT(notificationMessage);
})();

@sazun Please try out these updated scripts and tell me if you observe the same behavior as me.

Also, @Andreas_Hegenberg your expertise and help would be really helpful here! :pray:

EDIT #1:
I forgot to mention that apart from the unexpected behavior of the "Folder TextEdit" folders being duplicated into the "For All Apps" group, the updated script I provided actually works as intended.

EDIT #2:
Also, please note that the enabled/disabled UI in the BTT GUI doesn't automatically update when the script is triggered. To see the updated UI, you have to force a refresh by navigating to another Trigger category tab or closing and reopening the BTT configuration GUI.

that duplication sounds like a bug, I'll have a look. thanks for all the details!

If in doubt, set both BTTEnabled and BTTEnabled2 :wink:

1 Like

The bug is gone in 5.276. Thanks Andreas!

1 Like

In the meantime, I have made a javascript function to toggle the enabled state between different folders but without using apps or cags. My use case for this is that I have a numeric keyboard, where different triggers are assigned to buttons depending on the active application. I additionally display a window with the actual active triggers, so it works as a poors man (and cheap!) version of a Stream Deck.

As it is working reasonably well, I am sharing it in case someone is interested. I have made a little preset to ilustrate:

Folders Enabling Example (btt forum version).bttpresetzip (3.9 KB)

You can modify at convenience, creating all the folders (with the keyboard bindings or whatever) you are interested in and putting their corresponding uuids
there. In the apps property, include the names of the applications for which you want that particular folder to be available (or include "all" if you want that folder to be available in every app). Then, when activating the function (press command + option + 1), the enabled folder will be changed accordingly.

In the following example, if the active application is TextEdit, it toggles between f1, f2 and f3; if Safari, between f1 and f4, Preview between f1, f2 and f4 and in any other application, the enabled folder will be f1.

  let keybs = {
    f1: {
      uuid: "469C1431-745E-486E-BB2C-5E5BE1372A14",
      apps: ["all"]
    },
    f2: {
      uuid: "03BE2B92-F4DE-4CCC-9B4C-25664C264BA6",
      apps: ["TextEdit", "Preview"]
    },
    f3: {
      uuid: "2CBC1841-1962-41D7-8617-4173D5F04A33",
      apps: ["TextEdit"]
    },
    f4: {
      uuid: "BFF9BE41-F152-48CE-808A-BE32BF96B654",
      apps: ["Safari", "Preview"]
    }
  };

Thanks again to @fortred2 and @Andreas_Hegenberg for your help!

EDIT #1 :
Here is the complete javascript funtion in case anyone wants to take a look without downloading the preset:

async function someJavaScriptFunction() {

  // Update the folder UUIDs and apps names below as necessary
  let keybs = {
    f1: {
      uuid: "469C1431-745E-486E-BB2C-5E5BE1372A14",
      apps: ["all"]
    },
    f2: {
      uuid: "03BE2B92-F4DE-4CCC-9B4C-25664C264BA6",
      apps: ["TextEdit", "Preview"]
    },
    f3: {
      uuid: "2CBC1841-1962-41D7-8617-4173D5F04A33",
      apps: ["TextEdit"]
    },
    f4: {
      uuid: "BFF9BE41-F152-48CE-808A-BE32BF96B654",
      apps: ["Safari", "Preview"]
    }
  };


  // Adds key-value properties:
  // Retrieves the folder configuration from BetterTouchTool and stores it in the 'configuration' property
  // Sets the 'enabled' property to 1 if the folder is enabled, 0 if disabled
  
  async function addProperties(obj) {
    for (const key in obj) {
      // Retrieve the folder configuration using its UUID and store it in the 'config' property
      let config = await get_trigger({ uuid: obj[key].uuid });
      // Parse the JSON string into an object.
      let config_obj = JSON.parse(config);
      // Set the 'configuration' property of the folder
      obj[key].configuration = config_obj;
      // Use 'BTTEnabled' to determine if the folder is currently enabled (1) or disabled (0)
      obj[key].enabled = config_obj.BTTEnabled;
    }
    return obj;
  }

  // Changes the enabled state of the folders:
  // It enables the first folder for the active application if none is enabled 
  // or the next one to the currently enabled in case there are more than one enabled.
  // Returns an array of the relevant folders keys for the active application

  async function aptFolders(obj) {
    // Retrieve the active app name from BetterTouchTool variables.
    var active_app_name = await get_string_variable({ variable_name: "active_app_name" });

    // Find all enabled folders keys
    // Find all folders keys that include the active app or "all"
    const enabledFoldersKeys = [];
    const applicationFoldersKeys = [];
    for (const key in obj) {
      if (obj[key].enabled === 1) {
        enabledFoldersKeys.push(key);
      }
      if (obj[key].apps.includes(active_app_name) || obj[key].apps.includes("all")) {
        applicationFoldersKeys.push(key);
      }
    }
  
    // If there are no enabled folders relevant to the active application, select the first one.
    // In other case find the first one that is enabled and set the next one to selectedFolder
    const enabledIndex = applicationFoldersKeys.findIndex(key => obj[key].enabled === 1);
    const selectedFolder = (enabledIndex !== -1 && enabledIndex + 1 < applicationFoldersKeys.length)
      ? applicationFoldersKeys[enabledIndex + 1]
      : applicationFoldersKeys[0];

    // Set the enabled property of all folders in obj to 0 except for the selectedFolder
    for (const key in obj) {
      obj[key].enabled = (key === selectedFolder) ? 1 : 0;
    }

    // Return the array of relevant folders, with the enabled properties of all folders in obj updated
    return selectedFolder;
  }

  // Sets the folders enabled state accordingly to its enabled property
  async function setFolders() {
    for (const key in keybs) {
      // Update the BTTEnabled property in the configuration to match the enabled state
      keybs[key].configuration.BTTEnabled = keybs[key].enabled;

      // Convert the configuration object to a JSON string
      let updateDefinition = JSON.stringify(keybs[key].configuration);

      // Update the folder configuration in BetterTouchTool
      let updateResponse = await update_trigger({ uuid: keybs[key].uuid, json: updateDefinition });
    }
  }

  // Main function to add properties, update folder states, and apply the changes
  async function main() {
    // Add configuration and enabled properties to each folder
    await addProperties(keybs);

    // Determine folders relevant to the active application and update their enabled states
    const selectedFolder = await aptFolders(keybs);

    // Push the updated enabled states back to BTT
    await setFolders();

    // Return the list of relevant folders
    return selectedFolder
  }

  // Call main, return the selected folder to BTT
  const result = await main();
  returnToBTT(result);

}

1 Like

nice job! Happy to have helped.