Warning
Beta software. jxleet is under active development: the interface, presets and
config format may change between releases, and there will be bugs. It is careful
with your files — nothing is ever hard-deleted, and replace verifies before
recycling — but keep your originals until you trust it.
Every byte of every
.jxlfile this tool produces is written bycjxlfrom the libjxl project. The format, the encoder, the codec research and all of the genuinely hard work are theirs. jxleet is a front end: it decides which files to hand over, assembles the arguments, runs the process, and shows you what came back.If you are comfortable on the command line, use
cjxldirectly — you lose nothing. jxleet exists for the parts that get tedious at scale: batching, keeping the binaries current, plugging into Lightroom, and not having to remember which of your files are still recoverable.
I think JPEG XL is the image format of the future, and I wanted a single tool that gives
me full control over every aspect of it — the whole cjxl flag surface, nothing wrapped
away — in all three places my images arrive: a GUI to drop files into, an
Explorer context menu for the quick right-click, and the command line, which is
how Lightroom hands over its exports.
One question turned out to matter more than any setting: is this conversion reversible?
Hand cjxl a JPEG and it will, by default, repack it losslessly — djxl can reconstruct
the original JPEG byte for byte, and you have saved about 20 %. Pass --lossless_jpeg=0
and it decodes and re-encodes instead, giving you something much smaller that can never be
turned back. Those are entirely different operations behind one flag.
jxleet treats them as different routes, colours them differently, counts them separately, and will not delete an original until it has decoded the result and proven it readable.
- jxleet
Every file takes one of three routes. The route follows from the input format times your preset — a JPEG can take either of the first two, and which one it takes is your decision, not a property of the file.
| Route | Input | What happens | Reversible |
|---|---|---|---|
| 🟢 Transcode | JPEG | Repacked losslessly into a JXL container | Yes — djxl restores the original JPEG byte for byte |
| 🟠 Reencode | JPEG, JXL | Decoded and encoded again with your settings | No |
| 🔵 Encode | PNG, GIF, EXR, NetPBM, PFM, PGX | Encoded from pixels, lossless at distance 0 | Only at distance 0 |
The colours are used everywhere: on the drop summary, on every row of the file list, and on the convert button itself, which takes the colour of whichever route dominates your batch. Change the rule that governs JPEG and the counts, badges and size estimates all move at once.
Download the latest release, unzip, run jxleet.exe. No installer, nothing written outside
your user profile.
On first start jxleet notices that the libjxl tools are missing and offers to fetch them. Accept, pick a preset for each of the three entry points, and you are ready.
Windows 10 or 11, 64-bit. Nothing else — the libjxl binaries are downloaded and managed by the application.
Each of the three requires a preset. Not a default that jxleet picks for you — one you
choose, once, per entry point. A tool that can replace files should never guess at your
intent. On first start the read-only default-gui, default-cli, and
default-explorer-context presets are bound to their respective entry points;
jxleet refuses to run only when a binding is missing or invalid.
Drag files or folders in - native Windows drag-and-drop accepts files or folders in every view, and the toolbar keeps separate Open File and Open Folder actions always available. A selected folder contributes only regular files directly inside it; subfolders are not traversed, and anything unsupported is skipped rather than aborting the batch. If no preset is selected, the files stay in the intake and the window explains that a preset is needed for route classification.
Settings split into Main - the essentials - and Expert, which exposes the full
generated cjxl flag surface; every flag includes its help text as a tooltip. The selected
preset seeds the controls: switching presets re-resolves everything immediately, and the
preset strip under the toolbar spells out each rule - the catch-all and every format with
its own settings. Edits in Main/Expert are session-only overrides; the strip then warns
in amber ("settings differ — preset not in effect") and a Revert button restores the
preset values. Persisting changes happens in the preset YAML itself (Presets → Open in
Editor). The live preview shows the exact command line that will run.
The Main view is intake only: files list flat in the order they were added, each
with its route badge and the resolved settings it will be staged with (e.g.
D 1.00 (Q 90) · E 7, with a +flags chip when extra cjxl flags apply); Effort
is a simple slider alongside Distance/Quality. Nothing converts here — Move to
queue stages each listed file with a frozen copy of the current settings and starts
the run, so
later preset edits never touch staged items and the same file may be queued twice
with different settings for comparison.
The Queue view runs the staged items back to back, one cjxl invocation per
file with its frozen settings. Global Start, Pause and Cancel sit above the table;
every item spans two rows: file, sizes, saving, route and the status (waiting, done
with needed time, or the failure reason) on top, the child PID with its CPU usage and
RAM (updated every second) plus the actions below — buttons never wrap. Right-click a row to remove
it, reclaim it back to Main (restoring its snapshot to the session settings), show source
or JXL in Explorer, open the converted file, clear done or all rows, or cancel just that
file. Successful conversions are recorded to
History with their needed time; failed, cancelled and skipped rows stay in the Queue
for retry and never reach History. The queue is session-only: closing the window with
pending items warns once and discards them on confirm.
After a conversion, click a finished file to run jxlinfo -v for its JXL output and show the
detailed metadata. Failed or skipped results without a JXL output report why metadata is
unavailable.
jxleet.exe "C:\photos\shoot\DSC_0001.jpg" "C:\photos\shoot\DSC_0002.jpg"
jxleet.exe --preset "web-d15-e7" "C:\photos\shoot\"Any number of paths, files or folders, in any mix. With paths given, jxleet uses the preset
bound to the command-line entry point and never shows a settings dialog. --preset
overrides that binding for a single call.
jxleet.exe --help prints the command-line options and jxleet.exe --version prints the
application version. The per-user Explorer entry can be registered or removed with
--register-context-menu and --unregister-context-menu; registration uses the preset bound
to the context-menu entry point.
An unknown preset name aborts with a non-zero exit code rather than falling back to something plausible. A typo in an automated pipeline should not quietly convert a hundred images with the wrong settings.
Running many instances at once is expected. Lightroom does not call an external tool once with a hundred paths; it launches several processes in parallel, each carrying a handful. jxleet is built for that: the first process to arrive takes ownership, every subsequent one hands over its paths through a named pipe and exits within milliseconds, and the arriving batches are coalesced into a single run. You get one window and one progress bar, not twenty — and the calling application is never left waiting. A batch that arrives after the previous run has finished starts a new run on its own, and while a run is in progress each completed row shows the output size and its saving ratio.
Right-click any file, folder, or folder background. Registration is per-user and needs no administrator rights.
The entry carries the name of the preset it will use — To JXL — archive-lossless — because a menu item that can replace your files should say what it does before you click it.
On Windows 11 the entry lives under Show more options. Reaching the primary context menu requires a packaged app with a COM handler, which jxleet does not ship.
A preset is a YAML file. It pairs file filters with cjxl arguments — and those
arguments are passed through verbatim. jxleet does not invent a settings vocabulary that
wraps the encoder; the preset is the argument list.
On first start jxleet creates three read-only presets — default-gui,
default-cli, and default-explorer-context — and binds each to its corresponding entry
point. Duplicate one to create a writable preset; the built-in defaults cannot be renamed,
deleted, or overwritten.
The selected preset supplies the file rules used for classification. Basic and Expert controls are temporary run overrides and do not yet mirror every value in a multi-rule preset; the Presets view shows those format-specific rules.
Presets are edited by changing the YAML files directly. The Preset library has an
open-folder button and a Reload button so you can edit a file and pull the change back
in without restarting. Every preset jxleet writes carries a # yaml-language-server modeline
pointing at a committed preset.schema.json (kept next to your presets), so a schema-aware
editor validates and autocompletes your edits. Keep a trailing "*" rule as the catch-all;
the built-in defaults and new presets already include one.
# %APPDATA%\jxleet\presets\archive-lossless.yaml
name: archive-lossless
description: Keep JPEGs recoverable, everything else mathematically lossless
version: 1
output:
policy: alongside # alongside | subfolder | replace
subfolder: jxl
on_collision: skip # skip | number | overwrite
embed_settings: false # true → photo.d1.00-e7-cjxl0.11.1.jxl instead of photo.jxl
jxlinfo_sidecar: false # true → <output>.jxlinfo.txt next to the converted file
rules:
# JPEG stays recoverable — the original can be reconstructed from the result
- match: [JPEG]
args:
"--lossless_jpeg": 1
# Pixel formats: mathematically lossless, high effort, it is an archive
- match: [PNG, APNG, GIF, PPM, PGM, PAM, PFM, PGX, EXR]
args:
"-d": 0
"-e": 9
"--num_threads": 8
# Anything else that cjxl accepts
- match: ["*"]
args:
"-d": 1.0
"-e": 7Rules are evaluated top to bottom and the first match wins. A "*" rule at the end acts
as the fallback; without one, unmatched files are skipped and reported.
match takes format names as listed under Supported formats, or "*".
args are keys and values exactly as cjxl expects them. Both spellings work, because both
are cjxl's:
"-j": 0 # short form
"--lossless_jpeg": 0 # long form — same flagFlags that take no value are written as true:
"--progressive": truename: web-d15-e7
description: Small files for the web, originals replaced
version: 1
output:
policy: replace
on_collision: overwrite
rules:
# Deliberately lossy, including for JPEG — not reversible
- match: ["*"]
args:
"--lossless_jpeg": 0
"-d": 1.5
"-e": 7Note what --lossless_jpeg: 0 does here: it moves every JPEG from the 🟢 transcode route to
the 🟠 reencode route. Combined with policy: replace, the originals become unrecoverable.
jxleet will say so, name the number of files, and ask once before starting.
Keys are checked against cjxl -v -v --help of the installed version before a run
starts. An unknown flag stops the run with a non-zero exit code rather than passing it to cjxl and hoping for the best.
Presets are plain files. Copy them, commit them, send them. Importing one through the
interface or by dropping it into %APPDATA%\jxleet\presets\ makes it available immediately.
Export from Lightroom losslessly to JPEG XL, then hand the result to jxleet to apply my own compression settings: I develop the photos in Lightroom, export them losslessly as JXL, and re-encode them in this second step with full control over the compression settings.
- In the Export dialog, set After Export → Open in Other Application and select
jxleet.exe. - In jxleet, bind the preset you want under Presets → Command line.
The parallelism is handled for you, as described above. Remaining time is estimated from measured throughput over recent files rather than a fixed guess, so it adapts when file sizes or effort change mid-run.
Worth knowing: re-encoding an already-lossy image quantises it a second time. If your Lightroom export is lossless JXL then this is your first lossy step and all is well. If you point jxleet at JXL files that were already lossy, it cannot tell.
jxleet is an expert tool and does not hide these. All three are cjxl concepts; what
follows is a summary, and cjxl -v -v -v -v --help remains the authority.
Distance (-d) is JPEG XL's quality measure in JND units, from 0.0 to 25.0.
0 is mathematically lossless. Useful lossy values sit between 0.5 and 3.0, with
1.0 visually indistinguishable for most photographic material.
Quality (-q) on a 0–100 scale is the same quantity in different clothing. Both sliders
are always shown side by side and linked: moving one moves the other. The stored value is
always the distance — a run always uses -d — so you cannot end up with two settings that
disagree. 90 is visually lossless; the recommended range is 68 to 96.
The sliders cover distance 0.0..5.0 (quality 100..46). At the limit the status hints
that anything beyond that range means using the cjxl command line directly.
The sliders color their bands by distance: below 0.5 purple, 0.5..1.0 dark green,
1.0..1.5 green, 1.5..2.0 yellow, 2.0..3.0 orange, beyond 3.0 red. At a distance of
1.0 or lower the status hint reads visually lossless.
Effort (-e) trades encoding time for file size, from 1 (lightning) to 10
(glacier), default 7 (squirrel). The number alone tells you nothing useful, so the expert
view shows an effort ladder: a grid of coding tools against the ten levels, lighting up
as you drag. Tools that come in several strengths are colour-graded — orange, yellow,
green, then blue for the strongest form; the steps mark changes in the tool's
implementation, such as more Butteraugli iterations or a more exhaustive search, not just
on/off. Rows that are a plain limitation (like 8×8 blocks only at the low efforts) stay
yellow. Every level cell has a tooltip saying
what exactly that effort does. Tools that do not apply to the mode you are in stay visible,
struck through and faded to half opacity, so you can see what the other mode would buy you.
What each effort level actually enables is documented upstream:
libjxl — encode effort.
The ladder follows that document; the ladder data, its provenance and the update procedure
are documented in frontend/src/lib/effort.md.
| Policy | Behaviour |
|---|---|
alongside |
Result next to the original. Default, and always the default for imported presets. |
subfolder |
Result in ./jxl/ relative to the source, name configurable. |
replace |
Result takes the original's place; the original goes to the recycle bin. |
replace follows a fixed order that cannot be short-circuited: write to a temporary file in
the target directory, decode it to prove it is readable — and on the transcode route
also confirm the reconstructed JPEG is byte-identical — then rename into place, and only
then move the original to the recycle bin. Fail at any step and your original is still
sitting there untouched.
The recycle bin is not optional. jxleet never deletes a file outright, and on volumes without one (network shares, some removable media) it refuses to replace rather than falling back to deletion.
When the target .jxl already exists, the preset's on_collision decides: skip (the safe
default), number a new name, overwrite silently. Under skip, the GUI asks instead of
skipping silently: overwrite this file, overwrite all, rename this file, rename all, skip
this file, or skip all — rename keeps the existing file and writes a numbered sibling
(photo (1).jxl); presets configured for number or overwrite never prompt.
With output.embed_settings: true, the distance, effort and used cjxl version are appended
to the output name: photo.d1.00-e7-cjxl0.11.1.jxl instead of photo.jxl. Distance always
keeps two decimals so suffixed files sort in numeric order; the transcode route reports
d0.00 with the rule's effort. The Main view has a matching checkbox (a session-only
override, like the output policy), and the preset value can be changed in the Presets view
or directly in the YAML file.
With output.jxlinfo_sidecar: true, the verbose jxlinfo -v output for each converted
file is written next to it as <output>.jxlinfo.txt — e.g.
photo.d1.00-e7-cjxl0.11.1.jxl.jxlinfo.txt — always overwriting an existing sidecar.
A failed sidecar keeps the conversion successful and is reported as a warning on that
file's result row. Like the filename setting above, the Main view has a session-only
checkbox and the Presets view persists the preset value.
jxleet does not bundle libjxl. It manages it.
Installed versions of cjxl, djxl and jxlinfo are shown on every start and compared
against the latest libjxl release. The binaries are downloaded and saved under %LOCALAPPDATA%\jxleet\bin\.
jxleet also watches its own releases: on start it silently checks the GitHub releases through the built-in Wails updater and shows a dismissable warning banner when a newer version exists. Notify-only, like the toolchain — the banner's Update button opens the updater window with the release notes and installs only on your confirmation; a manual check lives in the Tools view. Downloading and replacing the binary stays your call.
Every successful conversion is recorded in %APPDATA%\jxleet\history.jsonl — one JSON
line per file, append-only, and tolerant of a torn last line, so a crash mid-write costs
at most the last entry, never the whole file. The History view lists the entries newest
first with input and output paths, both sizes, the saving, the route, the preset and the
needed time, plus the timestamp.
Click an entry to run jxlinfo -v on the stored output and inspect what ended up in the
file; if the output has since been moved or deleted, the entry says so instead of failing
silently. A Reload action picks up new entries, and Clear — behind a confirmation —
empties the history.
In: JPEG, PNG, APNG, GIF, OpenEXR, NetPBM (.pam, .pgm, .ppm), PFM, PGX, JXL
Out: JXL
These are exactly the formats cjxl accepts — jxleet adds none and removes none. Notably
absent:
- TIFF — not supported by
cjxl. Export PNG from your editor instead. - RAW (DNG, CR3, NEF, …) — out of scope. Develop first, then convert.
%APPDATA%\jxleet\config.yaml settings and the three entry-point bindings
%APPDATA%\jxleet\presets\ one YAML file per preset
%APPDATA%\jxleet\history.jsonl one JSON line per successful conversion (History view)
%LOCALAPPDATA%\jxleet\bin\ the managed libjxl binaries
%LOCALAPPDATA%\jxleet\logs\ run logs
Uninstalling means deleting jxleet.exe, removing the context menu entry from within the
app, and deleting those two folders.
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
git clone https://github.com/dhcgn/jxleet
cd jxleet
wails3 dev # development build with hot reload
wails3 build # release build
task check # build, vet, lint, race testsRequires Go 1.27+ and cjxl on PATH for the integration tests.
Tests generate their own image fixtures at runtime; there are no binaries in the repository.
Why not just use cjxl directly?
Do, if that suits you. jxleet adds no encoding capability whatsoever. It adds batching,
preset management, toolchain updates, Lightroom integration and a safety net around
replacing originals.
Can I get my JPEG back?
On the transcode route, exactly: djxl photo.jxl photo.jpg reproduces the original byte for
byte. On the other routes, no.
Why is my JXL bigger than the original? Usually effort set too low, or an input that was already aggressively compressed. Try effort 7 or higher and check the size column in the results view.
Does it strip my metadata?
No. Exif and XMP are carried across by cjxl, and the result view shows what ended up in
the file. But I recommend you check your own workflow.
Can one preset handle a mixed folder?
That is what the rules list is for. Put a [JPEG] rule first, pixel formats second, "*"
last, and one preset covers a folder of anything.
Does JPEG XL open anywhere yet? Support is getting more common, but it is not universal. If you need to share images with people who cannot open JPEG XL yet, consider exporting to a more widely supported format like JPEG.
My JXL images look bad in the web browser — why?
For the best experience browsing JPEG XL images, make sure your browser has JPEG XL decoding
enabled (a feature flag in most browsers — see the tutorials at
jpegxl.io). Without native decoding, a gallery or viewer
may fall back to a re-encoded JPEG copy, which looks worse than the actual .jxl file.
MIT. See LICENSE.
Built on libjxl (BSD-3-Clause) and Wails (MIT). jxleet is an independent front end and is not affiliated with the libjxl project, the JPEG committee, or Adobe.






