Pre-generate Directus image transform ("preview") cache files so the admin UI and API never pay the first-request sharp cost.
Find a file
Rawman Grey 18db52a47c fix: count pre-existing preview files as cached in backfill
Pre-scan storage filenames and check each task's predicted cache file
before generating. Existing previews are now counted as "already cached"
and skipped, instead of being counted as remaining work and regenerated.
Storage-scan and plan-computation failures fall back to generation.
2026-10-25 13:52:28 +03:00
dev chore: prepare for public release 2026-10-25 12:25:19 +03:00
src fix: count pre-existing preview files as cached in backfill 2026-10-25 13:52:28 +03:00
.gitignore chore: prepare for public release 2026-10-25 12:25:19 +03:00
AGENTS.md chore: prepare for public release 2026-10-25 12:25:19 +03:00
LICENSE chore: add MIT license and AI-generated disclosure 2026-10-25 12:28:09 +03:00
package-lock.json feat: pre-generate Directus image previews 2026-10-25 11:19:54 +03:00
package.json chore: add MIT license and AI-generated disclosure 2026-10-25 12:28:09 +03:00
README.md chore: add MIT license and AI-generated disclosure 2026-10-25 12:28:09 +03:00
SECURITY.md chore: prepare for public release 2026-10-25 12:25:19 +03:00
tsconfig.json feat: pre-generate Directus image previews 2026-10-25 11:19:54 +03:00

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 now warms 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.

AI-generated. This project was produced by an autonomous AI coding agent and reviewed by a human maintainer. Review and test it before relying on it in production.

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 use format:'auto', so they generate one file per entry in formats (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) ignores formats and generates exactly that format. A custom preset with format:'auto' follows formats.
  • A custom preset whose key equals 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 and GET /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 dates
  • GET /image-previews/config, PUT /image-previews/config
  • GET /image-previews/presets — system + custom presets, effective formats, shadowed keys
  • POST /image-previews/run — start backfill ({"ignoreWindow":true} for an immediate run)
  • POST /image-previews/run/cancel
  • GET /image-previews/disk-usage — previews vs originals vs total
  • POST /image-previews/generate — one file ({fileId, keys?, formats?, dryRun?})
  • GET /image-previews/resolve-plan?fileId=&key= — predicted cache filename, no write
  • GET /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. See dev/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 previous dist/.
  • Helpers: dev/deploy-staging.sh, dev/rollback-staging.sh, template dev/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.

License

MIT — see LICENSE.