API Docs

Module for interacting with the macOS pasteboard (clipboard)

The macOS pasteboard is "rich" — a single clipboard operation can carry multiple representations of the same content for different applications to consume. For example, text copied from a web browser may carry plain text, HTML, and RTF representations simultaneously.

Basic Usage

// Read and write plain text
const text = hs.pasteboard.readString()
hs.pasteboard.writeString("Hello from Hammerspoon!")

// Check what types are currently on the pasteboard
const available = hs.pasteboard.types()

// Write multiple representations at once
hs.pasteboard.writeObjects({
    "public.utf8-plain-text": "Hello",
    "public.html": "<b>Hello</b>"
})

// Watch for pasteboard changes
const handler = (changeCount) => {
    console.log("Pasteboard changed, count:", changeCount)
    console.log("New text:", hs.pasteboard.readString())
}
hs.pasteboard.addWatcher(handler)
// Later: hs.pasteboard.removeWatcher(handler)

Pasteboard Conventions (nspasteboard.org)

macOS has no built-in notification API for transient or confidential clipboard content, so a community convention has emerged (see nspasteboard.org) around four org.nspasteboard.* UTI marker types. These markers carry no payload — their mere presence on the pasteboard signals intent to other applications.

Standard marker UTIs

UTI Meaning
org.nspasteboard.TransientType Content is temporary; it will be removed or overwritten shortly. Clipboard historians should not record this change.
org.nspasteboard.ConcealedType Content is sensitive (e.g. a password). Historians should obfuscate it if displayed and ideally encrypt it if stored.
org.nspasteboard.AutoGeneratedType Content was placed by an application without any user Copy action. Historians should generally skip recording it.
org.nspasteboard.source The bundle identifier of the application that placed the content. Use an empty string when the source is unknown.

Legacy proprietary markers

Several apps defined their own markers before the org.nspasteboard.* standard existed. Clipboard historians should also honour these for compatibility:

UTI Application
de.petermaurer.TransientPasteboardType TextExpander, Butler
com.typeit4me.clipping TypeIt4Me
Pasteboard generator type Typinator
com.agilebits.onepassword 1Password (confidential)
com.apple.is-remote-clipboard macOS (remote content)

For scripts that write to the pasteboard

If your script temporarily commandeers the pasteboard (e.g. to trigger a paste), add org.nspasteboard.TransientType so clipboard historians skip the entry:

hs.pasteboard.writeObjects({
    "public.utf8-plain-text": "temporary value",
    "org.nspasteboard.TransientType": ""
})

For sensitive content such as a generated password, use org.nspasteboard.ConcealedType:

hs.pasteboard.writeObjects({
    "public.utf8-plain-text": "s3cr3t!",
    "org.nspasteboard.ConcealedType": ""
})

For scripts that monitor the pasteboard

If you are building a clipboard history tool with addWatcher, skip or obfuscate entries that carry any of the marker UTIs listed above:

const SKIP_TYPES = [
    "org.nspasteboard.TransientType",
    "org.nspasteboard.AutoGeneratedType",
    "de.petermaurer.TransientPasteboardType",
    "com.typeit4me.clipping",
    "Pasteboard generator type",
]
const CONCEAL_TYPES = [
    "org.nspasteboard.ConcealedType",
    "com.agilebits.onepassword",
]

hs.pasteboard.addWatcher((changeCount) => {
    const types = hs.pasteboard.types()
    if (SKIP_TYPES.some(t => types.includes(t))) return        // ignore transient
    const conceal = CONCEAL_TYPES.some(t => types.includes(t)) // handle sensitively
    // … record or display the pasteboard contents …
})

Properties

hs.pasteboard.changeCount

number
The pasteboard change count. Increments each time any application writes to the pasteboard. Comparing a saved value to the current value is the standard way to detect external changes.

hs.pasteboard.watcherInterval

number
The polling interval for the pasteboard watcher, in seconds. Defaults to 0.5. Changes take effect the next time a watcher is started (i.e. after removing and re-adding).

Methods

hs.pasteboard.readString() -> string

Read plain text from the pasteboard
hs.pasteboard.readString() -> string
string
The plain text string, or null if not available
const text = hs.pasteboard.readString()

hs.pasteboard.readHTML() -> string

Read HTML from the pasteboard
hs.pasteboard.readHTML() -> string
string
The HTML string, or null if not available
const html = hs.pasteboard.readHTML()

hs.pasteboard.readRTF() -> string

Read RTF from the pasteboard
hs.pasteboard.readRTF() -> string
string
The RTF string, or null if not available
const rtf = hs.pasteboard.readRTF()

hs.pasteboard.readURL() -> string

Read a URL from the pasteboard
hs.pasteboard.readURL() -> string
string
The URL as a string, or null if not available
const url = hs.pasteboard.readURL()

