Actions ▸ Reference
Standard library reference
Every action that ships inside the app, section by section, with its own help. This page is generated from the library the app actually carries, so it lists what your copy has.
These are the actions the app builds from code at every launch. They are read-only: to change one, take a copy in the Actions pane, which lands above the library as one of your own and takes over that keyword.
Each is named by its keyword, which is what a pipeline writes, what a trigger names, and what a shortcut binds:
$1 | trim | upper
$2 | split , | first
$1 | addtz UTC | totz America/New_York | strftime "%F %T"
A few of these read the entry rather than the text handed to them — undo and ocr — so they have to be the first filter in a pipeline. Two of them, now and utcnow, read nothing at all, which is what makes them somewhere to start: | now | strftime %H:%M works on an empty history. forget, unfmt and html change no characters, so they can sit anywhere in a chain. And md-rich is the one filter here that makes formatting — it reads the text as Markdown and puts what that renders to on the clipboard, so it pastes as headings and bullets rather than as the marks that describe them.
++ trim collapse-spaces squeeze-blanks strip-html tidy sort-lines unique-lines reverse-lines number-lines join-lines count
upside-down
General
undoReads the entry
Go back to an earlier value
Reads what the entry the pipeline started from used to hold, instead of what it holds now. undo 1 is the value before the current one, undo 2 the one before that, and undo -1 the first value it ever held. With no number it goes back once.
This is the way back from a trigger that fired when it should not have: the text as it was copied is still in the entry, behind what the trigger made of it.
$1 | undo
A variable is put back by sending the result to itself, which is recorded like any other change — so an undo is itself undoable:
$signature | undo 1 > $signature
Because it reads the entry rather than the text, undo has to be the first filter in a pipeline; anything before it would have its work thrown away. Filters after it work on the older value as usual.
ocrReads the image
Read the text in an image
Hands on the words in a picture. Every image copied to the clipboard is read on its way into the history, and this is how what it said is got at:
$1 | ocr
$2 | ocr | trim > $address
That makes it the way to get a serial number, an error message or an address out of a screenshot without typing it out. What comes back is text like any other, so the rest of the library follows it:
$1 | ocr | join-lines ", "
The reading is also what makes a picture findable: typing an invoice number into the overlay finds the screenshot of the invoice, without ocr being run at all.
Because it reads the entry's picture rather than the text it was handed, ocr has to be the first filter in a pipeline — nothing produces an image for it to read, so anything before it would only have made text for it to ignore. For the same reason it is a filter you type rather than one the overlay offers: the list of actions only appears while the newest item is text, which is exactly when this one has nothing to read.
A picture with no words in it says so rather than handing back nothing, as does one copied before the app could read images, and one whose reading has not come back yet — it happens in the background, so a very large screenshot can take a moment after it is copied.
forgetHistory
Keep it out of the history
Passes the text through exactly as it was given, and asks for it not to be kept: the entry it ran on is dropped from the clipboard history, and nothing the run produced is put back there.
The text stays on the clipboard. Forgetting is about what PasteDaemon remembers, not about what you copied — you can still paste it, once, wherever you were going to.
Name it from a trigger to keep a whole shape of thing out of the history. A trigger that matches what your password manager copies, or an API token, or a one-time code, will drop every such copy the moment it is made:
Matches regex ^[A-Za-z0-9_-]{32,}$
Actions forget
It can also be run on something already copied — from the menu bar, from the overlay, or by hand — which is the way to take one thing back out again:
$1 | forget
A variable is never deleted by it: variables are written down on purpose and kept until you delete them, so $token | forget keeps the run out of the history and leaves ${token} alone.
It changes no text, so it can sit anywhere in a chain — $1 | trim | forget puts the trimmed text on the clipboard and keeps both it and the untrimmed original out of the history.
unfmtplainText
Paste without formatting
Puts the text on the clipboard with none of the shapes it was copied in — no bold, no links, no colour, no fonts. The characters are untouched; what changes is what the app you paste into is offered.
Run it on the newest copy from the overlay or the menu bar, or name a row:
$2 | unfmt
Run by hand it is a decision about this paste: the entry keeps how it was copied, so pressing ↩ on that row later pastes it dressed as it always was.
Named from a trigger it is a decision about the copy, because a trigger fires when something is copied and there is no paste yet to decide about. The copy is stored plain and stays plain however often it is used:
Matches regex .
Actions unfmt
Text that was never formatted is already plain, so running it there costs nothing and does nothing.
htmlhtml
Paste as HTML
Puts the HTML of the copy on the clipboard in place of the richer shapes offered beside it, with the plain text as always. A copy from a web page carries the page's own markup and that is what goes back; a copy from a word processor carries rich text instead, which is converted.
$1 | html
It is the companion to unfmt: one narrows a copy to the text, the other to the markup. Both leave the characters exactly as they were, and both can be run by hand — for this paste — or named from a trigger, for the copy itself.
Because formatting describes one exact run of characters, it only answers for text that is still the text that was copied. $1 | upper | html has no HTML for what it produced, and says so rather than pasting the old copy's markup over the new text. A copy that was never formatted has no HTML either.
evaleval
Expand the references in the text
Reads the text it is handed as if it had been written out in quotes: every $name, $1 and $( … ) in it stands for what it names, exactly as it would inside a "…" in the bar.
That is what turns text that was stored into text that says something. A variable holding a template is a sentence with a $ in it until this runs on it:
$greeting Dear $name,
$greeting | eval Dear Marion,
Everything a quoted piece of text can hold works here, including a command:
$line | eval Sent $(| now | strftime %H:%M)
A name that stands for nothing is left as it was written — the same answer an argument gets, so a $ that was only ever a dollar sign survives. $$ is how to write one that must not expand.
Over a list it is map that runs it, an item at a time:
@letters | map eval
It may not reach itself: text that evaluates to text that evaluates would never finish, so a second eval inside one is refused rather than run.
Text
++Join
Join text on the end
Joins text onto the end of what it is given, with nothing between — a space goes in where a space is wanted.
$1 ++ "!"
$1 | ++ "!" the same thing
$1 ++ " — " ++ $2 two clipboard items, with a dash between
$1 | trim ++ $(| strftime %F)
The words after it are the text to join, so quotes are needed only to keep the spaces at either end of it. A $( … ) runs a pipeline of its own and joins on what it produced.
Like the sums, it needs a space either side: $1 ++5 is refused rather than read as a join, so that an argument beginning with a + stays the argument it is.
trimSubstitution
Trim whitespace
Removes whitespace from the start and end of the text, leaving what is in the middle alone.
Handy at the front of a pipeline, since text copied out of a web page or a terminal usually arrives with something invisible attached:
$1 | trim | json
collapse-spacesSubstitution
Collapse runs of spaces and tabs
Replaces every run of spaces and tabs with a single space. Line breaks are left alone, so the shape of a paragraph survives while the ragged spacing of copied-out columns does not.
Pair it with trim to tidy a line completely.
squeeze-blanksSubstitution
Collapse repeated blank lines
Turns any run of two or more blank lines into a single blank line. Paragraph breaks stay; the gaps left behind by deleting things do not.
strip-htmlSubstitution
Remove HTML tags
Deletes anything between < and >, leaving the text between the tags.
This is a regex, not a parser: it is meant for pulling the words out of a snippet of markup, and it will happily eat a < that was never a tag. Do not use it to sanitise anything.
tidyPipeline
Trim, collapse spaces and blank lines
Everything that makes copied text untidy, in one go: the whitespace at either end, runs of spaces and tabs in the middle, and repeated blank lines.
This one is a pipeline action — it is nothing but three other actions in a row:
| trim | collapse-spaces | squeeze-blanks
The leading | is the text it was handed, so it can be run on the clipboard, named by a trigger, or used as a filter like any other. Everything in it is a substitution, so it is pure and previews live as you type.
sort-linesScript
Sort lines
Sorts the lines in ascending order, the way sort does — byte order, so capitals come before lowercase.
Follow it with unique-lines to get a sorted list with no repeats:
$1 | sort-lines | unique-lines
unique-linesScript
Drop repeated lines
Keeps the first appearance of each line and drops the rest. Unlike uniq, the input does not have to be sorted, and the order of what survives is the order it arrived in.
reverse-linesScript
Reverse the order of the lines
Last line first. Useful for reading a log that was written oldest-first.
number-linesScript
Number the lines
Puts a right-aligned line number and two spaces in front of every line, including blank ones.
join-linesScript
Join the lines into one
Runs the lines together into a single line.
The separator is the filter's argument, and defaults to a single space. Quote it to keep a trailing space or to use something with a space in it:
$1 | join-lines ", "
$1 | join-lines ,
countScript
Count lines, words and characters
Replaces the text with a count of it — 12 lines, 340 words, 2,110 characters — which makes it a filter to end a pipeline with rather than one to build on.
Characters are counted as characters, not bytes, so accented letters and emoji count once each.
Markdown
md-quoteScript
Quote the text
Puts > in front of every line, which is how Markdown says a blockquote.
A blank line inside gets a bare > rather than nothing, because a blank line with no > on it ends the quote — two paragraphs quoted that way come out as one quote and one paragraph beside it. That is the whole reason this is a filter rather than something to type.
Text that is already quoted gains a second >, which is what quoting a quote looks like.
md-unquoteSubstitution
Remove one level of quoting
Takes the > off the front of every line — the way back out of a reply, a forwarded mail or a quoted message.
One level at a time, so text quoted twice needs it twice; that is what makes it usable on a reply inside a reply without flattening the lot. The space after the > goes with it where there is one, and up to three spaces in front are allowed, which is what Markdown itself allows.
$1 | md-unquote | md-unquote
md-bulletScript
Make the lines a bulleted list
Puts - in front of every line, turning a copied column or a run of sentences into a list.
Blank lines are left blank rather than given a bullet of their own, so a gap between two groups stays a gap instead of becoming an empty item.
md-numberedScript
Make the lines a numbered list
The same as md-bullet with 1., 2., 3. instead of bullets.
The count skips blank lines rather than spending a number on them, so a gap in the middle of a list does not leave a hole in the numbering.
md-codeScript
Wrap the text in a fenced code block
Puts the text inside a fenced code block. The filter's argument is the language, and is left off where there is none:
$1 | md-code swift
The fence is made longer than the longest run of backticks in the text, so pasting a snippet that already contains a fence produces a block that holds it rather than one that ends in the middle of it. Three backticks where the text has none, five where it has four. This is the part that is worth a filter: it is exactly the case where doing it by hand goes wrong, and it goes wrong quietly.
md-tableScript
Tab-separated text to a table
Turns rows of tab-separated values into a Markdown table, with the first row as the header and the | --- | row that has to follow it.
Tabs are what a copy out of a spreadsheet is separated by, which is what this is for: select a range in Numbers or Excel, copy, and paste a table into a pull request. Another separator can be named as the argument:
$1 | md-table ,
A | inside a cell is escaped, since an unescaped one would split the cell in two, and a row with fewer cells than the header is padded with empty ones — a table whose rows are different lengths does not render at all.
md-escapeSubstitution
Escape the Markdown in the text
Puts a \ in front of the characters that would otherwise be read as markup, so text survives being pasted somewhere that renders Markdown. my_file_name stays my_file_name instead of turning file into italics.
The characters escaped are \, ` `, *, _, [, ], <, > and | — the ones that mean something **anywhere in a line**. #, - and 1.` are left alone: they only mean anything at the start of a line, and escaping every hyphen in a document to catch the two that begin one is a cure worse than the problem.
md-htmlMarkdown
Markdown to HTML
Reads the text as Markdown and hands back the HTML it describes, as characters — headings, lists, quotes, fenced code, links, bold and emphasis:
# Title <h1>Title</h1>
- one <ul>
- two <li>one</li>
<li>two</li>
</ul>
What comes out is a fragment rather than a whole page, since that is what goes into a template, a CMS field or an email that is built out of parts. A fence that names a language keeps it, as the language- class a highlighter looks for.
This is the source. md-rich is the same conversion put on the clipboard as formatting instead, for pasting into something that shows it rather than something that stores it.
md-richMarkdown
Paste Markdown as formatted text
Reads the text as Markdown and puts what it renders to on the clipboard as formatting — so writing in Markdown and pasting into Mail, Notes, a document or a chat window gives real headings, real bullets and real links rather than the marks that describe them.
$1 | md-rich
The value it hands on is what the rendered document reads as — the marks gone, a list reading as its bullets. So the row reads and searches as what it renders to, an app that takes only text still gets that text, and an app that can take a shape gets the shape. It is the same bargain a script that declares its output makes, and it is held to the same rule afterwards — $1 | md-rich | upper pastes plain, because upper produced characters the formatting no longer describes.
Markdown with no markup in it renders to nothing worth having, and is left exactly as it was.
Lists
splitSplit
Split into a list
Divides the text into a list on a delimiter. The delimiter is the filter's argument, and with none given it splits on line breaks:
$1 | split , a, b, c → three items
$1 | split ", " without the spaces
$1 | split one item a line
A list is what @name holds, so this is how one is filled from something copied — > writes over what is there, >> adds to the end of it:
$1 | split , > @recipients
$1 | split , >> @recipients
And a list on the clipboard is pasted an item at a time — the first goes on now, the next after each paste — which is what makes $1 | split , a way to deal out a row of values into a row of fields. Copying something in the middle of that ends the sequence; reaching for the same list again carries on from where it had got to.
A list is text with a shape: its text is the elements, one to a line, so every other filter reads one as the lines it looks like. The shape describes that exact text and no other, so a filter that rewrites it produces text — | split is how the result becomes a list again.
joinjoin
Join a list into one string
Runs a list back together into a single piece of text, with the separator between the elements. The separator is the filter's argument and defaults to , :
@recipients | join a, b, c
@recipients | join "; " a; b; c
$1 | split , | join " and "
Handed plain text it reads the lines as the list, so $1 | join is a way to turn a column into a sentence without splitting it first.
A list is text with a shape: its text is the elements, one to a line, so every other filter reads one as the lines it looks like. The shape describes that exact text and no other, so a filter that rewrites it produces text — | split is how the result becomes a list again.
itemList
Take one item of a list
Takes a single element out of a list, by index. Lists count from zero: item 0 is the first, item 1 the second, and a negative index counts from the other end — item -1 is the last.
@recipients | item 1
@recipients[1] the same item, written shorter
$1 | split , | item -1
@name[…] is this filter written on the reference and counts the same way, so @list[1] and | item 1 are one element.
Asking for an item the list does not have is an error naming the numbers it does run between, rather than an empty result that would look like an empty item. As everywhere, plain text is read as its lines, so $1 | item 2 is the third line.
first and last are this filter with the index already filled in — item 0 and item -1.
reverselistReverse
Reverse a list
Turns a list back to front: the last item first.
reverse @recipients
@recipients | reverse the same thing
$1 | split , | reverse
Handed plain text it reads the lines as the list, the way every list filter does — so $1 | reverse is reverse-lines with a list coming out of it rather than text, which is what makes it worth having beside that one. On the clipboard that means the items are pasted from the bottom up.
An item with a line break inside it stays one item, which is the difference that matters once a list has been made: reverse-lines would turn such an item inside out.
continuelistContinue
Carry on down a list
Hands on what the clipboard has not been dealt yet of a list.
A list on the clipboard is pasted an item at a time, and copying something in the middle of that ends the sequence — which is the ordinary way to end up half way down one, since something has to be copied to fill in the field beside it. This is how the rest is asked for:
continue @recipients
@recipients | continue the same thing
Naming the list itself always starts it again from the top. Carrying on is the rarer of the two and the one that would be surprising to get by accident, so it is the one that has to be said out loud.
What it hands over is the tail of the list, so a second continue finds where this one left off rather than starting again. A list nothing has been dealt out of, and one whose sequence ran to the end, both come back whole — there being nowhere in either to carry on from.
indexlistFrom
Take a list from an item onward
Hands on a list from a given item, dropping what came before it. Lists count from zero, so index 0 is the whole list, index 2 everything from the third on, and a negative index counts from the other end — index -2 is the last two.
index @recipients 2
@recipients | index 2 the same thing
index @recipients $next from wherever a variable says
On the clipboard that means the sequence starts there: index @rows 3 picks up a form at its fourth field. Where the answer is "wherever it left off", continue says it without the counting.
Asking for an item the list does not have is an error naming the numbers it does run between, rather than an empty list that would look like an empty answer.
maplistMap
Run a filter over every item
Runs another filter over each element of a list, and hands back a list of what each one gave. The filter to run is map's first argument, and anything after it is that filter's own:
@recipients | map upper
@recipients | map trim
| map @recipients upper the same thing
$1 | split , | map trim
The result is always the same length as the list that went in — one answer to one item, which is the whole of what mapping means. A filter that produces a list of its own therefore lands as one item holding that list's text rather than melting into the items around it.
An operator has to be quoted, because a pipeline splits its steps on one: write map "+" 5 and map "++" "!", not map + 5.
eval is the filter this exists for. A list of templates becomes a list of what they say:
@letters | map eval
A list is text with a shape: its text is the elements, one to a line, so every other filter reads one as the lines it looks like. The shape describes that exact text and no other, so a filter that rewrites it produces text — | split is how the result becomes a list again.
firstPipeline
The first item of a list
The first element — item 0 under a name worth reaching for.
@recipients | first
A variable holds one thing, so sending a list to a $name is an error rather than a guess at which item was meant. This is one of the two answers to it — join is the other, where all of them were meant:
@recipients | first > $to
lastPipeline
The last item of a list
The last element — item -1 under a name worth reaching for.
@recipients | last
Case
upperScript
UPPERCASE
Every letter in capitals. ASCII only, so accented letters are left as they are.
lowerScript
lowercase
Every letter in lowercase. ASCII only, so accented letters are left as they are.
titleScript
Title Case
Capitalises the first character of every run of non-space characters and lowercases the rest, so the QUICK brown fox becomes The Quick Brown Fox.
Spacing, tabs and line breaks are preserved exactly. It knows nothing about which small words a style guide would leave alone.
snakeScript
snake_case
Lowercases the text and joins the words with underscores. A capital inside a word is treated as the start of a new one, so myVarName becomes my_var_name, and anything that is not a letter or a digit becomes the separator.
kebabScript
kebab-case
snake with hyphens: Hello There becomes hello-there. This is also what a URL slug wants, which is what slug uses it for.
Encoding
base64Script
Encode as Base64
Base64-encodes the text as UTF-8, on one line with no wrapping.
base64-decode is the way back.
base64-decodeScript
Decode Base64
Decodes Base64 back to text. Whitespace in the input is ignored, so a wrapped block pasted out of a mail header still decodes.
Input that is not valid Base64 fails and leaves the text alone.
url-encodeScript
Percent-encode for a URL
Percent-encodes the text the way encodeURIComponent does, so it is safe to drop into a query string. &, =, / and ? are all escaped.
$1 | url-encode
url-decodeScript
Decode percent-encoding
Turns %20 and friends back into the characters they stand for.
md5Script
MD5 hash
The MD5 of the text as UTF-8, in lowercase hex, with no trailing newline included in what is hashed.
MD5 is broken for anything security-related. It is here for checksums and cache keys.
sha256Script
SHA-256 hash
The SHA-256 of the text as UTF-8, in lowercase hex, with no trailing newline included in what is hashed.
Math
+Arithmetic
Add a number
Adds to the number in the text.
$1 + 5
$1 | + 5 the same thing
$subtotal + $shipping
Every step of a pipeline runs in the order it is written, and a sum is an ordinary step — so there is no operator precedence here. $1 + 2 * 3 multiplies the sum by three; it does not add six. The search bar says so when a command mixes + or - with * or /, which is the only place the two readings differ.
Numbers are read and written plainly: a . for the point, no separator between the thousands, and a leading - for a negative. Text that is not a number — 12kg, $4.99, an empty clipboard — is an error rather than a guess.
-Arithmetic
Subtract a number
Subtracts from the number in the text.
$1 - 5
$1 - -5 which adds five
An operator needs a space either side of it, so that dateadd -30min and sed -E stay the arguments they are. $1 -5 is refused rather than read as a subtraction.
Every step of a pipeline runs in the order it is written, and a sum is an ordinary step — so there is no operator precedence here. $1 + 2 * 3 multiplies the sum by three; it does not add six. The search bar says so when a command mixes + or - with * or /, which is the only place the two readings differ.
Numbers are read and written plainly: a . for the point, no separator between the thousands, and a leading - for a negative. Text that is not a number — 12kg, $4.99, an empty clipboard — is an error rather than a guess.
*Arithmetic
Multiply by a number
Multiplies the number in the text.
$1 * 1.2 add 20% to a price
$1 * -1 flip the sign
Every step of a pipeline runs in the order it is written, and a sum is an ordinary step — so there is no operator precedence here. $1 + 2 * 3 multiplies the sum by three; it does not add six. The search bar says so when a command mixes + or - with * or /, which is the only place the two readings differ.
Numbers are read and written plainly: a . for the point, no separator between the thousands, and a leading - for a negative. Text that is not a number — 12kg, $4.99, an empty clipboard — is an error rather than a guess.
/Arithmetic
Divide by a number
Divides the number in the text.
$1 / 100
$total / $people
Division is the one sum that cannot always be written down exactly, so it is carried to 10 decimal places and rounded there. Everything else is exact: 0.1 + 0.2 is 0.3, not 0.30000000000000004, because the arithmetic is decimal rather than binary.
Dividing by zero is an error and leaves the text alone.
Every step of a pipeline runs in the order it is written, and a sum is an ordinary step — so there is no operator precedence here. $1 + 2 * 3 multiplies the sum by three; it does not add six. The search bar says so when a command mixes + or - with * or /, which is the only place the two readings differ.
Numbers are read and written plainly: a . for the point, no separator between the thousands, and a leading - for a negative. Text that is not a number — 12kg, $4.99, an empty clipboard — is an error rather than a guess.
JSON
jsonScript
Pretty print JSON
Re-indents JSON with two spaces per level, leaving the keys in the order they were written.
It parses first, so it is also a way to find out whether something is valid JSON at all: if it is not, the action fails and says where. The JSON copied trigger runs this automatically for anything that parses.
Pretty printing is idempotent, and the text as it was copied stays in the entry's history, so reformatting is never lost work.
json-compactScript
Compact JSON onto one line
The opposite of json: parses the JSON and prints it again with no whitespace at all, which is what a config file or a curl body usually wants.
json-escapeScript
Quote the text as a JSON string
Wraps the text in double quotes and escapes what has to be escaped — quotes, backslashes, newlines — so the result can be pasted straight in as the value of a JSON field.
The input is treated as plain text, whatever it looks like: {"a":1} comes back as the string "{\"a\":1}".
Time
nowThe time now
The time now
The time now, in this Mac's own zone, written as ISO 8601 so that everything here can read it back.
It reads nothing, which is what makes it a start rather than a step. Every command needs a source, so it is written with the leading | that means "the text at hand" — there just is not any:
| now 2026-08-06T15:04:05-04:00
| now | strftime %H:%M 15:04
| now | strftime %F 2026-08-06
| now | dateadd +1w a week today
Because it reads nothing, an empty clipboard history is no obstacle to it, nor is a picture sitting at the top of one.
With a zone named it answers in that zone — now UTC is utcnow, and now Asia/Tokyo is what time it is in Tokyo. Zones are named as they are for addtz.
A command that names the time twice names one time: the moment is taken once, when the command starts.
utcnowThe time now
The time now in UTC
The time now in UTC, written as ISO 8601 with the Z that says so — now in every other way.
| utcnow 2026-08-06T19:04:05Z
| utcnow | strftime %FT%T 2026-08-06T19:04:05
This is the one to stamp anything that leaves the machine: a log line, a commit message, a ticket. now is the one to read off a clock in the room.
strftimeDate format
Reformat a date
Reads the text as a moment in time and writes it back out in the shape you ask for, by the rules of strftime(3).
What it will read: ISO 8601 (2026-08-06T15:04:05Z), a date on its own (2026-08-06), a log line's 2026-08-06 15:04:05, an email or HTTP header's Wed, 06 Aug 2026 15:04:05 +0000, a Unix time (1786000000, or milliseconds when it is thirteen digits or more), and failing all of those, a date written out in words anywhere in the text — August 6, 2026.
The format is the filter's argument, so it can be different every time:
$1 | strftime %A Thursday
$1 | strftime "%B %e, %Y" August 6, 2026
$1 | strftime %s as a Unix time
$1 | strftime %FT%T%z back to ISO 8601
Several words are one format, so the quotes above are only needed where you want the spaces kept exactly. With no argument it uses the format on the action itself, which is what a trigger uses too — triggers name actions without arguments, so a copy of this action with its own format is how dates get normalised on copy.
Times come out in the Mac's own time zone. Month and day names come out in English, so that a pipeline means the same thing on every machine it is run on.
addtzTime zone
Say which zone a date is in
Says where an unzoned date was written, leaving its clock alone. 2026-08-06 15:04:05 says three minutes past three without saying three o'clock where, so it is read as three o'clock here; addtz UTC says it was three o'clock in UTC all along.
$1 | addtz UTC
2026-08-06 15:04:05
→ 2026-08-06T15:04:05Z
A date that already says where it is — an ISO 8601 offset, a Z, an email header's GMT, or a Unix time, which is an instant by itself — is an error rather than something to overwrite. Moving one of those to another zone is totz.
Zones can be named as UTC, as America/New_York, as PST — the coast that keeps daylight saving, not a fixed -08:00 — or as an offset like +05:30. With no zone named it uses the one on the action, and failing that the Mac's own.
This is the way to handle a server log, which is usually written in UTC and says so nowhere:
$1 | addtz UTC | totz America/New_York | strftime "%F %T %Z"
2026-08-06 15:04:05
→ 2026-08-06 11:04:05 EDT
totzTime zone
Move a date to another zone
Moves a date to another zone: the same moment, read off another clock. Where addtz keeps the numbers and changes the moment, this keeps the moment and changes the numbers.
$1 | totz America/New_York
2026-08-06T15:04:05Z
→ 2026-08-06T11:04:05-04:00
A date that never said which zone it was in is read as this Mac's time, as everything here reads one; addtz is how to say otherwise before converting.
Zones are named as they are for addtz, and with none named it moves the date to the zone on the action, or failing that to the Mac's own — which is how a time in a mail from someone three zones away becomes a time you can act on.
The result carries its new zone, so anything after it works in that zone: strftime writes the clock this left rather than moving it back.
dateaddDate offset
Move a date forwards or back
Moves a date by a span of time: a count and a unit, as many as you like, with an optional sign in front of the lot.
$1 | dateadd +2d two days on
$1 | dateadd -30min half an hour back
$1 | dateadd 1h30m an hour and a half on
$1 | dateadd "-1mo 2d" a month and two days back
The units are s, min, h, d, w, mo and y, spelled out or abbreviated — sec, mins, hrs, days, weeks, months, yr all read as you would expect.
A lowercase m is minutes, as it is in Prometheus, systemd and Go. Months are mo: an uppercase M means months to some tools and minutes to others, and one shift key should not be the difference between a minute and forty-four thousand of them, so it is refused rather than guessed at.
Days and larger are counted on the calendar, not in seconds. A day later is this time tomorrow even where the clocks changed overnight and the day ran to 23 hours, and a month after the 31st is the last day of a month that has no 31st.
A date that said which zone it was in still says so afterwards, and one that did not still does not — moving a date is no answer to a question about zones, which is what addtz is for.
One limit worth knowing: a date carrying an offset — -04:00, which is all ISO 8601 can carry — is counted in that offset, since an offset knows nothing of when the clocks change. Across a change, +1d there is 24 hours rather than 23 or 25. Text with no zone is counted in the Mac's own, which does know.
Web
remove-url-trackingSubstitution
Strip URL tracking parameters
Removes the utm_*, fbclid and gclid parameters a link picks up on its way to you, and leaves the rest of the query string alone — including the ? that starts it, so ?utm_source=news&id=7 comes back as ?id=7 rather than as a query nothing can read.
It edits the text rather than parsing a URL, so it works on a paragraph with several links in it.
hostSubstitution
Keep only a URL's host
Reduces https://www.example.com:8080/a/b?c=1 to www.example.com:8080. Text that does not start with a scheme is left as it is.
httpsSubstitution
Upgrade http:// to https://
Rewrites the scheme of every http:// URL in the text. Nothing else changes.
slugScript
URL slug
Lowercases the text and joins the words with hyphens, dropping punctuation — Hello, World! becomes hello-world. The same thing kebab does, under the name you reach for when you are naming a page.
Development
jira-linkSubstitution
Jira ticket to link
Turns a bare ticket key — ABC-123 — into a link to it.
It needs a variable called jiraBaseURL holding the root of your Jira, without a trailing slash, which you can add in the Variables pane or from the overlay with ⇧↩:
jiraBaseURL = https://jira.example.com
Until that variable exists the replacement is left as written, so you will see the ${jiraBaseURL} rather than a broken link. The Jira ticket copied trigger runs this on copy once you enable it.
uuidScript
A fresh UUID
Ignores the text entirely and produces a new lowercase UUID. A filter that generates rather than transforms, which makes $1 | uuid a quick way to get one onto the clipboard.
Fun
upside-downScript
uʍop ǝpᴉsdn
Turns the text over. Every letter, digit and bracket is swapped for the character that looks like it standing on its head, and the whole lot is reversed so that it reads from the other end.
Hello, World! → ¡plɹoM 'ollǝH
Brackets and quotes turn with everything else — a ( comes back as a ) — so a flipped line still looks like a line rather than like a mistake. Anything with no upside-down twin, emoji included, is left as it is and simply moves to the other end. Several lines are turned as one piece of text, so the last line comes out first, which is what the page would look like held the other way up.
These are ordinary Unicode characters, so the result pastes anywhere that will take an accented letter. Running it twice does not give you back what you started with — nothing turns a Ɩ back into a 1 — but the text as it was copied is still in the entry, behind this, which is the way back:
$1 | undo