Repository navigation
Safari Extension (Developer Mode)
Safari does not accept an extension the way Chrome or Firefox do. There is no unpacked directory to load and no file to drag in: a Safari web extension ships inside a macOS app, and the app registers the extension with the system when you run it. Until the extension is signed with an Apple Developer ID and notarized, Safari treats it as unsigned and only loads it while developer mode is armed.
This page is the whole route, start to finish, for a build you made yourself or downloaded from a release.
| Feature | Chrome / Firefox | Safari |
|---|---|---|
| Right-click → Download with Hydra | yes | yes |
| Selection pill over highlighted links | yes | yes |
| Download all links | yes | yes |
| Popup, capture toggle, status badge | yes | yes |
| Media / HLS / DASH sniffing | yes | only where Safari exposes webRequest
|
| Automatic download capture | yes | not possible |
Safari has no downloads API — Apple exposes no download events to
extensions at all, so there is nothing to intercept. This is a platform limit
that every download manager on macOS shares; it is not a gap in Hydra. Use the
right-click item or the selection pill instead.
- macOS 11 or later (the wrapper app's deployment target), Safari 16.4 or later.
-
Full Xcode, if you are building it yourself. Command Line Tools alone
cannot build an app extension:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
- Hydra itself installed and running. On Safari the extension can only reach an app that is already up — see Known limits.
Either take hydra-safari-extension-<version>.zip from a
release and unzip it, or build it
from a checkout:
scripts/build-safari-extension.shThe script syncs the web resources from extensions/chrome into
extensions/safari/Resources, generates the wrapper Xcode project, installs
SafariWebExtensionHandler.swift, allows cleartext loopback networking, and
builds a Release app into Xcode's DerivedData. It prints the path at the end.
Move the app somewhere permanent before you open it:
mv ~/Downloads/"Hydra Safari Extension.app" /Applications/Safari registers the extension from wherever the app currently sits, and deleting or moving the app afterwards uninstalls the extension.
Keep exactly one copy on disk. This matters more than it sounds. macOS
registers every copy it finds — including one left in ~/Downloads and one in
/Applications — and Safari then lists the same extension twice and refuses
to enable either, because two apps claim the bundle id
io.github.ja7ad.hydra.safari.Extension. The checkbox simply will not stick.
Check what is registered:
pluginkit -mAv | grep hydra.safariOne line is correct. If you see more, delete the extra copies and unregister them:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -u ~/Downloads/"Hydra Safari Extension.app"Then open the app once — double-click it. It shows a small window saying the extension is currently off. That window is the registration; there is nothing to configure in it.
Safari → Settings → Advanced → tick Show features for web developers (older macOS calls it Show Develop menu in menu bar).
Develop → Allow Unsigned Extensions.
Safari clears this setting every time it quits, and an unsigned extension goes back to disabled with it. So the order has to be:
- Quit Safari completely (⌘Q).
- Launch Safari.
- Develop → Allow Unsigned Extensions.
- Enable the extension (next step) — without quitting Safari in between.
The trap: the wrapper app's own "Quit and Open Safari Extensions Preferences…" button quits Safari on its way to the settings window, which clears the flag you just set. Use the Safari menu bar, not that button.
You repeat step 3 after every Safari restart. That is inherent to an unsigned build, not a fault in this one.
Safari → Settings → Extensions → tick Hydra Download Manager Integration. There should be exactly one entry.
Then click the Hydra button in the toolbar and choose Always Allow on Every Website. Without host access the content script never runs, so the right-click item and the selection pill do nothing.
The extension talks to Hydra over a plain WebSocket on loopback — port 6799,
falling back to 16799. Open the popup: the status dot is green once the
socket is up.
From the app's side, Hydra logs every connection and every refusal:
tail -f ~/.config/hydra/logs/gui.log | grep --line-buffered extbusA healthy connection looks like:
[INFO ] extbus: ws connected (safari-web-extension://XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)
A refused one names the origin it turned away:
[WARN ] extbus: rejected ws handshake (origin: "…")
If neither line ever appears when you open the popup, the extension is not reaching the app at all — nothing arrived at the socket. Start at the extension isn't running below rather than looking at Hydra.
Two copies of the app are registered. See
step 2 — delete the spare, lsregister -u it,
quit Safari, relaunch, re-arm Allow Unsigned Extensions, tick.
Expected for an unsigned build: Allow Unsigned Extensions is cleared on quit. Re-arm it, in a Safari session you have not quit since.
Safari only scans for app extensions at launch, and you probably installed the
app while it was running. Quit Safari with ⌘Q and reopen it. If it is still
missing, confirm macOS knows about the extension with
pluginkit -mAv | grep hydra.safari; an empty result means the app has not
been opened yet, or it is in a location macOS does not scan.
The popup could not reach the app. In order of likelihood:
- Hydra is not running. On Safari the extension cannot start it — see Known limits. Launch Hydra and try again.
-
Nothing is listening. Check:
lsof -nP -iTCP:6799 -sTCP:LISTEN
-
The handshake was refused. Look for
rejected ws handshakein~/.config/hydra/logs/gui.log. Hydra acceptschrome-extension://,moz-extension://andsafari-web-extension://origins; anything else is turned away by design. - Nothing arrived at all — no log line either way. Then the extension's background never opened the socket; carry on below.
Both symptoms together mean the extension's own pages are not running, which is a Safari-side failure rather than anything Hydra can see. Look at the background directly:
Safari → Develop → Web Extension Background Content → Hydra Download Manager Integration.
- If that menu entry is missing, the background service worker never registered — that is the failure, and the console of the app's own window will not tell you more. Re-check steps 2 to 4.
- If it is there, it opens a Web Inspector on the background. Any exception thrown while the background loads shows in its console, and that error is the thing to report.
Please open an issue with that console output if you hit this — it is the one piece of information that cannot be read from outside Safari.
Three of these are properties of an unsigned developer build, not bugs:
-
No automatic download capture, ever. Safari has no
downloadsAPI. Use right-click or the selection pill. - Developer mode must be re-armed after every Safari restart. Signing the app with an Apple Developer ID and notarizing it is what removes this step.
-
Hydra cannot be launched by the extension. On Chrome and Firefox, the
native-messaging host starts Hydra when it is not running. The Safari
equivalent,
SafariWebExtensionHandler, is blocked by the app sandbox from reading~/.config/hydra/ipc.jsonand from launching the app, so on Safari the extension only works with Hydra already running. Everything the WebSocket carries — downloads, status, the popup — is unaffected. -
Media sniffing is partial, limited to what Safari exposes through
webRequest.
Quit Safari, then:
rm -rf /Applications/"Hydra Safari Extension.app"The extension disappears from Safari with the app. To clear the registration immediately rather than at the next system scan:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -u /Applications/"Hydra Safari Extension.app"Edit extensions/chrome/*, never extensions/safari/Resources/* — the latter
is regenerated from the Chrome sources by
scripts/sync-extension-resources.sh safari on every build, and CI fails if
the committed tree does not match what that script produces. The Safari
manifest (extensions/safari/manifest.json) and
SafariWebExtensionHandler.swift are Safari's own and are edited in place.
Hydra Download Manager: Website · GitHub · Releases · Changelog · Issues
Something on this page wrong or out of date? Open an issue and link to the page. · Licensed under GPL-3.0-or-later, with MIT/Apache-2.0 for the library crates (details).
Get started
Guides
- macOS Permissions
- Safari Extension
- FFmpeg Integration
- Youtube Downloader
- Writing, building, and signing Hydra plugins
Browser extensions
Developers
Project