hs.pasteboard.readImage() -> HSImage

Read an image from the pasteboard
hs.pasteboard.readImage() -> HSImage
HSImage
An HSImage, or null if not available
const img = hs.pasteboard.readImage()

hs.pasteboard.readData(uti) -> string

Read raw data for a specific UTI type, returned as a base64-encoded string. Use this for types not covered by the convenience read methods.
hs.pasteboard.readData(uti) -> string
Name Type Description
uti string A UTI type string (e.g. "com.adobe.pdf")
string
A base64-encoded string, or null if the type is not available
const b64 = hs.pasteboard.readData("com.adobe.pdf")

hs.pasteboard.writeString(str) -> boolean

Write plain text to the pasteboard, replacing all current contents
hs.pasteboard.writeString(str) -> boolean
Name Type Description
str string The text string to write
boolean
true if the write succeeded
hs.pasteboard.writeString("Hello, world!")

hs.pasteboard.writeHTML(html) -> boolean

Write HTML to the pasteboard, replacing all current contents
hs.pasteboard.writeHTML(html) -> boolean
Name Type Description
html string The HTML string to write
boolean
true if the write succeeded
hs.pasteboard.writeHTML("<b>Hello</b>")

hs.pasteboard.writeRTF(rtf) -> boolean

Write RTF to the pasteboard, replacing all current contents
hs.pasteboard.writeRTF(rtf) -> boolean
Name Type Description
rtf string The RTF string to write
boolean
true if the write succeeded
hs.pasteboard.writeRTF(rtfString)

hs.pasteboard.writeURL(url) -> boolean

Write a URL to the pasteboard, replacing all current contents
hs.pasteboard.writeURL(url) -> boolean
Name Type Description
url string The URL string to write
boolean
true if the write succeeded
hs.pasteboard.writeURL("https://www.example.com")

hs.pasteboard.writeImage(image) -> boolean

Write an image to the pasteboard, replacing all current contents
hs.pasteboard.writeImage(image) -> boolean
Name Type Description
image HSImage An HSImage to write
boolean
true if the write succeeded
const img = hs.pasteboard.readImage()
hs.pasteboard.writeImage(img)

hs.pasteboard.writeData(base64, uti) -> boolean

Write raw base64-encoded data for a specific UTI type, replacing all current contents. Use this for types not covered by the convenience write methods.
hs.pasteboard.writeData(base64, uti) -> boolean
Name Type Description
base64 string The data encoded as a base64 string
uti string A UTI type string (e.g. "com.adobe.pdf")
boolean
true if the write succeeded
hs.pasteboard.writeData(b64Encoded, "com.adobe.pdf")

hs.pasteboard.writeObjects(representations) -> boolean

Write multiple type representations to the pasteboard atomically, replacing all current contents. Keys must be UTI type strings; values must be strings. This is how you provide both a plain-text fallback and a richer representation (such as HTML) in a single clipboard operation.
hs.pasteboard.writeObjects(representations) -> boolean
Name Type Description
representations JSValue A JavaScript object whose keys are UTI strings and values are strings
boolean
true if the write succeeded
hs.pasteboard.writeObjects({
    "public.utf8-plain-text": "Hello",
    "public.html":            "<b>Hello</b>"
})

hs.pasteboard.types() -> string[]

Get all UTI type strings currently on the pasteboard, across all items
hs.pasteboard.types() -> string[]
string[]
An array of UTI strings (e.g. ["public.utf8-plain-text", "public.html"])
const types = hs.pasteboard.types()

hs.pasteboard.hasType(uti) -> boolean

Check whether a specific UTI type is currently available on the pasteboard
hs.pasteboard.hasType(uti) -> boolean
Name Type Description
uti string A UTI type string to check for
boolean
true if the type is available
if (hs.pasteboard.hasType("public.html")) console.log("HTML available")

hs.pasteboard.clear() -> None

Clear all contents from the pasteboard
hs.pasteboard.clear() -> None
None
hs.pasteboard.clear()

hs.pasteboard.addWatcher(listener) -> None

Add a watcher that is called whenever the pasteboard contents change. Multiple watchers may be registered; they are each called independently. Because macOS provides no pasteboard change notification API, this is implemented by polling `changeCount` at the interval specified by `watcherInterval`.
hs.pasteboard.addWatcher(listener) -> None
Name Type Description
listener JSValue A function called with one argument: the new `changeCount` integer
None
hs.pasteboard.addWatcher((count) => {
    console.log("Pasteboard changed:", count)
})

hs.pasteboard.removeWatcher(listener) -> None

Remove a previously registered pasteboard watcher
hs.pasteboard.removeWatcher(listener) -> None
Name Type Description
listener JSValue The function previously passed to `addWatcher`
None
hs.pasteboard.removeWatcher(myHandler)