Safari Browser Scripts PRO

Safari Browser Scripts let Scripting run userscripts in Safari through the Safari Web Extension. The runtime supports Greasemonkey-style GM.* APIs and selected Scripting APIs.


Where Scripts Run

There are two sources:

  • browser.tsx in a Scripting script project. It is built to browser.js.
  • Installed .user.js / .js files from Safari's extension popup.

Scripts installed from Safari's extension popup are stored under:

scripting-safari-extension/userscripts/

Downloads and GM storage live in the same root:

scripting-safari-extension/downloads/
scripting-safari-extension/storages/

This root follows the Safari Browser Data storage location configured in Settings, including WebDAV when enabled.


Script Format

Create or install a .user.js / .js file with a userscript metadata block:

// ==UserScript==
// @name GitHub Demo
// @match https://github.com/*
// @grant GM.log
// @grant Scripting.FileManager
// ==/UserScript==

GM.log("loaded", location.href)

Common metadata keys:

// @name
// @namespace
// @version
// @description
// @author
// @match
// @include
// @exclude
// @exclude-match
// @connect
// @grant
// @require
// @resource
// @run-at document-start | document-end | document-idle
// @inject-into content | page
// @weight 1..999
// @noframes
// @homepageURL
// @supportURL
// @updateURL
// @downloadURL
// @license

@weight controls execution order when multiple scripts match the page. Larger weights run first. If no @run-at is provided, scripts run at document-end.

Use @inject-into page only for page-context scripts that do not need privileged APIs. GM.*, Scripting.*, and extension messaging are only available in content-context scripts.


Permissions

Privileged APIs must be declared with @grant.

// @grant GM.getValue
// @grant GM.setValue
// @grant GM.xmlHttpRequest
// @grant GM.cookie
// @grant Scripting.FileManager

@grant none disables GM APIs, but GM_info and GM.info remain available for compatibility.

Cross-origin network, download, resource, and cookie access must be allowed with @connect.

// @connect api.github.com
// @connect https://api.github.com/*
// @connect *

When a required grant or connect rule is missing, the runtime throws an error with a stable code such as permissionDenied or connectDenied.


GM_info

GM_info and GM.info expose parsed metadata and runtime details:

GM_info.script.name
GM_info.script.version
GM_info.script.matches
GM_info.script.connects
GM_info.script.runAt
GM_info.script.injectInto
GM_info.script.weight
GM_info.scriptHandler
GM_info.version

Supported GM APIs

Storage

await GM.getValue(key, defaultValue)
await GM.setValue(key, value)
await GM.deleteValue(key)
await GM.listValues()

const id = GM.addValueChangeListener(key, (key, oldValue, newValue, remote) => {})
GM.removeValueChangeListener(id)

GM storage is stored as JSON under scripting-safari-extension/storages/.

DOM and Menu

GM.log(...items)
GM.addStyle(css)
GM.addElement("div", { textContent: "Hello" })
GM.addElement(document.body, "button", { textContent: "Run" })

const id = GM.registerMenuCommand("Run", () => {})
GM.unregisterMenuCommand(id)
GM.removeMenuCommand(id)

Menu commands are shown in Safari's extension popup for the current page.

Tabs, Clipboard, Notification

const tab = await GM.openInTab("https://github.com/trending", { active: false })
tab.close()
await GM.closeTab()

await GM.setClipboard("Hello")
await GM.notification({ title: "Scripting", text: "Done" })

GM.closeTab() closes the current tab when supported by Safari. GM.openInTab() returns a tab control with close().

Resources

const text = await GM.getResourceText("name")
const url = await GM.getResourceURL("name")

Declare resources in the metadata block:

// @resource name https://example.com/file.txt

Download

await GM.download({
  url: "https://example.com/file.txt",
  name: "file.txt",
  onload(response) {
    GM.log(response.path)
  }
})

Downloaded files are stored under scripting-safari-extension/downloads/.

XHR

await GM.xmlHttpRequest({
  method: "GET",
  url: "https://api.github.com/zen",
  responseType: "text",
  overrideMimeType: "text/plain",
  onloadstart(event) {},
  onprogress(event) {},
  onreadystatechange(response) {},
  onload(response) {},
  onloadend(response) {}
})

The runtime supports responseType values text, json, arraybuffer, blob, and document, plus user, password, headers, data, timeout, binary, overrideMimeType, upload callbacks, finalUrl, and responseURL.

Cookies

await GM.cookie.set({
  url: location.href,
  name: "scripting_test",
  value: "1",
  path: "/",
  secure: true
})

const cookies = await GM.cookie.list({ url: location.href, name: "scripting_test" })
await GM.cookie.delete({ url: location.href, name: "scripting_test" })

Callback style is also supported:

GM.cookie.list({ url: location.href }, cookies => {
  GM.log(cookies)
})

Scripting.FileManager

Use @grant Scripting.FileManager or @grant Scripting.* to access the API.

Properties

Scripting.FileManager.documentsDirectory: string
Scripting.FileManager.iCloudDocumentsDirectory: string | null
Scripting.FileManager.appGroupDocumentsDirectory: string | null
Scripting.FileManager.safariBrowserDirectory: string
Scripting.FileManager.safariBrowserStorageDirectory: string
Scripting.FileManager.safariBrowserDownloadsDirectory: string
Scripting.FileManager.safariBrowserUserscriptsDirectory: string
Scripting.FileManager.isiCloudEnabled: boolean

In regular Scripting app scripts, the same Safari data directories are available as FileManager.safariBrowserDirectory, FileManager.safariBrowserStorageDirectory, FileManager.safariBrowserDownloadsDirectory, and FileManager.safariBrowserUserscriptsDirectory.

Methods

await Scripting.FileManager.readAsString(path)
await Scripting.FileManager.writeAsString(path, contents)
await Scripting.FileManager.createDirectory(path, true)
await Scripting.FileManager.readDirectory(path)
await Scripting.FileManager.exists(path)
await Scripting.FileManager.remove(path)

All file operations are limited to directories exposed by Scripting.FileManager.


Installed Scripts

Safari's extension popup can install userscripts from the current page or from a URL. Installed scripts can be enabled, disabled, updated, or deleted in the popup and in Tools > Development > Safari Browser Scripts.

Use the Tools page to inspect:

  • Installed userscripts.
  • GM storage JSON files.
  • Downloads created by GM.download.

Example

// ==UserScript==
// @name Save Page URL
// @match https://github.com/*
// @grant GM.log
// @grant Scripting.FileManager
// ==/UserScript==

const fm = Scripting.FileManager
const dir = `${fm.appGroupDocumentsDirectory ?? fm.documentsDirectory}/Safari Notes`
const file = `${dir}/last-url.txt`

await fm.createDirectory(dir)
await fm.writeAsString(file, location.href)

GM.log(await fm.readAsString(file))