Open work and personal links in different browsers
Learn how to send links to different browsers when clicked.
Next ChapterAPIs used today | Description |
hs.loadSpoon | Allows you to load user-created Hammerspoon libraries into your config. |
hs.task | An async (non-blocking) library for running shell commands. |
URLDispatcher.spoon | This library becomes your system default browser in order to intercept all URL clicks, so you can redirect them to your desired browser. |
What you’re building #
In this chapter, you’ll learn how to open links in different browsers when clicked, based on URL pattern. This is handy for work computers–if you want to keep your personal browsing and your work browsing separate, this is the project for you!
To make this work, you’re going to use some Chrome profile tricks, and the URLDispatcher Spoon. In the Hammerspoon world, Spoons are installable packages that you can load from a URL and require()
in your config files. Spoons are similar to Ruby gems, Python eggs, JavaScript npm packages, etc.
Install URLDispatcher #
- First, download URLDispatcher.spoon.zip.
- Extract the ZIP file.
- There should be a extracted file called
URLDispatcher.spoon
. - Double click that file, and it will automatically install itself to
~/.hammerspoon/Spoons/URLDispatcher.spoon
.

URLDispatcher.spoon
file.How URLDispatcher works #
The URLDispatcher injects itself as the default link handler in macOS. Whenever a link is clicked, the URLDispatcher library detects the click, and hands the url
off to you to do whatever you want with it.
In our case, we’re going to set it up to open different URL patterns in separate Chrome profiles.
Here’s the short version of what we’re going to make:

hs.loadSpoon("URLDispatcher")
local function openInChromeWorkProfile(url)
-- TODO: pretend this function does what it says
end
local function openInChromePersonalProfile(url)
-- TODO: pretend this function does what it says
end
-- Configure your URL patterns to open in the correct profile.
--
-- Each entry in this table consists of a pattern, and a function to be called
-- if a URL click matches the pattern.
spoon.URLDispatcher.url_patterns = {
-- Open corporate links in work profile
{ ".corp.company.com", nil, openInChromeWorkProfile },
-- Open all other links in the personal one
{ ".*", nil, openInChromePersonalProfile },
}
-- Start the dispatcher
spoon.URLDispatcher:start()
You just give the dispatcher a table of patterns, each consisting of a (Lua pattern, function to call when clicked)
. Then you start the dispatcher, and you’re done.
Before we do that, however, let’s make sure Chrome is setup for multiple profiles.
Configure Chrome with multiple profiles #
A Chrome profile lets you keep all your Chrome info separate, like bookmarks, history, passwords, and other settings. It’s the easiest way to keep your online work and personal life separate. In the next few steps, you’ll set up a new profile for personal browsing, and figure out the names of each of your profiles so you can reference them later on in your Lua code.
Create a new Chrome profile for personal browsing #
Assuming you currently use Chrome for work browsing, you’ll want to set up a personal profile. Follow the steps here to add a new profile to the browser.
Find the names of your Chrome profiles #
- Open a new tab in each of your Chrome profiles
- Copy and paste this URL into each of the tabs: chrome://version/#:~:text=Profile%20Path
Under the Profile Path section, look for your profile name. On my computer, I had the following paths:
/Users/dbalatero/Library/Application Support/Google/Chrome/Default
/Users/dbalatero/Library/Application Support/Google/Chrome/Profile 2
From those paths, I can see my profile names are:
- Default
- Profile 2
Yours might be slightly different from mine. In any case, write these profile names down, as you’ll be using them in the next steps.
Create a new config file #
First, make a config file to hold all your URL handling code.

touch ~/.hammerspoon/url-dispatcher.lua
And require it in your main config:

require("url-dispatcher")
Load the URLDispatcher spoon #
Loading a Spoon library in your Hammerspoon config is almost the same as requiring a file. You just call hs.loadSpoon("URLDispatcher")
.
Add this to the very top of your config file:

