Configuration ideas - johnste/finicky GitHub Wiki

Feel free to add your own ideas for how to use Finicky.

Force https for all urls

rewrite: [{
    match: ({url}) => url.protocol === "http",
    url: (url) => {
      url.protocol = "https";
      return url;
    }
}]

Matching an array of multiple apps

handlers: [{
    // Open any link clicked in Mail & Outlook in Google Chrome
    match: (_url, {opener}) =>
        ["com.apple.mail", "com.microsoft.Outlook"].includes(opener?.bundleId ?? ""),
    browser: "Google Chrome"
}]

or

handlers: [{
    // Open any link clicked in Mail & Outlook in Google Chrome
    match: (_url, {opener}) =>
        ["Mail", "Microsoft Outlook"].includes(opener.name),
    browser: "Google Chrome"
}]

Open links of example.com including 'Finicky' in the path

handlers: [{
    match: ({url}) =>
        url.host.includes("example.com") && url.pathname.includes("Finicky"),
    browser: "Firefox"
}]

Replace domain of urls to amazon.com with smile.amazon.com

rewrite: [{
    match: "amazon.com/*",
    url: {
        host: "smile.amazon.com"
    }
}]

Remove all marketing/tracking information from URLs

rewrite: [
  {
    match: m => true,
    url: url => {
      const lower = s => String(s || "").toLowerCase();
      const removeExact = new Set(["fbclid", "gclid", "dclid", "gbraid", "wbraid", "msclkid", "ttclid", "twclid", "li_fat_id", "mkt_tok", "mc_cid", "mc_eid", "igsh", "si", "feature", "ref", "ref_src", "spm"]);
      const removePrefixes = ["utm_", "uta_", "ga_", "pk_", "vero_"];
      const removeByValue = new Set(["share", "social", "social_media", "social_network"]);

      const keys = [...url.searchParams.keys()];

      for (const key of keys) {
        const k = lower(key), v = lower(url.searchParams.get(key));
        const isExact = removeExact.has(k);
        const isPrefix = removePrefixes.some(p => k.startsWith(p));
        const isValueNoise = (k === "source" || k === "src" || k === "medium") && removeByValue.has(v);

        if (isExact || isPrefix || isValueNoise) {
          url.searchParams.delete(key);
        }
      }

      return url.href;
    },
  },
],

Redirect google links to duckduckgo.com

rewrite: [{
    match: finicky.matchDomains(["google.com"]),
    url: "https://duckduckgo.com"
}]

Open Trello links in Trello app

handlers: [{
    match: finicky.matchDomains(["trello.com"]),
    url: ({url}) =>
        ({...url, protocol: "trello"}),
    browser: "Trello"
}]

Open Spotify links in Spotify app

handlers: [{
    match: finicky.matchDomains("open.spotify.com"),
    browser: "Spotify"
}]

Open Zoom links in Zoom app with or without password

  handlers: [{
    match: /zoom\.us\/join/,
    browser: "us.zoom.xos"
  }],
  rewrite: [{
    match: (url) => url.host.includes("zoom.us") && url.pathname.includes("/j/"),
    url: (url) => {
      try {
        const match = url.search.match(/pwd=(\w*)/);
        var pass = match ? '&pwd=' + match[1] : '';
      } catch {
        var pass = "";
      }
      const pathMatch = url.pathname.match(/\/j\/(\d+)/);
      var conf = 'confno=' + (pathMatch ? pathMatch[1] : '');
      url.search = conf + pass;
      url.pathname = '/join';
      url.protocol = "zoommtg";
      return url;
    }
  }]

Open Apple Music links in the Music app

handlers: [{
    // Open Apple Music links directly in Music.app
    match: [
        "music.apple.com*",
        "geo.music.apple.com*",
    ],
    url: {
        protocol: "itmss"
    },
    browser: "Music",
}]

Open Microsoft Teams links in the native app

handlers: [{
    match: finicky.matchHostnames(['teams.microsoft.com']),
    browser: 'com.microsoft.teams',
    url: ({url}) =>
        ({...url, protocol: 'msteams'}),
}]

Open Figma links in Figma app

