Can BTT do that? Please GUIDE 🙏

I’m currently using BetterTouchTool to map key sequences to Arc browser tab shortcuts, like qq to Cmd+1 etc., so I can jump to frequently used tabs more easily.

I am using qq to trigger applescript
tell application "System Events"
tell process "Arc"
set frontmost to true
keystroke "1" using {command down}
end tell
end tell

The issue is, Arc’s tab positions shift around as I open or rearrange tabs, so the shortcuts stop pointing to the correct ones. I'd love a more reliable way to jump to specific tabs — maybe by matching part of the tab title or URL, instead of relying on position.

Is there any way in BTT to:

  • Detect open Arc tabs by name or URL?
  • Trigger a tab switch based on that?
  • Maybe use AppleScript or a clever workaround?

Appreciate any advice or pointers — would love to streamline this workflow!

Thanks :pray:

Hi @amandaswtz.

This is achievable using AppleScript as well as using JavaScript For Automation (JXA). I prefer scripting in JXA.

Here's a JXA script that when run will activate the (unpinned) Arc tab whose URL contains folivora:

(() => {
  "use strict";

  // Define the search query string to filter tab URLs.
  // This value can be modified to search for a different string.
  const queryString = "folivora";

  // This variable represents the location of the tab in the sidebar.
  // It can be 'topApp', 'pinned', or 'unpinned'.
  const tabLocation = "unpinned";

  // Get a reference to the Arc browser application.
  // This allows interaction with Arc's scripting interface as described in its sdef.
  const arc = Application("Arc");

  // Access the first (frontmost) window of Arc and filter its tabs.
  // Only include tabs where:
  //   1. The tab's URL contains the specified query string.
  //   2. The tab's location matches the defined tabLocation.
  const frontWindowTabs = arc.windows.at(0).tabs.where({
    _and: [
      { url: { _contains: queryString } }, // Tab's URL includes the query string
      { location: tabLocation }             // Tab's location is as specified (e.g., 'unpinned')
    ],
  });

  // Retrieve the first matching tab.
  const firstMatchingTab = frontWindowTabs[0];

  // Send the "select" command to the matching tab to make it active.
  firstMatchingTab.select();
})();

Likewise, here's a script that will activate the Arc tab whose title contains BetterTouchTool Community:

(() => {
  "use strict";

  // Define the search query string to filter tab titles.
  // This value can be modified to search for a different string.
  const queryString = "BetterTouchTool Community";

  // This variable represents the location of the tab in the sidebar.
  // It can be 'topApp', 'pinned', or 'unpinned'.
  const tabLocation = "unpinned";

  // Get a reference to the Arc browser application.
  // This allows interaction with Arc's scripting interface as described in its sdef.
  const arc = Application("Arc");

  // Access the first (frontmost) window of Arc and filter its tabs.
  // Only include tabs where:
  //   1. The tab's title contains the specified query string.
  //   2. The tab's location matches the defined tabLocation.
  const frontWindowTabs = arc.windows.at(0).tabs.where({
    _and: [
      { title: { _contains: queryString } }, // Tab's title includes the query string
      { location: tabLocation }               // Tab's location is as specified (e.g., 'unpinned')
    ],
  });

  // Retrieve the first matching tab.
  const firstMatchingTab = frontWindowTabs[0];

  // Send the "select" command to the matching tab to make it active.
  firstMatchingTab.select();
})();

Finally, here's a complete BTT configuration JSON that includes a key sequences trigger (specifically, qq) connected with a Run Apple Script (async in background) Action (configured using Apple JavaScript for Automation (JAX)). If you copy and then paste this JSON into the BTT configuration GUI then you'll replicate my exact setup.

