38

How do you set your Cocoa application as the default web browser?

I want to create an application that is launched by default when the user clicks on an HTTP or HTTPS link in other applications (Mail, iChat etc.).

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
georgebrock
  • 28,393
  • 13
  • 77
  • 72
  • https://github.com/Lord-Kamina/SwiftDefaultApps#usage-notes could help you as it allows to reassociate URI scheme handlers to different applications from its GUI. – Jaime Hablutzel Oct 16 '18 at 15:28

4 Answers4

81

There are four steps to making an app that can act as the default web browser. The first three steps allow your app to act as a role handler for the relevant URL schemes (HTTP and HTTPS) and the final step makes it the default role handler for those schemes.

1) Add the URL schemes your app can handle to your application's info.plist file

To add support for http:// and https:// you'd need to add the following to your application's info.plist file. This tells the OS that your application is capable of handling HTTP and HTTP URLs.

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>http URL</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>http</string>
        </array>
    </dict>
    <dict>
        <key>CFBundleURLName</key>
        <string>Secure http URL</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>https</string>
        </array>
    </dict>
</array>

2) Write an URL handler method

This method will be called by the OS when it wants to use your application to open a URL. It doesn't matter which object you add this method to, that'll be explicitly passed to the Event Manager in the next step. The URL handler method should look something like this:

- (void)getUrl:(NSAppleEventDescriptor *)event 
    withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
  // Get the URL
  NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] 
    stringValue];

  //TODO: Your custom URL handling code here
}

3) Register the URL handler method

Next, tell the event manager which object and method to call when it wants to use your app to load an URL. In the code here I'm passed self as the event handler, assuming that we're calling setEventHandler from the same object that defines the getUrl:withReplyEvent: method.

You should add this code somewhere in your application's initialisation code.

NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
[em 
  setEventHandler:self 
  andSelector:@selector(getUrl:withReplyEvent:) 
  forEventClass:kInternetEventClass 
  andEventID:kAEGetURL];

Some applications, including early versions of Adobe AIR, use the alternative WWW!/OURL AppleEvent to request that an application opens URLs, so to be compatible with those applications you should also add the following:

[em
  setEventHandler:self 
  andSelector:@selector(getUrl:withReplyEvent:) 
  forEventClass:'WWW!' 
  andEventID:'OURL'];

4) Set your app as the default browser

Everything we've done so far as told the OS that your application is a browser, now we need to make it the default browser.

We've got to use the Launch Services API to do this. In this case we're setting our app to be the default role handler for HTTP and HTTPS links:

CFStringRef bundleID = (CFStringRef)[[NSBundle mainBundle] bundleIdentifier];
OSStatus httpResult = LSSetDefaultHandlerForURLScheme(CFSTR("http"), bundleID);
OSStatus httpsResult = LSSetDefaultHandlerForURLScheme(CFSTR("https"), bundleID);
//TODO: Check httpResult and httpsResult for errors

(It's probably best to ask the user's permission before changing their default browser.)

Custom URL schemes

It's worth noting that you can also use these same steps to handle your own custom URL schemes. If you're creating a custom URL scheme it's a good idea to base it on your app's bundle identifier to avoid clashes with other apps. So if your bundle ID is com.example.MyApp you should consider using x-com-example-myapp:// URLs.

georgebrock
  • 28,393
  • 13
  • 77
  • 72
  • 1
    Launch Services is part of CoreServices, not Carbon. (As such, it is surviving the 64-bit transition.) – Peter Hosey Nov 10 '08 at 16:42
  • 1
    There's no need to create an `NSString` literal and then cast it to `CFStringRef`, as in `(CFStringRef)@"http"`. Instead, directly create a `CFString` literal: `CFSTR("http")`. – Jeremy W. Sherman Nov 03 '10 at 18:42
  • Can you post a download link to an example project? I'm not exactly clear on where to paste all the pieces of code. Also, any idea how to achieve this as a Cocoa-AppleScript application? – Geoffrey Booth Aug 21 '13 at 17:13
  • 1
    Is there anything extra/different that has to be done to register an application as an email app? – RJVB Sep 04 '14 at 01:08
  • 1
    It's worth noting that in step #3 above, the event handler registration must occur within init (or similar). It will NOT work if placed in applicationDidFinishLaunching: – tomwhipple Nov 03 '10 at 17:15
  • Great tutorial. One question: where does code from point 4 go? Thanks – ed22 Jun 22 '15 at 15:09
  • I have an app that starts JVM through JNI in separate thread. If I start the thread the app no longer receives the event (kAEGetURL), if I don't it gets the event all right. Any ideas what's wrong? The Cocoa gui remains running in the main thread while JVM has it's own. – ed22 Jul 15 '15 at 14:12
  • what if we want to use a document handler app to tie the extensions for office documents with our application? – Skywalker Apr 13 '22 at 06:14