handlers: [{
    match: "https://www.figma.com/file/*",
    browser: "Figma",
}]

Skip vk.com link tracking (vk.com/away.php)

Also added to Remove all marketing/tracking information from URLs, above.

rewrite: [{
    match: /vk\.com\/away.php/,
    url: ({url}) => {
        const match = url.search.match(/to=(.+)/)
        return !match ? url : decodeURIComponent(decodeURIComponent(match[1]));
    }
}]

Match multiple conditions

For example, you may want to only open matching URLs in a specific browser when opening those URLs from within a particular application. In that case, you need to match on both the opener and the url:

handlers: [{
    // Open Google Drive in Firefox if opened from Slack
    match: ({opener, url}) =>
        opener.bundleId === "com.tinyspeck.slackmacgap" && url.host.includes("drive.google.com"),
    browser: "Firefox"
}]

Match only one of multiple conditions

For example, you may want to only open matching URLs from any of a group of domains:

  handlers: [
    {
      match: (url) => {
        const chromeDomains = ["office.com", "box.com", "microsoft.com"];
        return chromeDomains.some(domain => url.host.endsWith(domain));
      },
      browser: "Google Chrome",
    },
  ],

Open Jitsi links in Jitsi desktop app for MacOS

handlers: [{
    match: ({url}) => url.host.includes("jitsi.your-selfhosted-server.com") ||
        url.host.includes("meet.jit.si"),
    url: ({url}) => {
        return {
            ...url,
            protocol: "jitsi-meet",
            host: url.host,
            pathname: url.pathname
        };
    },
    browser: "/Applications/Jitsi\ Meet.app"
}]

Open links clicked in Telegram in specific Profile in Edge

Since 3.2.0, you can specify the browser profile as an option.

{
    match: ({opener}) => ["Telegram"].includes(opener.name),
    browser: {
        name: "Microsoft Edge",
        profile: "Profile 1",
    };
}

Open installed desktop applications instead of web apps

This example assumes you have installed Figma, Linear, and Slite desktop apps. But you can make it work with any other app.

Requirements

  • install the targeted apps you want to support,
  • check their URL schemes with:
defaults read /Applications/THE_APP.app/Contents/Info.plist CFBundleURLTypes

finicky Configuration

handlers: [{
  match: ({url}) => !url.protocol.match(/^https?$/),
  browser: "Finder"
}],

rewrite: [{
  match: ({url}) => url.host.split('.').some((c) => ['figma', 'linear', 'slite'].includes(c)),
  url: ({url}) => {
    const protocol = url.host.match(/\.?(\w+)\.\w\w\w?$/)[1];
    const exceptions = { 'linear': ['uploads.linear.app'] };
    if (!(protocol in exceptions) || !exceptions[protocol].includes(url.host)) {
      return {...url, protocol };
    }
    return url;
  }
}]

Open Chrome PWA apps with the correct URL

