Guide

The sips Command: macOS’s Built-In Image Tool

By the Smol team13 min read

sips is macOS’s built-in image tool, at /usr/bin/sips, already installed on every Mac with no Homebrew and no download. sips -Z 1600 photo.jpg --out small.jpg resizes. -s format converts. -g reads properties. For most resize-and-convert work it is the correct answer, and it is free.

It is also full of undocumented behavior. The four named quality presets are exact aliases for numbers. The 0–100 quality scale is not continuous. WebP can be read but not written. Setting DPI on a JPEG writes tags that sips itself then refuses to read back. And there is a full HTML5 Canvas API hiding behind one flag that Apple never mentions.

Everything below was run on 26 September 2026 on a MacBook Pro (M2 Pro, 10 cores, 16 GB) on macOS 27.0 build 26A428 with sips-316. The fixture is an Apple system wallpaper, /System/Library/Wallpapers/.default/DefaultAerial.jpg, at 4,096 × 2,160 and 3,398,183 bytes. Byte counts and error codes are real.

Which formats can sips actually read and write?

Run sips --formats. It is the single most useful undocumented flag in the tool, it prints the authoritative answer for your exact OS version, and almost nobody cites it. On macOS 27.0 it lists 65 formats, and the ones that matter split like this:

FormatReadWrite
JPEG, PNG, TIFF, GIF, BMP, PSD, JPEG 2000YesYes
HEIC, HEICS, AVIFYesYes
PDFYesYes (first page in, one page out)
WebP (org.webmproject.webp)YesNo
JPEG XL (jxl), HEIF, AVCIYesNo
RAW: CR2, CR3, NEF, ARW, DNG, RAF, ORF, RW2, PEF, IIQ, 3FRYesNo
SVG, DICOM, Radiance HDRYesNo

WebP is the one that catches people. sips opens WebP files happily, so sips -s format jpeg in.webp --out out.jpg works. Going the other way does not. sips -s format webp base.jpg --out out.webp prints Error: Can’t write format: org.webmproject.webp, then Error 13: an unknown error occurred, and exits 13. No file is written. The same happens with -o outdir/ and with an in-place rewrite, and in every case the original is left byte-for-byte untouched. If you scripted a WebP pipeline around sips without checking exit codes, it produced nothing.

AVIF, on the other hand, is writable, and it is very good. Same 4,096 × 2,160 source, same formatOptions value, three output formats:

formatOptionsJPEGAVIFHEIC
30604,199 B128,508 B225,889 B
601,201,344 B376,744 B452,700 B
902,054,703 B925,627 B1,025,221 B

At the same nominal quality AVIF came out 68.6% smaller than JPEG and 16.8% smaller than HEIC. A lossless PNG of the same image is 8,629,863 bytes, which is a useful reminder that PNG is the wrong container for a photograph.

How do I resize an image with sips?

Five flags do geometry, and the difference between two of them will ruin a batch if you guess. All measurements below are on the 4,096 × 2,160 fixture.

CommandResultKeeps aspect?
sips -Z 10001000 × 527Yes
sips -z 1000 10001000 × 1000No
sips --resampleWidth 10001000 × 527Yes
sips -c 1000 10001000 × 1000, center cropCrops
sips -p 2400 4096 --padColor FF00004096 × 2400, red barsPads

-Z (capital) takes one number, the longest edge, and preserves aspect ratio. -z (lowercase) takes height then width in that order and will squash your image without comment. Nine times out of ten you want -Z.

-Z is not a maximum. The flag is spelled --resampleHeightWidthMax, which reads like a ceiling, and it is not one. Run sips -Z 6000 against the 4,096 px fixture and you get a 6,000 × 3,164 upscale. Feed a folder of mixed sizes to -Z 2000 and every image below 2,000 px gets blown up. Guard it yourself with sips -g pixelWidthbefore you resample.

