Guide

Watch a Folder and Compress Everything That Lands In It

By the Smol team9 min read

Two ways to watch a folder and compress what lands in it on a Mac. A Folder Action is free, built into macOS, and needs about forty lines of AppleScript. A watched folder in an app is a checkbox. Both were running on this machine an hour ago; a 3.4 MB JPEG dropped into the Folder Action folder came back compressed in 2.2 seconds, and the app’s watched folder took 5.3 seconds.

The Folder Action route has one failure mode that almost every tutorial online gets wrong, and it is not subtle. Write your compressed output back into the folder you are watching and the folder fires again on your own output, and again on the output of that. We ran it without a guard: one dropped file became five files in twenty-five seconds and was still going when we pulled the plug.

Everything below was executed on 27 September 2026 on a MacBook Pro (M2 Pro, 10 cores, 16 GB) running macOS 27.0 build 26A428. The script was compiled, attached, fired with real drops, and detached afterwards.

Folder Action or a watched folder: which should I use?

Folder ActionApp watched folder
CostFree, in the boxPrice of the app
What you writeAppleScript plus shellNothing
Latency, one 3.4 MB JPEG2.2 s5.3 s
Ten files dropped at once14.0 s7.2 s (five files)
Recurses into subfoldersNoNo
Safe to write output into the watched folderNo, loopsYes, verified
Handles mixed file typesOne branch per type, yours to writeBuilt in
Per-file resultsWhatever you logReported
Config you can read and versionNo, a binary keyed archiveJSON settings file

The honest summary: if you have one folder, one file type and one recipe, write the Folder Action. It is free, it is fast, and it will outlive several app subscriptions. The moment you have three folders with three recipes, or mixed images and PDFs and video arriving together, the AppleScript grows a branch per case and you are maintaining a small program.

How do I make a folder compress images automatically on macOS?

A Folder Action is an AppleScript handler attached to a directory. macOS calls it with the list of items that just appeared. The dispatcher that does the calling is a per-user LaunchAgent at /System/Library/LaunchAgents/com.apple.FolderActionsDispatcher.plist, with RunAtLoad and KeepAlive both true and ProcessType: Interactive. That last key is the important one: it runs in your logged-in GUI session and nowhere else. This is not a background service for a Mac mini sitting at a login window.

Open Script Editor, paste this, and save it as a script into ~/Library/Scripts/Folder Action Scripts/. This is the exact script we ran.

on adding folder items to this_folder after receiving added_items
	set out_dir to (POSIX path of this_folder) & "Compressed/"
	do shell script "/bin/mkdir -p " & quoted form of out_dir
	repeat with an_item in added_items
		set p to POSIX path of an_item
		if my is_image(p) then
			-- A download or a scanner may still be writing. Wait for the size to settle.
			set last_size to -1
			set this_size to my byte_size(p)
			repeat while this_size is not equal to last_size
				delay 1
				set last_size to this_size
				set this_size to my byte_size(p)
			end repeat
			set out to out_dir & my base_name(p) & ".jpg"
			try
				do shell script "/usr/bin/sips -Z 2000 -s format jpeg -s formatOptions 60 " & ¬
					quoted form of p & " --out " & quoted form of out
				if (my byte_size(out)) is greater than or equal to (my byte_size(p)) then
					do shell script "/bin/rm -f " & quoted form of out
				end if
			on error errText
				do shell script "/bin/echo " & quoted form of (p & " :: " & errText) & ¬
					" >> " & quoted form of (out_dir & "errors.log")
			end try
		end if
	end repeat
end adding folder items to

on is_image(p)
	set lower to do shell script "printf '%s' " & quoted form of p & " | /usr/bin/tr 'A-Z' 'a-z'"
	repeat with ext in {".jpg", ".jpeg", ".png", ".heic", ".tif", ".tiff"}
		if lower ends with (ext as text) then return true
	end repeat
	return false
end is_image

on base_name(p)
	set f to do shell script "/usr/bin/basename " & quoted form of p
	set n to (number of characters of f)
	repeat with i from n to 1 by -1
		if character i of f is "." then return text 1 thru (i - 1) of f
	end repeat
	return f
end base_name

on byte_size(p)
	try
		return (do shell script "/usr/bin/stat -f%z " & quoted form of p) as integer
	on error
		return 0
	end try
end byte_size

Then right-click the folder you want watched, choose Services → Folder Actions Setup, tick Enable Folder Actions, and pick your script. The setup app lives at /System/Library/CoreServices/Applications/Folder Actions Setup.app when the context menu refuses to cooperate. To do it without the GUI:

osascript -e 'tell application "System Events" to set folder actions enabled to true'

