Finicky is a macOS app that acts as your default browser. When you click a link anywhere on your system (Slack, email, terminal, etc.), Finicky intercepts it, evaluates rules from ~/.finicky.js, and opens the URL in the right browser — or the right profile of a browser.
This is useful when you work across multiple organizations and want to keep sessions, cookies, and bookmarks separated.
Finicky's official documentation covers the profile property for Chromium browsers only (e.g. browser: { name: "Google Chrome", profile: "Work" }). Firefox profile routing is undocumented, and the args property that makes it possible isn't mentioned in the wiki at all. This guide fills that gap.
Route URLs to specific Firefox profiles based on the domain or path:
open "https://github.com/orgA" → Firefox Profile "Work"
open "https://github.com/orgB" → Firefox Profile "Personal"
Recent versions of Firefox introduced a new profile management system backed by SQLite. Profiles you create through Firefox's UI are stored in:
~/Library/Application Support/Firefox/Profile Groups/<storeId>.sqlite
You can inspect these with:
sqlite3 ~/Library/Application\ Support/Firefox/Profile\ Groups/*.sqlite "SELECT * FROM Profiles;"Which shows something like:
1|Profiles/usht2jg6.default-release|Original profile|paw-print|...
2|Profiles/abc12345.Profile 1|Work|sparkle-single|...
3|Profiles/def67890.Profile 2|Personal|heart-rate|...
However, Firefox's command-line flags (-P, --profile) still use the old profile system defined in:
~/Library/Application Support/Firefox/profiles.ini
If you created your profiles through Firefox's new UI, they may not appear in profiles.ini at all — meaning the -P ProfileName flag silently does nothing.
However, the --profile /full/path/to/directory flag bypasses both systems entirely — it just tells Firefox "use this directory." The working Finicky solution in this guide uses --profile with a full path, so it doesn't depend on profiles.ini or the SQLite database at all.
Note: This step is not required for the Finicky solution below, which uses
--profile /full/path. But it's useful if you want-P ProfileNameto work for other CLI tools, scripts, or the old profile chooser dialog (firefox -P).
Check what's currently in your profiles.ini:
cat ~/Library/Application\ Support/Firefox/profiles.iniYou'll likely see only the default profiles. You need to add entries for your new profiles. Find the StoreID from the SQLite filename (e.g., afc1a1c0.sqlite → StoreID=afc1a1c0) and add sections like:
[Profile2]
Name=Work
IsRelative=1
Path=Profiles/abc12345.Profile 1
StoreID=afc1a1c0
[Profile3]
Name=Personal
IsRelative=1
Path=Profiles/def67890.Profile 2
StoreID=afc1a1c0Note: Firefox may reorder sections when it next writes to this file. That's fine.
Before touching Finicky, confirm Firefox respects the --profile flag when called directly. You need the full path to the profile directory — find it by listing what's in your Profiles folder:
ls ~/Library/Application\ Support/Firefox/Profiles/Then test:
/Applications/Firefox.app/Contents/MacOS/firefox \
--profile ~/Library/Application\ Support/Firefox/Profiles/abc12345.Profile\ 1 \
"https://example.com"This should open the URL in the correct profile. Since --profile takes a direct path to the profile directory, it works regardless of what's in profiles.ini or the new SQLite database.
Important: The direct binary call works, but open -a Firefox --args --profile ... does not when Firefox is already running. macOS Launch Services ignores --args for an already-running app. This distinction is critical.
After extensive debugging (see below), here's the configuration that actually works with Finicky v4.2.2 and modern Firefox:
// ~/.finicky.js
export default {
defaultBrowser: "Firefox",
handlers: [
{
match: ["github.com/orgA*", "*.orgA.com/*", "orgA.com/*"],
browser: (url) => ({
name: "/Applications/Firefox.app",
appType: "path",
args: [
"-n",
"--args",
"--profile",
"/Users/YOUR_USERNAME/Library/Application Support/Firefox/Profiles/abc12345.Profile 1",
url.href,
],
}),
},
{
match: ["github.com/orgB*", "*.orgB.com/*", "orgB.com/*"],
browser: (url) => ({
name: "/Applications/Firefox.app",
appType: "path",
args: [
"-n",
"--args",
"--profile",
"/Users/YOUR_USERNAME/Library/Application Support/Firefox/Profiles/def67890.Profile 2",
url.href,
],
}),
},
],
};The resulting shell command is:
open -a /Applications/Firefox.app -n --args --profile '/path/to/profile' https://example.com
Each piece matters:
| Fragment | Why |
|---|---|
open -a /Applications/Firefox.app |
Finicky always uses open. The -a flag and app path come from name + appType: "path" |
-n |
Critical. Forces open to launch a new instance. Without this, open hands off to the already-running Firefox which ignores --args entirely |
--args |
Separates open flags from Firefox flags. Everything after this is passed to Firefox as argv |
--profile '/path/to/profile' |
Tells Firefox which profile directory to use. Must be the full absolute path |
url.href |
The URL to open. Must be passed explicitly in args when using a browser function |
This is the key trick. Finicky constructs the command like this:
open -a <name> [args...]
When args contains "--args", Finicky doesn't add its own --args (to avoid doubling). So our args array ["-n", "--args", "--profile", ...] gets appended directly after -a /Applications/Firefox.app. Since -n appears before --args, macOS open interprets it as its own flag (new instance), not as a Firefox argument.
Finicky v4.2.2 has a built-in profile property that's supposed to handle this automatically. It reads profiles.ini, finds the matching profile name, and adds -P ProfileName to the command.
But it doesn't work because browsers.json (embedded in the v4.2.2 binary) only contains Chromium-based browsers. Firefox entries were added to main after the v4.2.2 release but haven't shipped yet. The profile resolution silently fails and the profile flag is never added.
You can verify by checking the debug log — you'll see profile: "Work" in "Final browser options" but the "Run command" line will just be open -a Firefox https://example.com with no profile flag.
This is what you'd expect to work. And it does — if Firefox isn't already running. When Firefox is already running, macOS open -a finds the existing instance and just sends it the URL via Apple Events, completely ignoring --args.
Adding -n forces a new instance, which should pass --args through. But -P Work triggers Firefox's profile chooser dialog instead of directly opening the named profile. This appears to be a Firefox bug or behavior change with the new profile system.
open -a /Applications/Firefox.app --args --profile '/path/to/profile' https://example.com
Same problem as above — --args is ignored when Firefox is already running.
| File | Purpose |
|---|---|
~/.finicky.js |
Your Finicky configuration |
~/Library/Application Support/Firefox/profiles.ini |
Old profile system (used by CLI flags) |
~/Library/Application Support/Firefox/Profile Groups/*.sqlite |
New profile system (used by Firefox UI) |
~/Library/Application Support/Firefox/Profiles/ |
Actual profile data directories |
~/Library/Logs/Finicky/ |
Finicky debug logs (when logRequests: true) |
~/Library/Caches/Finicky/ |
Cached/bundled config — check config_cache_*.json to verify Finicky picked up your changes |
Add logRequests: true to your config:
export default {
defaultBrowser: "Firefox",
options: {
logRequests: true,
},
handlers: [ ... ],
};Then check the log files in ~/Library/Logs/Finicky/. The Finicky UI only shows INFO level, but the log files include DEBUG messages — most importantly the "Run command" line showing the exact shell command being executed.
If you're migrating from v3, note these breaking changes:
urlStringis removed. The browser function receives the URL object directly as the first argument:browser: (url) => ({ ... }). Useurl.hreffor the string.{ url }destructuring doesn't work. The first argument is the URL, not an object containing it. Use(url) =>not({ url }) =>.matchis required. Typos likebeepmatchcause a silent validation error and Finicky falls back to the default browser.
Always test the open command directly in terminal first to isolate whether the problem is Finicky or Firefox:
# This should work (direct binary call):
/Applications/Firefox.app/Contents/MacOS/firefox \
--profile "/path/to/profile" \
"https://example.com"
# This is what Finicky needs to generate:
open -a /Applications/Firefox.app -n \
--args --profile "/path/to/profile" \
"https://example.com"# Find the SQLite database
ls ~/Library/Application\ Support/Firefox/Profile\ Groups/
# List all profiles in the new system
sqlite3 ~/Library/Application\ Support/Firefox/Profile\ Groups/*.sqlite \
"SELECT id, path, name FROM Profiles;"- johnste/finicky#90 — "Support for Firefox profiles" (closed). Long history of workarounds. The
--profile+ wrapper script approach originated here. - johnste/finicky#431 — "Args not working on latest version" (closed). Documents the
-nflag requirement and howopen -aignores--argsfor already-running apps. - johnste/finicky#211 — "Finicky + Firefox Containers" (open). Different from profiles — containers require a Firefox extension.
- macOS (Sequoia / Darwin 25.x)
- Firefox 137+ (with new profile system)
- Finicky v4.2.2