After switching from Chrome to Safari, I started missing the excellent TabCopy extension that allowed me to easily copy the current tab’s URL to the clipboard, formatted as a clickable link displaying the page title. I often use this to reference web pages in emails, to-do lists, documentation, Slack, etc. Instead of dealing with potentially long URLs, I prefer pasting a human-readable link with the URL hidden behind it.

This post outlines how I managed to implement this specific requirement for Safari. I’m not focussing on TabCopy’s other features as they weren’t relevant for my workflow.

TL;DR: Here’s the finished script which retrieves the URL and title of the active Safari tab, constructs an HTML link, and copies it to the macOS clipboard. It also includes a plain-text representation in Markdown syntax.

You can use an automation tool such as Alfred to bind this script to a hot key local to Safari and show a notification when done:

Alfred Workflow to bind the script to a hot key and show a notification.

The Script Explained

Fetching the Title

Using AppleScript, we fetch the URL and the title displayed in the frontmost Safari window.

Massaging the Title

Starting with version 17, Safari introduced support for profiles. The profile name will be prefixed to the document title that is retrieved using AppleScript. We detect it based on the long dash (—), which Safari uses to separate the profile name from the web page title, and strip that string using sed.

You can extend this function to do further processing if needed.

Preparing The HTML String

Adding the <meta charset='utf-8'> ensures that the string is treated as UTF-8 when adding it to the clipboard. Without this, special characters in the page title will break.

I’m also adding a style tag to use the default sans-serif font when pasting the link, which I usually prefer. Your mileage may vary.

hexdump -ve '1/1 "%.2x"' will convert a string into a stream of two-digit hexadecimal numbers, without skipping any repeated lines. This seems to be required to get the string over into the AppleScript world, which I learned in this StackOverflow post.

Preparing a Plain Text Version

Some apps, such as Slack, are picky and won’t paste the HTML string when no plain text version is present (details in the next section below). I’m therefore adding plain text representation in Markdown syntax which includes both the page title and the link.

Setting the Clipboard

Finally, I’m writing both strings to the clipboard, again using AppleScript.

Getting It To Work In Slack

At first glance, all seemed to work, as the link would paste correctly in native macOS apps, such as Mail, TextEdit, or OmniOutliner. However, at some point I noticed that it wasn’t working in Slack. Performing the paste action would do nothing, not even giving me the title without the link. Maybe this is an issue with Electron apps in general, which I haven’t verified.

I was puzzled and decided to take a deep dive into the inner workings of the macOS clipboard. I knew that copying a link from a webpage in Chrome or Safari and pasting it into Slack worked correctly, so I wanted to figure out how the raw data would look differently in the clipboard.

Apple provides a Clipboard Viewer utility as part of the Additional Tools for Xcode, which was exactly what I needed to get to the bottom of this. Despite having an icon that you’d never expect from an Apple app shipped today, it gets its job done, no frills.

A collection of three application icons. From left to right: The first icon shows a chessboard with a knight piece, representing Apple’s chess application. The second icon features a golden lamp with smoke trailing from its spout, signifying Apple’s Clipboard Viewer application. The third icon displays a simple clock face with hands indicating ten past ten, symbolizing Apple’s clock application.

Manually copying a link from a web page in Safari would write the following data in the clipboard:

Clipboard Viewer with public.html data type selected and many other data types available.

More than I expected. There are multiple HTML and RTF representations, including a data type, and various plain text representations in both UTF-8 and UTF-16.

In contrast, my script would only write two data types, both being the HTML representation:

Clipboard Viewer showing public.html and Apple HTML pasteboard type, containing a link to the Nyan Cat Wikipedia page.

After some trial and error, I found out that the plain text version is key. Slack expects a plain text to be included, even though it won’t be using it at all. As long as a plain text is included, it will happily paste the HTML version.

So I updated my script to include the plain text version:

plain=$(echo "[${title}](${url})" | sed 's/"/\\"/g')
osascript <<EOF
set the clipboard to {«class HTML»:«data HTML${html}», string:"${plain}"}

For some reason, this adds a bunch of other plain text representations. But it does the trick! The link can now be pasted into Slack.

To make this more useful, I decided to use the Markdown syntax here.