[
  {
    "BTTActionCategory" : 0,
    "BTTLastUpdatedAt" : 1744741028.1389461,
    "BTTTriggerType" : 624,
    "BTTTriggerTypeDescriptionReadOnly" : "Please Select a Trigger ",
    "BTTTriggerClass" : "BTTTriggerTypeKeySequence",
    "BTTUUID" : "DB12DDAE-906C-4D6B-8F30-74A6BFB3CCFC",
    "BTTPredefinedActionType" : 366,
    "BTTPredefinedActionName" : "Empty Placeholder",
    "BTTEnabled" : 1,
    "BTTEnabled2" : 1,
    "BTTOrder" : 0,
    "BTTAdditionalActions" : [
      {
        "BTTActionCategory" : 0,
        "BTTLastUpdatedAt" : 1744746624.383714,
        "BTTTriggerParentUUID" : "DB12DDAE-906C-4D6B-8F30-74A6BFB3CCFC",
        "BTTIsPureAction" : true,
        "BTTTriggerClass" : "BTTTriggerTypeKeySequence",
        "BTTUUID" : "C99211E8-633A-49A6-880E-C3D0E3B0EAD8",
        "BTTPredefinedActionType" : 252,
        "BTTPredefinedActionName" : "Run JavaScript for Automation (async in background)",
        "BTTAdditionalActionData" : {
          "BTTScriptType" : 1,
          "BTTAppleScriptString" : "(() => {\n  \"use strict\";\n\n  \/\/ Define the search query string to filter tab titles.\n  \/\/ This value can be modified to search for a different string.\n  const queryString = \"BetterTouchTool Community\";\n\n  \/\/ This variable represents the location of the tab in the sidebar.\n  \/\/ It can be 'topApp', 'pinned', or 'unpinned'.\n  const tabLocation = \"unpinned\";\n\n  \/\/ Get a reference to the Arc browser application.\n  \/\/ This allows interaction with Arc's scripting interface as described in its sdef.\n  const arc = Application(\"Arc\");\n\n  \/\/ Access the first (frontmost) window of Arc and filter its tabs.\n  \/\/ Only include tabs where:\n  \/\/   1. The tab's title contains the specified query string.\n  \/\/   2. The tab's location matches the defined tabLocation.\n  const frontWindowTabs = arc.windows.at(0).tabs.where({\n    _and: [\n      { title: { _contains: queryString } }, \/\/ Tab's title includes the query string\n      { location: tabLocation }               \/\/ Tab's location is as specified (e.g., 'unpinned')\n    ],\n  });\n\n  \/\/ Retrieve the first matching tab.\n  const firstMatchingTab = frontWindowTabs[0];\n\n  \/\/ Send the \"select\" command to the matching tab to make it active.\n  firstMatchingTab.select();\n})();",
          "BTTAppleScriptUsePath" : false,
          "BTTAppleScriptRunInBackground" : true,
          "SelectedAction" : 195,
          "BTTScriptLocation" : 0
        },
        "BTTInlineAppleScript" : "(() => {\n  \"use strict\";\n\n  \/\/ Define the search query string to filter tab titles.\n  \/\/ This value can be modified to search for a different string.\n  const queryString = \"BetterTouchTool Community\";\n\n  \/\/ This variable represents the location of the tab in the sidebar.\n  \/\/ It can be 'topApp', 'pinned', or 'unpinned'.\n  const tabLocation = \"unpinned\";\n\n  \/\/ Get a reference to the Arc browser application.\n  \/\/ This allows interaction with Arc's scripting interface as described in its sdef.\n  const arc = Application(\"Arc\");\n\n  \/\/ Access the first (frontmost) window of Arc and filter its tabs.\n  \/\/ Only include tabs where:\n  \/\/   1. The tab's title contains the specified query string.\n  \/\/   2. The tab's location matches the defined tabLocation.\n  const frontWindowTabs = arc.windows.at(0).tabs.where({\n    _and: [\n      { title: { _contains: queryString } }, \/\/ Tab's title includes the query string\n      { location: tabLocation }               \/\/ Tab's location is as specified (e.g., 'unpinned')\n    ],\n  });\n\n  \/\/ Retrieve the first matching tab.\n  const firstMatchingTab = frontWindowTabs[0];\n\n  \/\/ Send the \"select\" command to the matching tab to make it active.\n  firstMatchingTab.select();\n})();",
        "BTTEnabled" : 1,
        "BTTEnabled2" : 1,
        "BTTOrder" : 766
      }
    ],
    "BTTKeySequence" : {
      "BTTPauseBetween" : 0.29999999999999999,
      "BTTCharactersToDeleteAfterwards" : 2,
      "BTTKeyCount" : 4,
      "BTTKeySequenceDownKeys" : [
        {
          "BTTKEYCharacter" : " Q",
          "BTTKEYCode" : 12,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1,
          "BTTKEYTime" : 766433280
        },
        {
          "BTTKEYCharacter" : " Q",
          "BTTKEYCode" : 12,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1,
          "BTTKEYTime" : 766433280
        }
      ],
      "BTTKeySequenceMixedKeys" : [
        {
          "BTTKEYCharacter" : " Q",
          "BTTKEYCode" : 12,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1,
          "BTTKEYTime" : 766433280
        },
        {
          "BTTKEYCharacter" : " Q",
          "BTTKEYCode" : 12,
          "BTTKEYRequired" : 1,
          "BTTKEYTime" : 766433280
        },
        {
          "BTTKEYCharacter" : " Q",
          "BTTKEYCode" : 12,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1,
          "BTTKEYTime" : 766433280
        },
        {
          "BTTKEYCharacter" : " Q",
          "BTTKEYCode" : 12,
          "BTTKEYRequired" : 1,
          "BTTKEYTime" : 766433280
        }
      ],
      "BTTKeySequenceUpKeys" : [
        {
          "BTTKEYCharacter" : " Q",
          "BTTKEYCode" : 12,
          "BTTKEYRequired" : 1,
          "BTTKEYTime" : 766433280
        },
        {
          "BTTKEYCharacter" : " Q",
          "BTTKEYCode" : 12,
          "BTTKEYRequired" : 1,
          "BTTKEYTime" : 766433280
        }
      ]
    }
  }
]