osascript <<'END'
tell application "System Events"
	make new folder action at end of folder actions ¬
		with properties {path:"/Users/you/Dropbox/Inbox", name:"Inbox"}
	tell folder action "Inbox"
		make new script at end of scripts with properties {name:"Compress On Drop.scpt"}
	end tell
end tell
END

Four things in that script are doing real work.

Output goes to a subfolder. That single decision is what stops the loop described in the next section. The script writes to Compressed/, which is inside the watched folder but is a directory, and directories do not re-fire an image handler.

The settle loop. A Folder Action fires when the file appears, not when it finishes writing. A scanner, an AirDrop or a browser download will hand you a zero-byte file and keep growing it. Polling stat -f%z until it stops changing costs a second and prevents a truncated result.

Absolute paths to every binary. The dispatcher does not inherit your shell environment. /usr/bin/sips resolves; a bare magick or gs from Homebrew does not. This is the same trap covered in the Quick Action walkthrough, and it is the single most common reason a script that works in Terminal does nothing on drop.

The size guard. sips defaults to JPEG quality 75, so an already-compressed file comes back bigger. We dropped a 79,358-byte image into the watched folder and the guard deleted the worse output, correctly leaving nothing behind. A 3,398,183-byte original produced 289,061 bytes, a 91.5% reduction.

Why does my Folder Action keep creating more and more files?

Because it is watching the folder it is writing into. Here is the version almost every tutorial publishes, with the output beside the input and no name check:

on adding folder items to this_folder after receiving added_items
	repeat with an_item in added_items
		set p to POSIX path of an_item
		if p ends with ".jpg" then
			set out to (text 1 thru -5 of p) & "-small.jpg"
			try
				do shell script "/usr/bin/sips -Z 1000 " & quoted form of p & ¬
					" --out " & quoted form of out
			end try
		end if
	end repeat
end adding folder items to

We attached that, dropped one 95,730-byte JPEG called photo.jpg, and waited twenty-five seconds. The folder then contained:

photo.jpg
photo-small.jpg
photo-small-small.jpg
photo-small-small-small.jpg
photo-small-small-small-small.jpg

It had not stopped. It stops when the filename hits the 255-character limit, or when you notice. Two fixes, and you want one of them before you attach anything:

Write somewhere else. A subdirectory, or a sibling folder entirely. This is what the working script above does and it is the fix we would pick, because it needs no reasoning about names.

Or skip your own output by name. Add if p ends with "-small.jpg" then return at the top of the loop. It works, and it fails the day someone drops a file that was already named that way.

What are the real limits of a Folder Action?

We measured these rather than repeating them. All figures on the 3,398,183-byte fixture, with the settle loop in place.

What happenedResult
One file copied inOutput after 2.2 s
Ten files copied in at onceAll ten done 14.0 s after the first copy
One file moved in from another folder on the same diskOutput after 6.9 s
A file placed in a subfolder of the watched folderNever processed
An already-small fileSize guard deleted the worse output, as intended

No recursion. A Folder Action watches one directory. Files that appear in a child folder are invisible to it. If your camera import creates a dated subfolder, the action never sees anything. Attaching the script to each subfolder is possible and does not scale.

Your configuration is not readable. Attachments live in ~/Library/Preferences/com.apple.FolderActionsDispatcher.plist, where the folderActions key holds an NSKeyedArchiver blob. With one folder attached it measured 2,904 bytes of binary; empty, 236. You cannot diff it, you cannot put it in a dotfiles repo, and you cannot reasonably review it. Compare that with the plain-text script in the PDF Folder Action, which you can read and version like any other code.

It is a GUI session service. ProcessType: Interactive means no logged-in user, no watching. For an always-on box, a LaunchDaemon around fswatch is the correct tool and a Folder Action is the wrong one.

Errors are silent unless you make them loud. An AppleScript try block without an on error branch swallows failures completely. The script above appends to errors.log for exactly this reason. Without it, a folder quietly stops compressing and you find out in six weeks.

How do Smol’s watched folders compare?

Smol ships watched folders. If you have read somewhere that it does not, that is out of date, and rather than assert it we drove the feature through Smol’s MCP server while writing this page so the calls and the responses could be quoted directly.

Adding one is a single call. The response:

{
  "id": "mujkvqa597142a",
  "path": "/private/tmp/smolart/smolwatch",
  "enabled": true,
  "presetId": null,
  "createdAt": "2026-09-27T08:50:10.258Z"
}

Then a copy of the same 3,398,183-byte JPEG into that directory. Output appeared 5.3 seconds later as incoming_compressed.jpg at 249,475 bytes, a 92.7% reduction, using the app’s saved defaults: quality 75, fit inside 2,000 × 2,000, metadata stripped. Five files dropped together were all finished 7.2 seconds after the first one landed, which is where the fixed startup cost of the AppleScript route starts to tell against it.

