This guide documents image_plug's conformance to the IIIF Image API 3.0 specification — what we implement, what we deliberately don't, and the Compliance Level we target.
The reference is the IIIF Image API 3.0 specification and the IIIF Image API Validator. When this guide and the IIIF spec disagree, treat the spec as the contract and file an issue against image_plug.
Compliance level
image_plug's IIIF provider targets Compliance Level 2. Level 2 is the level most production servers (Cantaloupe, Loris, IIPImage) implement; it covers the full set of region / size / rotation / quality / format combinations that real IIIF clients (Universal Viewer, Mirador, OpenSeadragon) need.
We do not currently target Level 3 (which adds more granular size syntax and per-feature negotiation).
URL forms recognised
| Form | IIIF | image_plug | Notes |
|---|---|---|---|
<prefix>/<id>/<region>/<size>/<rotation>/<quality>.<format> | ✅ | ✅ | The standard image-request URL. All five segments required. |
<prefix>/<id>/info.json | ✅ | ✅ | The Image Information document. Returned as application/ld+json. |
<prefix>/<id> (bare identifier) | ✅ (303 redirect to info.json) | ⚠️ | Currently rejected with :malformed_url. Roadmap: add the 303. |
The <prefix> segment is fully configurable via the :endpoint option — "iiif/3" (default), "image", "" (mount-root), or anything else. The plug's outer mount path is also stripped before recognition via the :mount option.
Region segment
| Form | image_plug | Notes |
|---|---|---|
full | ✅ | No Crop op produced. |
square | ⚠️ | Parses as pct:0,0,100,100 (whole image). True centred-square requires source-aware computation; the lossy fallback is documented. |
<x>,<y>,<w>,<h> (pixels) | ✅ | Ops.Crop{units: :pixels}. Out-of-bounds regions are clamped at apply time per spec §4.1. |
pct:<x>,<y>,<w>,<h> | ✅ | Ops.Crop{units: :percent}. Resolved against actual source dimensions at apply time. |
Size segment
| Form | image_plug | Notes |
|---|---|---|
max | ✅ | Ops.Resize{upscale?: false} — no dimension constraints; the source size becomes the output size. |
^max | ✅ | Ops.Resize{upscale?: true} — the ^ permits upscaling. |
<w>, | ✅ | Width-only resize; height computed from source aspect. |
,<h> | ✅ | Height-only resize. |
<w>,<h> | ✅ | Distorts to exact dimensions (fit: :squeeze). |
!<w>,<h> | ✅ | Fits within the bounding box, preserving aspect (fit: :contain). |
pct:<n> | ✅ | Resize by percentage; mapped to Ops.Resize{size_pct: n}. |
^ prefix on any of the above | ✅ | Sets Resize.upscale?: true. |
Rotation segment
| Form | image_plug | Notes |
|---|---|---|
0 | ✅ | Dropped by the normaliser. |
Integer 1–360 | ✅ | Ops.Rotate{angle: n} (integer preserved). |
Float 0.0–360.0 | ✅ | Ops.Rotate{angle: n} (float preserved). |
!N (mirror-then-rotate) | ⚠️ | The angle parses correctly but the leading mirror is silently dropped — there is no Mirror op in the IR yet. Roadmap. |
Quality segment
| Form | image_plug | Notes |
|---|---|---|
default | ✅ | No quality-related op. |
color | ✅ | Treated identically to default (the spec allows this). |
gray | ✅ | Ops.Adjust{saturation: 0.0}. |
bitonal | ✅ | Ops.Posterize{levels: 2}. |
The IIIF spec lists default, color, gray, bitonal as the four base qualities. Servers MAY advertise more under extraQualities in info.json; we do not.
Format segment
| Extension | IR Format.type | Notes |
|---|---|---|
jpg, jpeg | :jpeg | Always supported. |
png | :png | Always supported. |
gif | :gif | Always supported (libvips bundled). |
webp | :webp | Always supported (libvips bundled). |
tif, tiff | :tiff | Gated on Image.Plug.Capabilities.tiff_write?/0 (most builds support it). |
jp2 | :jp2 | Gated on Image.Plug.Capabilities.jp2_write?/0 (requires libvips built with libopenjp2). |
pdf | :pdf | Gated on Cairo support; advertised conditionally. |
The info.json document advertises only the formats this build can actually produce.
info.json discovery document
The <id>/info.json endpoint serves the IIIF Image Information document. What we emit:
| Property | Value | Notes |
|---|---|---|
@context | http://iiif.io/api/image/3/context.json | The Image API 3.0 JSON-LD context. |
id | The canonical service URL | Reconstructed from the request URL with /info.json stripped. |
type | ImageService3 | |
protocol | http://iiif.io/api/image | |
profile | level2 | We're a Level 2 server. |
width, height | Source dimensions | Read from the source resolver at request time. |
extraQualities | ["gray", "bitonal"] | Plus the always-supported default and color. |
extraFormats | Capability-gated subset of ["webp", "tif", "jp2", "avif"] | Always advertise webp; gate the others on Image.Plug.Capabilities. |
extraFeatures | The standard Level 2 set | See Image.Plug.Provider.IIIF.InfoJson source for the full list. |
Response headers:
Content-Type: application/ld+jsonLink: <http://iiif.io/api/image/3/level2.json>;rel="profile"(per spec §6)Cache-Control: public, max-age=86400(info.json is highly cacheable)
What we do not emit (Level 3 / Image API 4.0 features):
sizesandtilesarrays — the discrete-tile and pyramid-aware features used by IIIF zoom-image viewers.serviceextensions — auth, search, content-state.partOf,seeAlso,rightsJSON-LD relations.
These are non-blocking for image-rendering clients but block heavyweight viewers like OpenSeadragon's pyramid mode. Roadmap.
Mounting the IIIF provider
forward "/iiif/3", Image.Plug,
provider: {Image.Plug.Provider.IIIF, []},
source_resolver: {Image.Plug.SourceResolver.File, root: "/var/lib/iiif"}Or with a custom endpoint path:
forward "/imageserver", Image.Plug,
provider: {Image.Plug.Provider.IIIF, [endpoint: "iiif/3"]},
source_resolver: {Image.Plug.SourceResolver.File, root: "/var/lib/iiif"}The full source-resolver story (file, HTTP, S3, custom) is in sources.md. The CDN-fronting story (CloudFront/Fastly/Cloudflare in front of the IIIF mount) is in cdn_origin.md — IIIF's URL grammar is just as cache-friendly as the four CDN providers.
Known gaps and roadmap
squareregion as true centred-square — needs source-aware computation in the parser, or a:squaremode onOps.Crop. Currently apct:0,0,100,100fallback (whole image).!Nmirror-then-rotate — angle parses; mirror is silently dropped. Add anOps.Mirrorop or a flag onRotate.<id>bare-identifier 303 redirect — currently rejected with:malformed_url. Spec-required redirect to<id>/info.json.- info.json
sizes/tilesarrays — needed for IIIF tile-pyramid clients (OpenSeadragon, Universal Viewer's pyramid mode). - Authentication / Authorization API integration — IIIF Image API can sit behind the IIIF Auth API; not modelled.
Related
sources.md— source resolution, including the S3 worked example.cdn_origin.md— runningimage_plugas the origin behind a CDN; applies equally to the IIIF mount.face_aware.md— face-aware crops; not used by IIIF (nogravity=faceequivalent in the IIIF spec).image_components's IIIF guide — the client-side story; how<.image provider={:iiif}>produces URLs this provider parses.