4

macOS Big Sur and Up

Copy and paste this code into your info.plist

<key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLName</key>
            <string>Web site URL</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>http</string>
                <string>https</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleURLName</key>
            <string>http URL</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>http</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleURLName</key>
            <string>Secure http URL</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>https</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeName</key>
            <string>HTML document</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.html</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeName</key>
            <string>XHTML document</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.xhtml</string>
            </array>
        </dict>
    </array>
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>GIF image</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>com.compuserve.gif</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>HTML document</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.html</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>XHTML document</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.xhtml</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>JavaScript script</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>com.netscape.javascript-​source</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>JPEG image</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.jpeg</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>MHTML document</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>org.ietf.mhtml</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>HTML5 Audio (Ogg)</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>org.xiph.ogg-audio</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>HTML5 Video (Ogg)</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>org.xiph.ogv</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>PNG image</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.png</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>SVG document</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.svg-image</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>Plain text document</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.text</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>HTML5 Video (WebM)</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>org.webmproject.webm</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>WebP image</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>org.webmproject.webp</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>org.chromium.extension</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleTypeIconFile</key>
            <string>document.icns</string>
            <key>CFBundleTypeName</key>
            <string>PDF Document</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>com.adobe.pdf</string>
            </array>
        </dict>
    </array>

Your app will be shown in the system preferences and will be default browser

Make sure you do this

    func application(_ application: NSApplication, open urls: [URL]) {
// do a for loop, I recommend it
}
  • I've done this, but it seems like it doesn't change on subsequent installs. Only during first install. Say afterwards, if a user changes his default app settings manually, then we install the app again, it doesn't change the default app only for those extensions :( – Skywalker Apr 12 '22 at 02:57
2

If you just want to change the default helper app for http(s), you can do so in the Safari preferences. There you’ll find a drop down which will let you select all the registered handler applications for http. To automatically have the app set itself as the default browser see the previous instructions.

Raphael Schweikert
  • 18,244
  • 6
  • 55
  • 75
2

Update for macOS Ventura 13.0

Settings

Default browser now appears on:
System Settings > Desktop & Dock > Default web browser

info.plist

info.plist remains the same, pasting it here again for completeness' sake. Some answers suggest adding many more entries to this file, but that is not really necessary, this is the leaner and minimum requirements option.

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeIconSystemGenerated</key>
        <integer>1</integer>
        <key>CFBundleTypeName</key>
        <string>HTML document</string>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>LSHandlerRank</key>
        <string>Default</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>public.html</string>
        </array>
    </dict>
    <dict>
        <key>CFBundleTypeIconSystemGenerated</key>
        <integer>1</integer>
        <key>CFBundleTypeName</key>
        <string>XHTML document</string>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>LSHandlerRank</key>
        <string>Default</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>public.xhtml</string>
        </array>
    </dict>
</array>
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>Website URL</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>http</string>
            <string>https</string>
        </array>
    </dict>
</array>
<key>NSUserActivityTypes</key>
<array>
    <string>NSUserActivityTypeBrowsingWeb</string>
</array>

Making it default

LSSetDefaultHandlerForURLScheme is deprecated and now we have the functionality on NSWorkspace, supporting both a completion handler and async/await.

setDefaultApplication(at:toOpenURLsWithScheme:completion:)

func setDefaultApplication(at applicationURL: URL, toOpenURLsWithScheme urlScheme: String) async throws

Example

try? await NSWorkspace.shared.setDefaultApplication(
                                     at: Bundle.main.bundleURL,
                                     toOpenURLsWithScheme: "http")

UX

Now the system prompts the user to confirm the change of default browser, which is a great improvement.

HTTP or HTTPS

I found out that using http scheme only is enough, as given in the example above. Trying https will throw an error.

Older versions (original answer)

In order to appear as an option on System Preferences > General > Default web browser (at least for macOS 11) you need to add the document types for HTML and XHTML to the Info.plist (after the 4 steps already described on the accepted answer), like this:

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>HTML document</string>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>public.html</string>
        </array>
    </dict>
    <dict>
        <key>CFBundleTypeName</key>
        <string>XHTML document</string>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>public.xhtml</string>
        </array>
    </dict>
</array>
vicegax
  • 4,709
  • 28
  • 37