hs.loadSpoon("URLDispatcher")
Next, you’ll configure the dispatcher to react to various URL patterns.
Figure out your URL patterns #
Depending on the URL being clicked, we want to open them in either your work profile, or your personal profile.
I came up with a list of patterns that looked something like this:
Pattern | Description | Which profile should it open in? |
---|---|---|
https?://go/ |
Internal golinks | Work |
.corp. |
Most internal links are at .corp.company.com |
Work |
docs.google.com |
Google Docs | Work |
drive.google.com |
Google Drive | Work |
slack.com |
Slack | Work |
paper.dropbox.com |
Dropbox Paper | Work |
pagerduty.com |
PagerDuty (boooo) | Work |
mail.google.com |
Gmail | Work |
.* |
Everything else | Personal |
Right now, write down a list of patterns you want to detect, and write down which browser profile each pattern should open in.
Configure URLs to open in different Chrome profiles #
Write a click handler function #
In order to open a URL in a particular Chrome profile, you need to write a handler function. This function will open the Chrome binary and pass it the url
to open, and either --profile-directory=<your work profile>
or --profile-directory=<your personal profile>
.
Add this function to the config file:

local function openInChromeProfile(profile)
return function(url)
local task = hs.task.new(
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
nil,
function() end, -- noop, we don't need to do anything else in here
{
"--profile-directory=" .. profile,
url
}
)
task:start()
end
end
This function returns another function that we will use as the click handler for our URL patterns in the next step.
Configure your patterns #
To configure the patterns, you just need to add them to the spoon.URLDispatcher.url_patterns
table.
Each entry in the table has the following shape:
{
-- The URL pattern that this entry matches.
"docs.google.com",
-- The application bundle ID if you just want a simple way to point the URL
-- at an app. This is always `nil`, as we're going to use a function instead.
nil,
-- The function you want to be called when a URL matching this pattern is
-- clicked.
function(url)
end
}
For each pattern you came up with earlier, you’re going to add a rule to the url_patterns
table to cover it.
Take each of your patterns and add it to spoon.URLDispatcher.url_patterns
. Here’s what my config looks like:

-- Remember when you wrote down these profile names earlier?
-- FIXME: Replace these values with ones corresponding to your profiles.
local workProfile = "Default"
local personalProfile = "Profile 2"
spoon.URLDispatcher.url_patterns = {
-- Work patterns
{ "https?://go/", nil, openInChromeProfile(workProfile) }
{ ".corp.", nil, openInChromeProfile(workProfile) }
{ "docs.google.com", nil, openInChromeProfile(workProfile) }
{ "drive.google.com", nil, openInChromeProfile(workProfile) }
{ "slack.com", nil, openInChromeProfile(workProfile) }
{ "paper.dropbox.com", nil, openInChromeProfile(workProfile) }
{ "pagerduty.com", nil, openInChromeProfile(workProfile) }
{ "mail.google.com", nil, openInChromeProfile(workProfile) }
-- Personal patterns
{ ".*", nil, openInChromeProfile(personalProfile) }
}
Take the above snippet and add it to your config, then replace the URL patterns with your own.
Start the URLDispatcher #
The last step is to start the URL dispatcher. If you don’t do this, it won’t be running and won’t intercept any of your links.
Add this line to the very bottom of your config (below your url_patterns
):

spoon.URLDispatcher:start()
Next, Reload your Hammerspoon config. You’ll see a prompt asking if you want to change your default browser to Hammerspoon. Click the Use “Hammerspoon” button in the prompt.

Test it out #
Find some links and start clicking them. You should see them open in your expected Chrome profiles. If you can’t find any links to click, send yourself an message or an email with some links you want to test, so they turn into actual clickable hyperlinks.
Make your own Read Article Later menu bar
Optional: Open some URLs in Firefox or Safari #
Another option is to open all your work links in Chrome, and all your personal links in Firefox and/or Safari. This has a couple of advantages:
- It’s simpler to implement.
- It’s easier to ⌘Tab between the two browsers.
If you want that kind of setup, here’s the entire script–just copy and paste it into your config and tweak the patterns to taste:

hs.loadSpoon("URLDispatcher")
spoon.URLDispatcher.url_patterns = {
-- Work patterns
{ "https?://go/", "com.google.Chrome" },
{ ".corp.", "com.google.Chrome" },
{ "docs.google.com", "com.google.Chrome" },
{ "drive.google.com", "com.google.Chrome" },
{ "slack.com", "com.google.Chrome" },
{ "paper.dropbox.com", "com.google.Chrome" },
{ "pagerduty.com", "com.google.Chrome" },
{ "mail.google.com", "com.google.Chrome" },
-- Personal patterns
{ ".*", "org.mozilla.Firefox" },
}
spoon.URLDispatcher:start()
If you want to use Safari instead of Firefox, replace org.mozilla.Firefox
with com.apple.Safari
.
Get the entire script #
Want to just paste in this whole project to your url-dispatcher.lua
file?

TK fill this in at the very end once we're sure all the code is solid