One more surprise from that upscale: the 6,000 px output was 2,241,378 bytes, smaller than the 3,398,183-byte original. The re-encode at sips’s default quality threw away more than the extra pixels added. Never infer quality from file size.

-p is documented as padding but behaves as “fit to this canvas”. On a 1,600 × 843 input, sips -p 1000 1000 returned 1,000 × 1,000 — it cropped. If you rely on padding never removing pixels, check your target against the source first.

What do sips quality settings actually mean?

-s formatOptions accepts low, normal, high, best, default, or a number 0–100. Encoding the fixture at all eight values and hashing the results with shasum -a 256 settles what the words mean:

formatOptionsBytesSHA-256 (first 16)
low / 25544,217940bf4cd502e02fc
normal / 50966,525627711d172f2fdaa
high / 75 / default / flag omitted1,654,922607835d1d40515ed
best / 1005,376,27715cf8ec8db96a606

The named presets are not “roughly” 25/50/75/100. They are byte-identical aliases. And omitting -s formatOptions entirely produces the same hash as 75, so sips’s default JPEG quality is 75. That one fact explains most “why did sips make my file bigger” reports: a JPEG already saved at quality 50 will grow when you touch it with any sips command at all. Our 157,425-byte fixture came back at 229,775 bytes after a plain re-encode, 46.0% larger, for no visible gain.

best is not a setting to reach for casually. At 5,376,277 bytes it is 3.25× the size of high and 58% larger than the original JPEG it was made from. It does not change chroma subsampling: a 4:2:0 source stayed 4:2:0 at every quality level, verified with identify -format %[jpeg:sampling-factor]. sips preserves the source’s subsampling rather than choosing its own.

The scale is quantized. Encoding the same source at every integer from 0 to 100 produced only 54 distinct files. Long runs collapse: 0–10 are one file, 75–78 another, 79–84 another, 85–90 another, and 91–99 another. Asking for 88 instead of 85 changes nothing at all. Tuning formatOptions in single steps is wasted effort; move in fives and check the bytes.

Does sips remove EXIF and GPS data?

No, and this is the most important thing on this page if you are publishing images.

Take a fixture carrying an invented tag set including coordinates, a capture date and a camera serial number, then run every kind of sips operation over it and ask exiftool what survived:

OperationGPSCapture dateSerialIPTCXMP
-Z 800SurvivesSurvivesSurvivesSurvivesSurvives
-s formatOptions 50SurvivesSurvivesSurvivesSurvivesSurvives
-r 90SurvivesSurvivesSurvivesSurvivesSurvives
-s format pngSurvivesSurvivesSurvivesLostSurvives
-s format heicSurvivesSurvivesSurvivesLostSurvives
-s format avifSurvivesSurvivesSurvivesLostSurvives
--deleteColorManagementPropertiesSurvivesSurvivesSurvivesSurvivesSurvives

Resizing to 800 px, re-encoding, rotating and converting to three different formats all carried the coordinates through untouched. Only IPTC dropped, and only on a format change. If your privacy plan is “I resized it, so the location is gone”, it is not gone.

sips -d does delete some things. -d make, -d model, -d artist and -d description each worked. Three things then happened that are worth knowing about.

One. sips -d copyright exits 0 and does nothing. The IFD0:Copyright tag is still there afterwards, and sips -g copyright still reads it back. Reproduced alone and in combination.

Two. Any successful -d writes a new XMP-dc:Rights block containing the copyright string. Deleting metadata copied metadata into a second place. Verified separately for all four working flags.

Three. Nothing in the -d set touches coordinates, capture dates, serial numbers, IPTC or XMP, and every -d is a full re-encode: the 160,907-byte fixture came back at 233,366 bytes, 45.0% larger, with the GPS still in it.

sips -g all is not a metadata audit either. It reports 17 properties, of which the only EXIF it surfaces are make, model, description, copyright and artist. No GPS. No dates. If you need to know what is actually in a file, or you need it gone, use exiftool — the commands are in removing metadata from the Terminal on macOS.