Chrome PWAs (installed via Chrome's "Install as app") use app_mode_loader under the hood, which ignores URL arguments and always opens the app's default page. To pass the actual URL, launch Chrome directly with --app-id and --app-launch-url-for-shortcuts-menu-item flags. Find the PWA's app ID from its Info.plist:

defaults read ~/Applications/Chrome\ Apps.localized/YOUR_APP.app/Contents/Info.plist CrAppModeShortcutID

Example with Google Meet:

handlers: [{
  match: /^https?:\/\/meet\.google\.com\//,
  browser: (url) => ({
    name: "Google Chrome",
    profile: "Default",
    args: [
      "--app-id=kjgfgldnnfoeklkmfkjfagphfepbbdan",
      `--app-launch-url-for-shortcuts-menu-item=${url.toString()}`
    ]
  })
}]

profile is required to force a new Chrome instance that respects the args. Without it, macOS reuses the existing Chrome process and drops the flags. Requires Finicky v4.2.1+.

Open Discord links in Discord app

handlers: [{
  match: "https://discord.com/*",
  url: { protocol: "discord" },
  browser: "Discord",
}]

Jump to #report on Spamcop pages

rewrite: [{
  match: finicky.matchHostnames("www.spamcop.net"),
  url: { hash: "report" }
}]

Rewrite TikTok videos to random Proxitok proxies to avoid creating TikTok user

rewrite: [{
  // Redirect Tiktok video links to use Proxitok public proxies
  match: ({ url }) => (url.host.endsWith("tiktok.com") && url.pathname.startsWith('/@')) || url.host.endsWith("vm.tiktok.com"),
  url: ({ url }) => {
    // See more https://github.com/pablouser1/ProxiTok/wiki/Public-instances
    const selectRandomTikTokProxy = () => {
      const TIKTOK_PROXIES = [
        "proxitok.pabloferreiro.es", // Official
        "proxitok.pussthecat.org",
        "tok.habedieeh.re",
        "proxitok.esmailelbob.xyz",
        "proxitok.privacydev.net",
        "tok.artemislena.eu",
        "tok.adminforge.de",
        "tt.vern.cc",
        "cringe.whatever.social",
        "proxitok.lunar.icu",
        "proxitok.privacy.com.de",
        "cringe.seitan-ayoub.lol",
        "cringe.datura.network",
        "tt.opnxng.com",
        "tiktok.wpme.pl",
        "proxitok.r4fo.com",
        "proxitok.belloworld.it",
      ]
      return TIKTOK_PROXIES[Math.floor(Math.random() * TIKTOK_PROXIES.length)]
    }
    return {
      protocol: "https",
      host: selectRandomTikTokProxy(),
      // Prepend pathname with /@placeholder/video to match ProkiTok urls
      pathname: (url.pathname.startsWith('/@') ? url.pathname : `/@placeholder/video${url.pathname}`)
    }
  }
]}

Open Slack link in Slack App

// Based on @opalelement's answer https://github.com/johnste/finicky/issues/96#issuecomment-844571182
// Team ID can be found in the browser URL : https://slack.com/help/articles/221769328-Locate-your-Slack-URL-or-ID
// Free, Pro, and Business+ plans => Team or workspace ID starts with a T in https://app.slack.com/client/TXXXXXXX/CXXXXXXX
// Enterprise grid plans => Org ID starts with an E in https://app.slack.com/client/EXXXXXXX/CXXXXXXX
const workSlackTeamMapping = {
  // 'subdomain': 'TXXXXXXX',
  // 'acmecorp.enterprise': 'EXXXXXXX',
  // 'acmecorp': 'EXXXXXXX',
};

const personalSlackMapping = {
  // personal slacks
};

const slackSubdomainMapping = {
  ...workSlackTeamMapping,
  ...personalSlackMapping,
};

const slackRewriter = {
  match: ["*.slack.com/*"],
  url: function (urlObj) {
    const subdomain = urlObj.host.slice(0, -10); // before .slack.com
    const pathParts = urlObj.pathname.split("/");

    let team,
      patterns = {};
    if (subdomain != "app") {
      if (!Object.keys(slackSubdomainMapping).includes(subdomain)) {
        console.log(
          `No Slack team ID found for ${urlObj.host}`,
          `Add a correct team ID to ~/.finicky.js to allow direct linking to Slack.`
        );
        return urlObj;
      }
      team = slackSubdomainMapping[subdomain];

      if (subdomain.slice(-11) == ".enterprise") {
        patterns = {
          file: [/\/files\/\w+\/(?<id>\w+)/],
        };
      } else {
        patterns = {
          file: [/\/messages\/\w+\/files\/(?<id>\w+)/],
          team: [/(?:\/messages\/\w+)?\/team\/(?<id>\w+)/],
          channel: [
            /\/(?:messages|archives)\/(?<id>\w+)(?:\/(?<message>p\d+))?/,
          ],
        };
      }
    } else {
      patterns = {
        file: [
          /\/client\/(?<team>\w+)\/\w+\/files\/(?<id>\w+)/,
          /\/docs\/(?<team>\w+)\/(?<id>\w+)/,
        ],
        team: [/\/client\/(?<team>\w+)\/\w+\/user_profile\/(?<id>\w+)/],
        channel: [
          /\/client\/(?<team>\w+)\/(?<id>\w+)(?:\/(?<message>[\d.]+))?/,
        ],
      };
    }

    for (let [host, host_patterns] of Object.entries(patterns)) {
      for (let pattern of host_patterns) {
        let match = pattern.exec(urlObj.pathname);
        if (match) {
          let search = `team=${team || match.groups.team}`;

          if (match.groups.id) {
            search += `&id=${match.groups.id}`;
          }

          if (match.groups.message) {
            let message = match.groups.message;
            if (message.charAt(0) == "p") {
              message = message.slice(1, 11) + "." + message.slice(11);
            }
            search += `&message=${message}`;
          }

          let outputStr = `slack://${host}?${search}`;
          console.log(
            `Rewrote Slack URL ${urlObj.href} to deep link ${outputStr}`
          );
          
          return new URL(outputStr);
        }
      }
    }

    return urlObj;
  },
};

module.exports = {
  defaultBrowser: "Google Chrome",
  rewrite: [slackRewriter],
  handlers: [
    {
      match: ({ url }) => {
        // Check for both 'slack:' and 'slack' since the property might not include the colon
        return url.protocol === "slack:" || url.protocol === "slack";
      },
      browser: "Slack",
    },
    {
      // Optional. If these work url cannot be converted, open them is work browser
      // Login in work workspace unfortunately lands on the personal browser, just copy the link to the work browser
      match: ({ url }) => {
        const workDomains = Object.keys(workSlackTeamMapping).map(subdomain => subdomain + ".slack.com");
        return workDomains.includes(url.host);
      },
      browser: "Google Chrome", // your work browser
    },
  ],
};

Open link in browser selector like Browserosaurus

On occasions, it's not possible to select a browser programmatically, it's useful to open a browser selector. Since there's none yet in Finicky, it's possible to use Browserosaurus (⚠️ Please note that Browserosaurus was archived by its owner on Aug 2, 2025).

Example with slack login URLs, e.g. when adding a Workspace from the app :

const browsers = {
  personal: {
    name: 'org.mozilla.firefox',
    openInBackground: false,
  },
  work: {
    name: 'com.google.Chrome',
    openInBackground: false,
  },
  selector: {
    name: 'com.browserosaurus',
    openInBackground: false,
  }
}

module.exports = {
  handlers = [
    {
      // Select browser when slack wants to login
      // https://app.slack.com/ssb/add?s=1&v=4.41.90&ssb_vid=.<hex id>&ssb_instance_id=<UUID>
      match: [
        "app.slack.com/ssb/*"
      ],
      browser: browsers.selector,
    },
  ]
}

Browserosaurus lists all apps that can handle links Slack, Spotify, Zoom, etc. however they can be sorted in the preferences.

Open links in the ClickUp desktop app

module.exports = {
  handlers: [
    {
      match: ({ url }) => url.protocol === "clickup",
      browser: "ClickUp"
    },
  ],
  rewrite: [
    {
      match: "https://link-inbox.clickup.com/CL0/*",
      url: ({ url }) => ({
        ...url,
        protocol: "clickup",
        host: "",
        // Links from e-mail notifications:
        // https://link-inbox.clickup.com/CL0/https:%2F%2Fapp.clickup.com%2Ft%2F86c1au25p%3Fcomment=90150095296062%26threadedComment=90150095416601%26utm_source=email-notifications%26utm_type=2%26utm_field=comment/1/010001943ba132c9-6ba4ceb3-9da1-4771-8022-60d330932eb7-000000/U4wqDVk2GS865NI30mhkcQc0HqXDzSuXR5-V2AnWBDg=386
        // URL unescape, remove header, remove CL0, remove https://
        pathname: url.pathname.slice(1).replaceAll("%2F", "/").replaceAll("%3F", "?").replaceAll("%26", "&").replace("CL0/https://", "")
      })
    },
    {
      match: "https://app.clickup.com/*",
      url: ({ url }) => ({
        ...url,
        protocol: "clickup",
        host: "",
        pathname: url.pathname.slice(1)
      })
    },
  ],
};
⚠️ **GitHub.com Fallback** ⚠️