🍎 Kuba Suder's blog on Mac & iOS development


Meet Safari Web Extensions

Categories: Mac, Safari 0 comments Watch the video

Existing extension ecosystem:

  • content blockers (iOS & macOS)
  • share extensions – can run JS on the currently opened web page and return data to the extension
  • Safari app extensions on macOS

If you’re a web developer and don’t want to learn Swift to build an extension, or you have an existing extension for Chrome/Firefox/etc., you can now use the new Safari Web Extensions API

Extensions built primarily using HTML, JS and CSS, like legacy Safari extensions

API compatible with other browsers (the WebExtensions standard)

Improved user privacy controls

Extensions are sold through the App Store

Some WebExtensions APIs are missing, so provide feedback if you want something added

Like other extensions, Web Extensions must be packaged inside a native Mac app

Xcode 12 is required to build and run

A command-line tool is provided which wraps an exising web extension (e.g. for Chrome/Firefox) into a new app:

xcrun safari-web-extension-converter […] /path
  • lets you know if any features are not available
  • the largest icon in the manifest is used as the app icon (it’s recommended to include 512×512 and 1024×1024 icons)

To create a new extension from scratch, create a “Safari Extension App” project or add a “Safari Extension” target, and choose Type = Safari Web Extension.

Extension privacy

If your extension needs access to specific sites, the user will be asked for permission to run it on that site for one day or always

Optional permissions: you can include the URL pattern under optional_permissions key and then ask for access using browser.permissions.request(…) at the moment when you require access

The Safari preferences window page of your extension shows information about what kind of access was granted to the extension

It’s best to use the activeTab permission, which grants access to the currently open page when the user interacts with your extension in some way

The hostname of the extension changes every time Safari is launched in order to prevent fingerprinting

→ use browser.runtime.getURL(“/path/to/resource”) to create URLs to assets


Access the background page through the Develop menu

Content scripts are visible in the Sources tab for the page

  • to run JS in the console in the context of a content script, choose the script from the pulldown menu in the corner

Don’t rely on code being executed when the page loads, since the extension may not have permission to run yet at this point

Communicating between components

Content script  ⭢  background page:



Background page  ⭢  extension:


Handled by the SafariWebExtensionHandler.beginRequest(with context:) delegate method

→ requires nativeMessaging permission

Extension  ⭢  background page:

Use completion handler from NSExtensionContext object in SafariWebExtensionHandler.beginRequest() to send back a response

App  ⭢  background page:

SFSafariApplication.dispatchMessage() (check that extension is turned on first)

App  ⭤  extension:

Shared NSUserDefaults from an app group, or NSXPCConnection

Leave a comment


This will only be used to display your Gravatar image.


What's the name of the base class of all AppKit and UIKit classes?