How do I set DPI and handle color profiles?

DPI in an image file is metadata. It is a note saying “print me at this density” and it changes no pixels. sips -s dpiWidth 300 -s dpiHeight 300 left the fixture at 1,600 × 843, as it should.

On a JPEG it also does not work. Exit code 0, no warning, and afterwards sips -g dpiWidth reports 72.000. exiftool shows why: XResolution and YResolution were written as 300, but ResolutionUnit was written as None, which makes the numbers a bare aspect ratio rather than a density. The same command on a TIFF or a PNG reports dpiWidth: 300.000 and works correctly. So on JPEG you pay for a full re-encode (157,425 → 229,775 bytes, different decoded pixels) and get nothing. Use exiftool for JPEG DPI, or convert to TIFF first.

Color management has four flags and they are easy to confuse.

FlagWhat it does
-x out.iccExtracts the embedded profile. Ours came out as a 3,144-byte ICC file.
-e profile.iccRe-tags without converting pixels. Colors shift. Use only to fix a wrong tag.
-m profile.iccConverts the pixels into the profile and re-tags. This is what you usually want.
--optimizeColorForSharingBakes down to sRGB. Verified: a Display P3 file came back tagged sRGB IEC61966-2.1.

-e AdobeRGB1998.icc on an sRGB file reported profile: Adobe RGB (1998) afterwards without converting anything, which is exactly how images end up looking desaturated on the web. -m did the real conversion. --optimizeColorForSharing is the one-flag answer for web export and is worth knowing about.

--deleteColorManagementProperties did not delete anything on our fixture. The profile still read sRGB IEC61966-2.1 afterwards, and the file grew 45.1%.

The whole “profile query” half of sips appears to be dead on macOS 27.0. Point it at Apple’s own /System/Library/ColorSync/Profiles/Display P3.icc and -g description, -g class, -g space, -g version, -g md5 and -g size all return <nil>. Plain file(1) reads the same profile fine (“ColorSync color profile 4.0, type appl, RGB/XYZ-mntr”). Use ColorSync Utility or file to inspect profiles, not sips.

How do I run sips across a whole folder?

sips takes many input files and one -o directory. It will not take many inputs and one --out file: that returns Error 9: cannot perform functions on files because no destination dir was specified, which is the tool refusing to overwrite the same output 100 times. Good design.