I hope this is helpful.

1 Like

Getting ReferenceError: Can't find variable: Application

It works fine on my machine. Could you share a screenshot?

I see the problem, you're running "Real JavaScript" instead of "Apple JavaScript For Automation (JXA)".

It should look like this:

1 Like

Okay this work along with AppleScript right?

I add
tell application "System Events"
tell process "Arc"
set frontmost to true
end tell
end tell

Thank you, it worked!

1 Like

i thank you everyday for this @fortred2

1 Like

You're a real gem for the BTT community. Thank you so much.

1 Like

Thank you both for your kind words.

2 Likes

Wow, insane. Didn't know one can do this. Thanks @fortred2

1 Like

Hey peeps,

I think I know what this does.. and I want to do this to! But three questions:

  1. Will this work with other browsers
  2. Is the last script a combo of the first two? Is that the only one I need to copy?
  3. As a script noob, do I create a trigger and then paste this into the scripting window OR is there a place to paste it in from the start without creating a trigger?

Any help would be appreciated.

RD

The script as it stands only works for the Arc browser. It can be modified to work with other browsers.

You have a couple misunderstandings. Firstly, the third code block contains JSON text, it's not a "script".

To explain JSON to a beginner:

  • JSON stands for JavaScript Object Notation.
  • It's a way to store and exchange data that looks like a simple text.
  • Think of it like a list of items or a set of labeled boxes where each box has a name and a value.
  • It uses two main structures:
    • Objects: Curly braces {} with key-value pairs inside, like a dictionary or map.
    • Arrays: Square brackets [] with a list of items inside.
  • Example:
{
  "name": "Alice",
  "age": 25,
  "hobbies": ["reading", "gaming", "hiking"]
}
  • This means:
    • The person’s name is Alice.
    • The person is 25 years old.
    • The person likes reading, gaming, and hiking.
  • JSON is easy for humans to read and write, and computers can easily understand it too.
  • It’s widely used for sending data between websites and apps.

Now, in BetterTouchTool everything in BetterTouchTool can be represented as JSON :exploding_head:

Read the docs here: JSON Definitions · BetterTouchTool Documentation

This means when you select a Trigger or Action in BetterTouchTool and paste the content of your clipboard to a text editor, you'll see JSON text! And if you paste the content of your clipboard in the BetterTouchTool editor, you'll see the Trigger or Action that you just copied duplicated!

There are two primary ways people share BetterTouchTool configurations. 1) Preset Exports. 2) JSON configuration.

So, the third code block is not a "script" it's the JSON configuration of my BTT setup. It is not "a combo of the first two". It's the Trigger and Action configuration of one of the two previous JavaScripts (I can't remember which one). If you copy the entire JSON and paste it into the BTT window, you'll see a new key sequences Trigger and a Apple JavaScript for Automation (JAX) Action.

If you copy and paste the JSON I provided before, you shouldn't have to create any new Triggers since the JSON already contains the Trigger definition. I've aways had a hard time trying to describe exactly where you paste the BTT JSON configurations. You paste it "anywhere" in the Trigger view. @Andreas_Hegenberg is there a better way to describe this?

Hey @fortred2 !

This was a good explanation for me as I have some basic understanding of how programming works (I’ll stress basic - lol).

I appreciate the time and effort that you put into your response to me. It has helped me understand what this is and perhaps how I can implement it. I like tinkering, so once I have a basic to intermediate understanding of something, I try from there.

Thanks again for your patience with me and I hope that others find this explanation useful too!

RD