You copy a tracking number to paste into a support form. On the way there you copy the order date, and the tracking number is gone. The Mac keeps no clipboard history, just one thing on the clipboard, and every copy overwrites it.
Clipboard managers fix this, and Alfred sells its clipboard history as part of the paid Powerpack. They also keep your history in their own storage, with their own rules about what gets saved and for how long. This flow keeps it in a SQLite file on your Mac that any SQLite tool can open, and every rule it follows is a node you can click on.
It has three parts, each in its own Section. Every copy is saved to a table. A hotkey lists recent clips, with thumbnails for images, and pastes the one you pick. A daily schedule deletes rows older than 30 days.
The flow
Remember every copy, but not passwords
Clipboard Changed checks the pasteboard every half second and fires on each change. Keep Image Copies is on, so an image copy is written as a PNG into the Clips folder (Settings ▸ Storage shows where) and the payload carries {{imagePath}}. Text arrives as {{text}}, and {{contentType}} is text, image or other.
The trigger does not skip anything on its own, including passwords. Password managers that follow the nspasteboard.org convention, 1Password's Mac app among them, mark a copied password with the pasteboard type org.nspasteboard.ConcealedType, and the trigger lists every type in {{types}}. So the Condition named Worth keeping? has three rules, all required:
typescontainsorg.nspasteboard.ConcealedType, negatedtypescontainsorg.nspasteboard.TransientType, negated (apps use it for copies they mean to throw away)contentTypedoes not equalother(a copy with no text and no image)
The marker is only as good as the app that sets it. 1Password's browser extension does not set it, so a password copied from the extension is saved like any other text.
Its Yes outlet goes to Save to Database, table clips, with three fields: body = {{text}}, kind = {{contentType}}, image_path = {{imagePath}}. There is no setup step. The first save creates the table with the fields you named, plus the id and created_at columns Watchflows manages. A text copy has no imagePath, so that field is skipped rather than stored as an empty string, and the image_path column appears the first time you copy an image.
Paste an earlier clip from a hotkey
Control-Option-V fires a Keyboard Shortcut, and Run SQL reads the table:
SELECT *,
CASE WHEN kind = 'image' THEN 'Image'
ELSE substr(trim(replace(body, char(10), ' ')), 1, 120)
END AS preview,
datetime(created_at, 'localtime') AS copied_at
FROM clips
WHERE id IN (
SELECT max(id) FROM clips
GROUP BY CASE WHEN kind = 'image' THEN id ELSE body END
)
ORDER BY id DESC
LIMIT 300
Find in Database could list the rows, but this does two things it cannot. Copy the same line five times and it shows once, at its newest position (images are never merged, since every one is its own file). And each row gets a one-line preview and a local copied_at time, instead of a multi-line body and a UTC stamp.
Pick from List uses the List layout, because Thumbnail Column only works there. Primary Column is preview, Secondary Column is copied_at, Thumbnail Column is image_path, and Search Columns is body, so search matches the whole clip, not just the first 120 characters. Rows with no image keep a plain glyph.
A second Condition checks selected.kind equals image. Yes goes to Copy to Clipboard in File mode with {{selected.image_path}}, which puts a PNG on the clipboard as an image. No goes to a second Copy to Clipboard in Text mode with {{selected.body}}. Both feed one Simulate Key Press of ⌘V.
That paste lands in the app you were using, because the chooser never activates Watchflows. And it does not come back around as a new clip: every copy Watchflows makes carries its own marker type, and Clipboard Changed skips any change that has it.
Forget after 30 days
A Schedule fires every day at 12:00 into Delete from Database on clips, with one condition: created_at older than (days) 30. No SQL needed. If the Mac was asleep at noon, the missed run happens once when it wakes.
Paste this into the Flow Builder to get the same flow:
Keep a clipboard history in a database called Clipboard History. When the clipboard changes, skip it if the pasteboard types include org.nspasteboard.ConcealedType or org.nspasteboard.TransientType, and otherwise save the text, the content type and the image path to a clips table. On Control-Option-V, list the most recent clips in a searchable list with image thumbnails; copy the one I pick back to the clipboard (as an image if it is one) and press Cmd+V. Every day at noon, delete clips older than 30 days.
Setting it up
After importing, the three database nodes (Save the clip, Recent clips, Forget old clips) are marked as needing setup, and the flow cannot be switched on until they point at a database. In the first one, open the Database field, choose New Database…, name it Clipboard History and click Create. Pick the same database in the other two.
Then copy something before pressing the hotkey. Until the first save, there is no clips table for the query to read, and the run fails with SQLite's no such table error.
Switching the flow on asks you to confirm, and that is expected. Run SQL and Delete from Database are always marked destructive, and the confirmation names both.
Edge cases
Rows are deleted, files are not. The daily delete removes rows. The PNGs stay in the Clips folder until you clear them.
Moving the Clips folder breaks old thumbnails. Settings ▸ Storage moves the files, but the paths already in your table still point at the old location. Those rows show a glyph, and picking one copies nothing, so the paste repeats whatever was already on the clipboard.
A copy with text and an image is saved as text. A selection from a web page carries both, so contentType is text and no PNG is written.
A failed image save still fires. You get {{imageError}} and no imagePath. The row is saved with kind image and nothing to paste back, so picking it pastes whatever was already on the clipboard.
Variations
- Keep the last 500 instead of 30 days. Swap the delete for Run SQL:
DELETE FROM clips WHERE id NOT IN (SELECT id FROM clips ORDER BY id DESC LIMIT 500). - Clean up the PNGs too. Add a Run Script after the delete that runs
findon the Clips folder with-mtime +30 -exec rm {} +. Thermmarks it destructive, which is correct. - Copy without pasting. Delete the Simulate Key Press node and paste yourself.
- Text only, sortable. Switch Layout to Table and you can sort by
copied_atorkind. Table rows have no thumbnails, so this suits a history you mostly use for text.