# Safe: many in, one output directory. Extensions are corrected for you.
mkdir -p out
sips -Z 1600 -s format jpeg -s formatOptions 70 shots/*.jpg -o out

That single invocation is no faster than a loop. Resizing 100 copies of the 3.4 MB fixture to a 1,600 px long edge took 37.4 s as one sips call and 36.4 s as a shell loop calling sips 100 times. sips is single-threaded and nine of ten cores sat idle. Parallelize it yourself:

mkdir -p out
find shots -maxdepth 1 -name '*.jpg' -print0 |
  xargs -0 -P 8 -I{} sips -Z 1600 {} -o out

That finished the same 100 files in 16.3 s, a 2.2× speedup. Use -print0 with xargs -0 rather than piping ls, or a filename containing a newline will split into two broken arguments.

Route100 filesOutput eachTotal saving
sips shell loop36.4 s287,813 B91.5%
sips, one call, 100 inputs37.4 s287,813 B91.5%
sips + xargs -P 816.3 s287,813 B91.5%
Smol, quality 75, fit 1,600 px219 s173,934 B94.9%

Read that honestly. On identical pixel dimensions (1,600 × 843 versus 1,600 × 844, a rounding difference) and the same nominal quality of 75, parallel sips was 13× faster and Smol’s files were 39.6% smaller. Different encoders, different trade. If throughput is your constraint, sips with xargs is hard to beat and costs nothing. If bytes on a CDN are your constraint, 39.6% is a lot of bytes.

Three practical notes on batching. -o dir/ corrects extensions for you, so -s format png in.jpg -o out/ writes out/in.png. There is no “skip if already small” option, so build the guard yourself with sips -g pixelWidth or stat -f%z. And sips has no size guard at all: compare output against input and keep the smaller file, because as shown above it will happily hand you something 46% bigger.

What is the sips JavaScript API?

sips -j script.js file.jpg runs a JavaScript file against your images. sips --help lists the flag as -j, --js file and says nothing else. Dumping the global scope from inside a script shows what you actually get: print, Output, Canvas, Rect, Size, Point, ImageData, Gradient, Image, PatternObject and sips.

Canvas.prototype carries drawImage, fillRect, fillText, measureText, createLinearGradient, createRadialGradient, arc, bezierCurveTo, clip, getImageData, globalCompositeOperation and about forty more. It is the HTML5 Canvas 2D API, on the command line, preinstalled on every Mac.

Each Image exposes name, size, aspectRatio, properties, getProperty, setProperty, scaledSizeWithLongestEdge and sizeToFitLongestEdge. Assigning to img.size does not resample — we tried, and the output came back at the original dimensions. Draw through a Canvas instead:

// resize.js — run with: sips -j resize.js --out out/ shots/*.jpg
var LONG_EDGE = 400

sips.images.forEach(function (img) {
  var fit = img.sizeToFitLongestEdge(LONG_EDGE)
  var canvas = new Canvas(fit.width, fit.height)
  canvas.drawImage(img, 0, 0, fit.width, fit.height)
  new Output(canvas, img.name).addToQueue()
})

Run against two inputs that produced two 400 × 211 files of 28,807 bytes each. For comparison, sips -Z 400 on the same source gave 25,971 bytes, so the Canvas path encodes slightly less efficiently. Where it earns its keep is the things no flag can do:

// watermark.js — run with: sips -j watermark.js --out out/ shots/*.jpg
sips.images.forEach(function (img) {
  var w = img.size.width, h = img.size.height
  var canvas = new Canvas(w, h)
  canvas.drawImage(img, 0, 0, w, h)
  canvas.fillStyle = '#ff0000'
  canvas.fillRect(0, h - 90, w, 90)
  canvas.font = '64px Helvetica'
  canvas.fillStyle = '#ffffff'
  canvas.fillText('DRAFT', 40, h - 28)
  new Output(canvas, img.name).addToQueue()
})

That runs and renders: 147,200 pixels changed against an unmarked control, the band measured pure red, and the text measured pure white inside it. textAlign works if you prefer to anchor from the right edge. Watch globalAlpha — a first attempt used a 65% white overlay with no backing band and changed only 738 pixels, technically drawn and practically invisible.

What else should I know before scripting sips?

Rotation and flipping are lossy. -r 90 followed by -r 270 returns the image to its original orientation and takes it from 157,425 to 248,432 bytes with different decoded pixels. Two flips came back at 231,358 bytes. There is no JPEG-lossless rotation path here; jpegtran -rotate is the tool for that.

sips -i adds 2.5 MB you cannot see. The --addIcon flag leaves the data fork at exactly 157,425 bytes, so ls -l and stat -f%z report no change, while du -k reports 2,656 KB. The icon lives in a com.apple.ResourceFork extended attribute of 2,557,417 bytes. It survives a Finder copy on APFS and is stripped by most uploads and archive tools, so you get invisible bloat and an inconsistent result.

sips -H is out of date. The built-in property help lists valid format values as jpeg | tiff | png | gif | jp2 | pict | bmp | qtif | psd | sgi | tga. HEIC and AVIF are missing from that list and both write correctly. Trust --formats over -H.

Use -g allxml when scripting. It emits a real property list you can pipe into plutil -extract instead of parsing the human-readable two-space output of -g all with awk.

# Long edge of an image, as a number, no text parsing
sips -g pixelWidth -g pixelHeight photo.jpg | awk '/pixel/ {print $2}' | sort -rn | head -1

# Or the structured route
sips -g allxml photo.jpg | plutil -extract pixelWidth raw -o - -

When sips is the right answer and when it is not

Use sips. It is installed, it is free, it needs no dependency management, it is stable across OS releases, and with xargs -P 8 it processes 100 large JPEGs in sixteen seconds. For resize, for format conversion, for reading dimensions in a script, for a Quick Action, for a Folder Action, for a build step on a Mac, it is the correct tool and we would rather you used it than paid us. Everything in building a Finder Quick Action and auto-compressing a watched folder works with nothing but sips and a text editor.

Reach for something else when:

You need metadata gone. sips carries coordinates through every operation tested. exiftool is the answer, covered in the Terminal metadata reference.

You need to write WebP. sips cannot. cwebp from Homebrew can, and so can a GUI that treats WebP as a first-class output.

You are running on Linux or in CI. sips is an Apple binary. ImageMagick and sharp travel.

You are compressing PDFs. sips reads PDF and writes a single-page PDF, which is almost never what you wanted. See batch compressing PDFs on a Mac for the Quartz filter, Ghostscript and Automator routes with measured timings.

The last 40% of bytes matters more than the wall clock. On the 100-file run above, at identical dimensions and identical nominal quality, Smol’s output was 39.6% smaller than sips’s, along with per-file results, a size guard that keeps the original when compression would make a file worse, and WebP and AVIF as real outputs. It is $29 once, and it is worth being clear that for a folder of holiday photos sips -Z 1600 is genuinely fine.

If you would rather not write the loop at all, Smol ships an MCP server, so an AI coding agent can plan the run, see the predicted output paths before anything is written, and report per-file results. Details on the Smol MCP page.

Frequently asked questions

What is the sips command on Mac?

sips is the scriptable image processing system, an Apple command-line tool at /usr/bin/sips that ships with every copy of macOS. It resizes, crops, pads, rotates, converts formats, reads and writes a small set of image properties, and applies ICC profiles. Nothing to install. Run sips --formats to see exactly which formats your OS version can read and write.

How do I resize an image with sips without distorting it?

Use sips -Z, with a capital Z, and one number: sips -Z 1600 photo.jpg --out small.jpg. That sets the longest edge and preserves the aspect ratio. Lowercase -z takes height then width and will squash the image. Be aware that -Z also upscales: on a 4,096 px source, sips -Z 6000 produced a 6,000 x 3,164 file, so check pixelWidth first if you only want to shrink.

What is the difference between sips formatOptions low, normal, high and best?

They are exact aliases. Encoding one image at each value and hashing the results with shasum showed low is byte-identical to 25, normal to 50, high to 75, and best to 100. Omitting formatOptions also produces the file identical to 75, so 75 is the default. best came out 3.25 times the size of high and 58% larger than the source JPEG.

Can sips convert to WebP?

No. sips reads WebP but cannot write it. Attempting sips -s format webp returns "Error: Can't write format: org.webmproject.webp", then "Error 13: an unknown error occurred", exits with code 13, and writes no file. The original is left untouched. AVIF and HEIC are both writable on macOS 27, and sips --formats lists the authoritative read and write sets for your OS.

Does sips remove EXIF or GPS data when it resizes an image?

No. On a tagged test file, coordinates, capture date, camera serial and XMP survived resizing to 800 px, re-encoding, rotating 90 degrees, and conversion to PNG, HEIC and AVIF. Only IPTC was lost, and only on a format change. Even sips -d leaves GPS in place. Use exiftool -all= if you need metadata actually gone.

Why is sips slow on a folder of images?

Because it is single-threaded. Resizing 100 copies of a 3.4 MB JPEG took 37.4 seconds as one sips call with 100 inputs and 36.4 seconds as a shell loop, on a 10-core M2 Pro that was mostly idle. Piping the files through xargs -0 -P 8 cut the same job to 16.3 seconds. Passing many files to a single sips invocation buys you nothing.

Keep reading