Skip to main content
Safari extensions enable you to extend Safari functionality with web extensions and content blockers.

Extension types

Safari Extension

Full web extensions with content scripts and background pages

Content Blocker

Declarative content blocking rules

Safari Extension

Modern Safari extensions use the WebExtensions API compatible with Chrome and Firefox extensions.

Extension point

PropertyValue
Typesafari
Extension Pointcom.apple.Safari.web-extension
FrameworksNone

Creating a Safari extension

npx create-target safari
This generates:
  • manifest.json - Extension manifest
  • background.js - Background script
  • content.js - Content script
  • assets/ - Extension resources

manifest.json

{
  "manifest_version": 3,
  "name": "My Safari Extension",
  "version": "1.0",
  "description": "Extension description",
  "icons": {
    "48": "images/icon-48.png",
    "96": "images/icon-96.png",
    "128": "images/icon-128.png"
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["*://*/*"],
      "js": ["content.js"]
    }
  ],
  "permissions": [
    "activeTab",
    "storage"
  ]
}

Background script

// targets/safari/assets/background.js
console.log("Background script loaded");

browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === "getData") {
    // Handle message from content script
    sendResponse({ data: "response" });
  }
});

browser.action.onClicked.addListener((tab) => {
  // Handle toolbar icon click
  browser.tabs.sendMessage(tab.id, { action: "iconClicked" });
});

Content script

// targets/safari/assets/content.js
console.log("Content script loaded on", window.location.href);

// Modify page content
document.body.style.backgroundColor = "#f0f0f0";

// Listen for messages from background
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === "iconClicked") {
    alert("Extension clicked!");
  }
});

// Send message to background
browser.runtime.sendMessage({ action: "getData" }, (response) => {
  console.log("Response:", response);
});

Native messaging

Communicate between the web extension and native iOS app:
// In content script or background script
browser.runtime.sendNativeMessage("application.id", { command: "getData" }, (response) => {
  console.log("Native response:", response);
});
Handle in your iOS app (requires native module implementation).

File structure

The assets/ directory is automatically linked as resources:
targets/safari/
├── expo-target.config.js
├── Info.plist
└── assets/
    ├── manifest.json
    ├── background.js
    ├── content.js
    └── images/
        ├── icon-48.png
        ├── icon-96.png
        └── icon-128.png
Files in the top-level assets/ directory are linked as resources. This is specific to Safari extensions.

Content Blocker

Declarative content blocking without JavaScript execution.

Extension point

PropertyValue
Typecontent-blocker
Extension Pointcom.apple.Safari.content-blocker
FrameworksNone

Creating a content blocker

npx create-target content-blocker

Blocking rules

Define blocking rules in blockerList.json:
[
  {
    "action": {
      "type": "block"
    },
    "trigger": {
      "url-filter": ".*",
      "resource-type": ["image"],
      "if-domain": ["example.com"]
    }
  },
  {
    "action": {
      "type": "block-cookies"
    },
    "trigger": {
      "url-filter": ".*",
      "load-type": ["third-party"]
    }
  },
  {
    "action": {
      "type": "css-display-none",
      "selector": ".ad-banner, #popup"
    },
    "trigger": {
      "url-filter": ".*"
    }
  }
]

Action types

ActionDescription
blockBlock the request
block-cookiesBlock cookies
css-display-noneHide elements with CSS
ignore-previous-rulesOverride previous rules
make-httpsUpgrade to HTTPS

Trigger options

PropertyDescription
url-filterRegular expression for URLs
url-filter-is-case-sensitiveCase sensitivity
resource-typeTypes: document, image, script, stylesheet, etc.
if-domainApply only on these domains
unless-domainApply except on these domains
load-typefirst-party or third-party

Loading rules

Content blocker rules are loaded automatically from the extension bundle. Ensure blockerList.json is included in the target.

Rule limits

Safari supports up to 50,000 content blocking rules per extension. Rules are compiled for performance.

Permissions

Common Safari extension permissions:
{
  "permissions": [
    "activeTab",        // Access current tab
    "storage",          // Use browser.storage API
    "tabs",             // Access tab information
    "webNavigation",    // Monitor navigation events
    "webRequest",       // Observe web requests
    "declarativeNetRequest" // Modify network requests
  ]
}

Testing Safari extensions

1

Build the extension

Build your Safari extension target in Xcode.
2

Enable Safari Developer settings

In Safari, go to Safari > Settings > Advanced and enable “Show Develop menu in menu bar”.
3

Allow unsigned extensions

In Develop > Allow Unsigned Extensions, enable unsigned extensions.
4

Enable your extension

In Safari > Settings > Extensions, enable your extension.
5

Test functionality

Navigate to websites and verify your extension works.

Best practices

  • Only request permissions you actually need
  • Use activeTab instead of <all_urls> when possible
  • Explain permission usage to users
  • Keep content scripts lightweight
  • Use event pages instead of persistent background pages
  • Lazy load resources when needed
  • Minimize DOM manipulations
  • Validate all external data
  • Provide fallbacks for failed operations
  • Log errors for debugging
  • Test on multiple websites

Troubleshooting

  • Verify the extension is built and installed
  • Enable “Allow Unsigned Extensions” in Safari Developer menu
  • Check Safari Settings > Extensions
  • Rebuild the extension target
  • Check matches pattern in manifest.json
  • Verify website matches the pattern
  • Look for JavaScript errors in Safari Web Inspector
  • Ensure manifest_version is correct
  • Verify files are in the assets/ directory
  • Check file paths in manifest.json
  • Rebuild after adding new files
  • Check Xcode build phase includes resources

Learn more

Safari extensions guide

Detailed Safari extension guide

Target config

Configure Safari extensions

Complete target list

All extension types

Apple documentation

Official Safari Web Extensions documentation

Build docs developers (and LLMs) love