- add repository/homepage/bugs metadata; target Directus ^11 - genericize internal deployment references - document install, compatibility, scheduling, security - stop tracking internal .opencode work artifacts
7.6 KiB
directus-extension-generate-image-previews
Pre-generate Directus image transform ("preview") cache files so the admin UI and API never pay the first-request sharp cost.
A Directus bundle:
- upload hook — warm previews right after an image upload.
- new-preset trigger —
Run nowwarms every preset (system + custom) for existing images. - backfill job — resumable, window-aware sweep over the existing library.
- admin module — progress, storage breakdown, configuration, diagnostics, and an API reference.
Requirements
- Directus 11.x (tested on 11.17.4).
- Node 18+.
- A non-sandboxed local extension (it uses Directus's internal services at runtime — see Compatibility).
- Local disk storage is supported out of the box; other storage drivers work for disk reporting through the Directus storage manager.
Why this approach
The cache file is <filename_disk>__<object-hash(resolved transforms)>.<ext>. The hash depends on the
RESOLVED transforms, and every system-* preset is format:'auto' — which resolves differently
server-side (jpg/png) than in a browser (avif). This extension therefore generates in-process with an
explicit acceptFormat (avif + webp), reproducing the browser's cache file byte-for-byte without any
HTTP request. Naive warming (and several community extensions) silently misses because of this.
Install
git clone https://code.at.locus.im/Rawman/directus-extension-generate-image-previews.git
cd directus-extension-generate-image-previews
npm install
npm run build
Copy the build into your Directus extensions directory:
cp -r dist package.json /path/to/directus/extensions/directus-extension-generate-image-previews/
Restart Directus (or let EXTENSIONS_AUTO_RELOAD pick it up). The Image Previews module appears in
the admin sidebar.
Development
npm run build # emits dist/app.js + dist/api.js
npm run typecheck # 0 errors
npm test # unit tests
npm run dev # watch build
npm run link # link into a local Directus extensions dir
An isolated Directus 11.17.4 stack for smoke testing lives in dev/ — see dev/README.md.
Settings
Defaults are applied automatically; everything is editable at runtime from the admin module — no rebuild.
| setting | default | meaning |
|---|---|---|
enabled |
true |
master switch (off = hooks/backfill inert) |
onUpload |
true |
generate previews immediately after an upload |
windowStart / windowEnd |
01:00 / 04:00 |
scheduled runs only start inside this window (local time) |
concurrency |
1 |
parallel generations |
formats |
avif,webp |
output variants for format:'auto' presets only (all system presets) |
presetKeys |
null (all) |
restrict to specific preset keys |
debug |
false |
verbose generation logs in the Directus server log |
IMAGE_PREVIEWS_STATE_DIR |
<STORAGE_LOCAL_ROOT>/.image-previews |
where settings + progress state live |
Presets and formats
- System presets (
system-small|medium|large×cover|contain) always useformat:'auto', so they generate one file per entry informats(default avif + webp), at their built-in sizes (64 / 300 / 800 px). - Custom presets from Settings → File Storage are added automatically. A custom preset with a fixed
format(e.g.png) ignoresformatsand generates exactly that format. A custom preset withformat:'auto'followsformats. - A custom preset whose
keyequals a system key is shadowed (system wins, same as Directus) and is flagged in the admin module. - Unsupported format strings (e.g.
bmp) fail at generation; the error is shown in Recent operations andGET /image-previews/ops.
Storage reporting
GET /image-previews/disk-usage (and the admin panel) reports bytes + file counts separately for
preview assets and original assets, plus the total — in both raw bytes and human-readable form.
API endpoints
All require a Directus admin session or admin token. Human-readable fields accompany machine-readable ones.
GET /image-previews/status— run progress, rate, ETA, window state, ISO + human datesGET /image-previews/config,PUT /image-previews/configGET /image-previews/presets— system + custom presets, effective formats, shadowed keysPOST /image-previews/run— start backfill ({"ignoreWindow":true}for an immediate run)POST /image-previews/run/cancelGET /image-previews/disk-usage— previews vs originals vs totalPOST /image-previews/generate— one file ({fileId, keys?, formats?, dryRun?})GET /image-previews/resolve-plan?fileId=&key=— predicted cache filename, no writeGET /image-previews/ops?limit=— recent operations with timing + error messages
The admin module includes a full API reference with curl examples.
Scheduling
Scheduled runs are window-bound; an external cron can trigger them:
0 * * * * curl -fsS -X POST http://localhost:8055/image-previews/run \
-H 'Authorization: Bearer <admin-token>' -H 'Content-Type: application/json' -d '{}'
Run now in the admin module ignores the window.
Safe testing & deployment
Never test against a production instance.
- Local / staging (
dev/compose.dev.yml): Directus 11.17.4 + its own postgres + volumes, on separate ports. Shares nothing with production. Seedev/README.md. - Fail-open: entrypoints never throw into Directus core.
- Rollout: build → verify on staging → back up the previous
dist/→ deploy → healthcheck → enable. Rollback: restore the previousdist/. - Helpers:
dev/deploy-staging.sh,dev/rollback-staging.sh, templatedev/compose.staging.yml.
Caveats
- Transform dimension guard, not a file-size limit. Directus rejects transformations of images whose
width or height exceeds
ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION(default 6000 px). This is a Directus env var, not something this extension imposes. To process larger images, raise it on the Directus instance (and watch memory). Upload size limits (FILES_MAX_UPLOAD_SIZE,MAX_PAYLOAD_SIZE) are separate. - Warm only after uploads are committed (local storage writes are non-atomic).
- In-process generation bypasses the HTTP rate limiter; bound it with
concurrency+ the window. - PM2 cluster: shared state is file-backed, so all workers see the same run state.
Compatibility
Tested against Directus 11.17.4. The extension reads Directus's own transform pipeline and storage
manager (@directus/api/utils/transformations, @directus/api/storage/index) via runtime imports; these
are not a public API and may change between Directus versions. Re-verify after a Directus upgrade,
especially the hash-match gate (resolve-plan vs a real browser asset request).
The extension is not sandboxed: Directus's sandbox forbids importing anything except directus:api, which
this extension needs for storage/transform access.