The output lands in the watched folder, next to the original. That is the exact arrangement that makes the AppleScript version spiral, so we left it idle for ten seconds and counted: two files, the original and one result. No re-trigger. A watcher that knows which files it wrote does not need a name guard, and this is the single clearest reason to prefer a real feature over forty lines of AppleScript.

One thing it does not do, and we would rather say so. The keep-the-smaller-result guard that Smol applies to a normal compression job does not appear to run on the watched-folder path. We dropped a 900 × 474 JPEG already encoded down to 37,434 bytes. The watched folder wrote probe2_compressed.jpg at 48,808 bytes, 30.4% larger, at the same dimensions. The identical file sent through the normal compress tool came back keptOriginal: true, reason: "already optimized", 0.0% savings, bytes untouched. Reproduced twice. If you point a watched folder at a directory that already holds optimized assets, check the results before you trust them.

Removing the folder is one call returning {"removed": true}, and listing afterwards returned an empty array, which is how we confirmed the test left nothing behind on this machine.

When a Folder Action is the right answer

One folder, one rule, files you control. A screenshots folder that resizes to 1,600 px. An inbox that flattens scans to JPEG. Forty lines, zero dollars, faster than any app on a single file, and it has no opinion about what you do next. Build it. We would rather you did that than paid us $29 for a checkbox.

Your pipeline is not just compression. Compress, then rename with a date prefix, then move to a dated folder, then POST to a webhook, then tell Notion. No compression app ships your workflow. A shell script runs it, and AppleScript can call anything scriptable on the machine.

You need it on a headless or always-on Mac. Neither a Folder Action nor a Mac app in the Dock is right here. Use fswatch or launchd with a WatchPaths key, and run sips, cwebp or gs from a daemon. A GUI app is the wrong shape for a server, and we will not pretend otherwise.

You are already happy. If a Folder Action has been quietly working for two years, leave it alone.

Where the app earns its one-time $29 is the part the script never quite reaches: several folders with different presets, mixed images and PDFs and video arriving in one drop, output written safely beside the originals with no re-trigger to reason about, and a settings file you can read. Plus the parts that have nothing to do with watching, like the WebP output sips cannot write.

If you would rather not choose, a coding agent with Smol’s MCP tools can add a watched folder, run a batch over what is already in it, and take the folder back off the list when the project ends. Every watched-folder number on this page came out of exactly those calls.

For the right-click version of the same job, see building a Finder Quick Action that compresses. For resizing rather than compressing, and the destructive default that makes Automator dangerous, batch resizing images with Automator has the tested workflow. And if the folder you keep wanting to watch is a source repository, the earlier hook is a pre-commit check, because once a 4 MB PNG is in git history, every clone pays for it forever.

Frequently asked questions

How do I make a folder automatically compress images on a Mac?

Attach a Folder Action. Save an AppleScript with an "on adding folder items to" handler into ~/Library/Scripts/Folder Action Scripts/, right-click the folder, choose Services then Folder Actions Setup, enable Folder Actions and pick the script. Inside the handler, call /usr/bin/sips with an absolute path and write the output into a subfolder rather than beside the original.

Why does my Folder Action run over and over on the same file?

Because the compressed output is landing in the folder being watched, which fires the action again on your own result. We tested an unguarded script: one dropped file became five files in twenty-five seconds and was still multiplying. Write the output into a subfolder, or skip files whose name already carries your output suffix.

How fast does a Folder Action fire after a file is dropped?

On an M2 Pro running macOS 27, a 3.4 MB JPEG copied into a watched folder produced compressed output 2.2 seconds later, including a one-second wait for the file size to settle. Ten files copied at once were all finished 14.0 seconds after the first. The same file moved in from elsewhere on the disk took 6.9 seconds.

Do Folder Actions work on subfolders?

No. A Folder Action watches exactly one directory. A file placed inside a subfolder of a watched folder is never passed to the handler, which we confirmed by dropping one and waiting. Attaching the same script to each subfolder works but does not scale, and nothing picks up folders created after you set it up.

Does Smol have watched folders?

Yes. Folders can be added, enabled, given a preset and removed, and each one compresses new files as they arrive using your saved settings. In testing, a 3,398,183 byte JPEG dropped into a watched folder produced a 249,475 byte result 5.3 seconds later, and five files dropped together finished in 7.2 seconds.

Will a watched folder ever make my files bigger?

It can. We dropped a 900 x 474 JPEG already encoded down to 37,434 bytes into a Smol watched folder and the result was 48,808 bytes, 30.4% larger at the same dimensions, while the same file through a normal compress job was correctly left alone as already optimized. A hand-written Folder Action has the same risk unless you compare sizes with stat -f%z and delete the worse result.

Keep reading