TinyJPG is a C++23 image optimizer built for fast, repeatable asset pipelines. It compresses JPEG, PNG, WebP, AVIF, and JPEG XL files in-process, generates responsive variants, scans folders, watches upload directories, and reports results as text, tables, or JSON.
The executable is available as both tinyjpg and the short alias tj.
- Preview
- Why TinyJPG
- Install
- Quick Start
- Commands
- Configuration
- Services
- Release Assets
- Build From Source
| Before | After |
|---|---|
| 1.5 MB JPEG | 243 KB JPEG |
This sample keeps the original 2236 x 1792 dimensions and reduces the file by about 84%.
- Native C++23 command line tool with no shell-outs for image conversion.
- Codec coverage for JPEG, PNG, WebP, AVIF, and JPEG XL workflows.
- One-shot
run, recursivescan, and continuouswatchmodes. - Built-in responsive presets plus TOML configuration for custom variants.
- Atomic writes, dry-run planning, skip-if-not-smaller behavior, and structured output for CI and automation.
- Cross-platform CMake and vcpkg build on Linux, macOS, and Windows.
Linux and macOS:
curl -fsSL https://tj.eorlov.org/install.sh | shWindows PowerShell:
irm https://tj.eorlov.org/install.ps1 | iexHomebrew on Apple Silicon macOS:
brew tap OrlovEvgeny/tinyjpg
brew install tinyjpgInstall a specific version:
curl -fsSL https://tj.eorlov.org/install.sh | sh -s -- --version 1.0.1irm https://tj.eorlov.org/install.ps1 -OutFile install.ps1
./install.ps1 -Version 1.0.1The installers read the release manifest, pick the correct archive for the
current platform, verify SHA256, and install tinyjpg plus the tj alias.
Run a safe dry-run first:
tj scan ./images --preset web --dry-run --format tableGenerate optimized variants:
tj scan ./images --preset web --format tableCreate a config file for repeatable jobs:
tj config print --defaults > tinyjpg.toml
tj config validate tinyjpg.toml
tj scan ./images --config tinyjpg.tomlWatch a directory continuously:
tj watch ./uploads --config tinyjpg.toml| Command | Purpose | Example |
|---|---|---|
run |
Optimize explicit files. | tj run hero.jpg banner.png --preset web |
scan |
Optimize supported images under files or directories. | tj scan ./public/images --config tinyjpg.toml |
watch |
Repeatedly scan paths and process changed images. | tj watch ./uploads --config tinyjpg.toml |
presets list |
Show built-in responsive presets. | tj presets list --format table |
config print |
Print a default TOML config. | tj config print --defaults |
config validate |
Validate a TOML config before automation. | tj config validate tinyjpg.toml |
doctor |
Check local runtime basics. | tj doctor --format table |
completion |
Print shell completion for bash, zsh, or fish. | tj completion zsh > _tj |
Common options for run, scan, and watch:
| Option | Meaning |
|---|---|
--config, -c |
TOML configuration file. |
--preset |
Replace configured variants with web, ecommerce, or avatar. |
--dry-run |
Plan work without writing files. |
--quiet, -q |
Suppress per-file text output. |
--format |
Output as text, table, or json. |
Output formats are intended for different uses: text for humans, table for
inspection, and json for CI or other automation.
Generate and validate a config:
tj config print --defaults > tinyjpg.toml
tj config validate tinyjpg.tomlTinyJPG starts from built-in defaults and then applies values from your TOML
file. CLI flags such as --preset and --dry-run override the loaded config for
that invocation.
This config is suitable for a service-style image pipeline. It watches an input directory, writes generated files into a separate output directory, and creates three web delivery variants.
[general]
workers = 0
log_level = "info"
queue_capacity = 512
stable_wait_ms = 400
dry_run = false
[watch]
paths = ["/var/lib/tinyjpg/inbox"]
recursive = true
include = ["*.jpg", "*.jpeg", "*.png", "*.webp"]
exclude = ["**/.cache/**", "*.tmp"]
prefix = []
[compress]
mode = "visually_lossless"
effort = "balanced"
keep_metadata = false
skip_if_not_smaller = true
preserve_original = true
[[variant]]
name = "large"
codec = "auto"
max_width = 1920
suffix = "-large"
[[variant]]
name = "card"
codec = "webp"
mode = "lossy"
max_width = 960
quality = 82
suffix = "-card"
[[variant]]
name = "thumb"
codec = "auto"
max_width = 320
max_height = 320
quality = 76
fit = "cover"
suffix = "-thumb"
[output]
directory = "/var/lib/tinyjpg/output"
pattern = "{stem}{suffix}.{ext}"
on_exist = "version"For /var/lib/tinyjpg/inbox/hero.jpg, this can produce:
/var/lib/tinyjpg/output/hero-large.jpg
/var/lib/tinyjpg/output/hero-card.webp
/var/lib/tinyjpg/output/hero-thumb.jpg
| Setting | Values | Meaning |
|---|---|---|
workers |
0 or positive integer |
Worker threads. 0 uses hardware concurrency. |
log_level |
trace, debug, info, warn, error |
Runtime verbosity. |
queue_capacity |
positive integer | Maximum queued work items before producers wait. |
stable_wait_ms |
0 or positive integer |
Watch-mode delay before reading a changed file, so uploads can finish. |
dry_run |
true, false |
Plan work without writing files. Can also be set with --dry-run. |
| Setting | Values | Meaning |
|---|---|---|
paths |
array of paths | Default paths for tj watch when no CLI path is passed. |
recursive |
true, false |
Parsed from TOML; directory scanning currently walks recursively. |
include |
glob array | Parsed from TOML; current processing still selects files by supported image codec. |
exclude |
glob array | Parsed from TOML; generated TinyJPG variants are skipped automatically. |
prefix |
string array | Parsed from TOML and reserved for path-prefix filtering. |
| Setting | Values | Meaning |
|---|---|---|
mode |
lossless, visually_lossless, lossy |
Default fidelity target for variants. |
effort |
fast, balanced, max |
Encoder effort/speed preference. |
keep_metadata |
true, false |
Keep image metadata when possible. |
skip_if_not_smaller |
true, false |
Skip outputs that would be larger than the source. |
preserve_original |
true, false |
Avoid overwriting the source path; an empty suffix becomes -optimized. |
Each variant describes one output image. At least one variant is required.
Variant names may contain letters, digits, _, and -.
| Setting | Values | Meaning |
|---|---|---|
name |
string | Variant name shown in output and available as {name}. |
codec |
auto, jpeg, png, webp, avif, jxl |
Output codec. auto keeps the source codec. |
mode |
same as [compress].mode |
Optional per-variant fidelity override. |
quality |
1 to 100 |
Encoder quality. If omitted, TinyJPG uses 82. |
max_width |
positive integer | Maximum output width. |
max_height |
positive integer | Maximum output height. |
fit |
contain, cover, fill |
Resize behavior when both dimensions are set. |
suffix |
string | Added to output file names, for example -thumb. |
Every non-original variant must set max_width, max_height, or both. The
special variant name original is allowed without size constraints.
| Setting | Values | Meaning |
|---|---|---|
directory |
path or empty string | Output directory. Empty means next to the input file. |
pattern |
string with tokens | Output path template. |
on_exist |
skip, overwrite, version |
Behavior when the output path already exists. |
Supported pattern tokens:
| Token | Value |
|---|---|
{dir} |
Input directory or configured output directory. |
{stem} |
Input file name without extension. |
{suffix} |
Variant suffix. |
{ext} |
Output extension for the chosen codec. |
{name} |
Variant name. |
{codec} |
Output codec name. |
{width} |
Planned output width. |
{height} |
Planned output height. |
Useful patterns:
pattern = "{dir}/{stem}{suffix}.{ext}"
pattern = "{stem}-{width}w.{ext}"
pattern = "{codec}/{stem}-{name}.{ext}"Use presets when you do not need custom variants:
tj scan ./images --preset web
tj scan ./products --preset ecommerce
tj scan ./avatars --preset avatar| Preset | Variants |
|---|---|
web |
original, large 1920w, medium 1024w, thumb 320x320 cover |
ecommerce |
original, hero 1600w, listing 900w, thumb 320w |
avatar |
original, full 512x512 cover, thumb 128x128 cover |
Release archives include service helpers for long-running optimization:
- systemd unit, sysusers, and tmpfiles snippets for Linux.
- launchd plist template for macOS.
- PowerShell install and uninstall scripts for Windows services.
The generated Linux unit runs:
tinyjpg watch --config /etc/tinyjpg/tinyjpg.toml
It uses a dedicated tinyjpg user, protects the host filesystem, and only grants
write access to /var/lib/tinyjpg and /var/log/tinyjpg. Keep watched input and
output directories under /var/lib/tinyjpg, or add a systemd override with extra
ReadWritePaths=.
Example setup:
sudo systemd-sysusers packaging/systemd/tinyjpg.sysusers
sudo systemd-tmpfiles --create packaging/systemd/tinyjpg.tmpfiles
sudo install -d -m 0750 -o root -g root /etc/tinyjpg
sudo install -d -m 0750 -o tinyjpg -g tinyjpg /var/lib/tinyjpg/inbox
sudo install -d -m 0750 -o tinyjpg -g tinyjpg /var/lib/tinyjpg/output
sudo install -m 0640 -o root -g tinyjpg tinyjpg.toml /etc/tinyjpg/tinyjpg.toml
sudo install -m 0644 build/packaging/systemd/tinyjpg.service /etc/systemd/system/tinyjpg.service
sudo systemctl daemon-reload
sudo systemctl enable --now tinyjpg.serviceCheck service health and logs:
systemctl status tinyjpg.service
journalctl -u tinyjpg.service -fIf your images live outside /var/lib/tinyjpg, add an override:
sudo systemctl edit tinyjpg.service[Service]
ReadWritePaths=/srv/uploads /srv/images /var/lib/tinyjpg /var/log/tinyjpgThen reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart tinyjpg.serviceTagged releases publish platform archives to Cloudflare R2 and create a GitHub Release with changelog and install instructions.
| Platform | Archive |
|---|---|
| Linux x86_64 | tinyjpg-{version}-linux-x86_64.tar.gz |
| Linux aarch64 | tinyjpg-{version}-linux-aarch64.tar.gz |
| macOS arm64 | tinyjpg-{version}-macos-arm64.tar.gz |
| Windows x86_64 | tinyjpg-{version}-windows-x86_64.zip |
The public manifest for installers and future self-update support is:
https://tinyjpg.eorlov.org/tinyjpg/manifest.json
Requirements:
- CMake 3.28 or newer.
- Ninja.
- A C++23 compiler.
- vcpkg.
nasmon Linux for codec dependencies.
Configure, build, test, and install:
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DBUILD_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failure
cmake --install build --prefix ~/.localCreate local CPack archives:
cpack --config build/CPackConfig.cmake