@iiif/helpers is a TypeScript utility library for building IIIF applications with Presentation 2.1, 3.0, and the supported parts of the Presentation 4.0 release candidate.
It combines:
- pure helpers for annotation targets, content state, language maps, and image services
- higher-level helpers for thumbnails, painting annotations, ranges, search,
navDate, and transcriptions - a normalized IIIF vault/store layer for loading, editing, serializing, and subscribing to IIIF resources
This repository publishes one npm package, @iiif/helpers, with a set of focused subpath entry points such as @iiif/helpers/thumbnail and @iiif/helpers/vault/store.
- IIIF Presentation 2.1, 3.0, and supported Presentation 4 loading, upgrading, and normalization
- normalized storage and mutation of IIIF entities through the Vault
- selector parsing for
xywh, temporal fragments, Image API selectors, and SVG selectors - IIIF Content State parsing, encoding, decoding, validation, and normalization
- language map selection and localization helpers
- thumbnail and image-service candidate selection
- painting annotation extraction, choice handling, and media discovery
- range traversal, table-of-contents generation, and sequence calculation
- Search API 1 and 2 search/autocomplete stores
- transcript discovery and extraction from plain text, VTT, and supplemental annotations
- Presentation 4 Scene inspection and Activation transaction parsing
- vault-backed UI glue for events and styles
@iiif/parser is a peer dependency and should be installed alongside the helpers package.
npm install @iiif/helpers @iiif/parserpnpm add @iiif/helpers @iiif/parseryarn add @iiif/helpers @iiif/parserNotes:
- Node.js 22 or newer is required.
- The package ships ESM, CJS, and TypeScript declarations for each public entry point.
- If you parse SVG selectors in Node, provide a
DOMParserimplementation such asjsdomorhappy-dom. - If you use remote loading in Node, a modern runtime with built-in
fetchis the simplest setup.
Use the root package when you want the common surface area:
import { Vault, createThumbnailHelper, getValue, expandTarget } from "@iiif/helpers";Use subpath imports when you want a smaller, more explicit import surface or when you need packages that are not re-exported from the root:
import { getCanvasTranscription } from "@iiif/helpers/transcriptions";
import { Vault4 } from "@iiif/helpers/vault-4";
import { createStore } from "@iiif/helpers/vault/store";
import { entityActions } from "@iiif/helpers/vault/actions";
import { Vault as NodeVault } from "@iiif/helpers/vault-node";The root entry point includes feature helpers such as transcriptions, Search 2, Scenes, and Activations. It does not re-export:
@iiif/helpers/vault-4@iiif/helpers/vault-node@iiif/helpers/vault/actions@iiif/helpers/vault/store@iiif/helpers/vault/utility@iiif/helpers/vault/global-vault
import { Vault, getValue, createThumbnailHelper } from "@iiif/helpers";
const vault = new Vault();
const manifest = await vault.loadManifest("https://iiif.io/api/cookbook/recipe/0005-image-service/manifest.json");
if (!manifest) {
throw new Error("Manifest failed to load");
}
const firstCanvas = vault.get(manifest.items[0]);
const label = getValue(manifest.label, { language: "en-GB" });
const thumbnails = await createThumbnailHelper(vault).getBestThumbnailAtSize(firstCanvas, {
width: 300,
height: 300,
});
console.log(label);
console.log(thumbnails.best);For a native Presentation 4 normalized model, choose Vault4 when the application is created:
import { Vault4 } from "@iiif/helpers/vault-4";
const vault = new Vault4();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
if (!manifest) {
throw new Error("Manifest failed to load");
}
const firstContainer = vault.get(manifest.items[0]);
const presentation4 = vault.toPresentation4(manifest);The package is organized as subpath exports. The table below is the complete public entry-point map from package.json.
| Entry point | Re-exported from @iiif/helpers |
Purpose |
|---|---|---|
@iiif/helpers |
n/a | Convenience entry point for the main helpers. |
@iiif/helpers/events |
Yes | Vault-backed event registration for IIIF resources. |
@iiif/helpers/i18n |
Yes | Language map and localization helpers. |
@iiif/helpers/styles |
Yes | Vault-backed style metadata helpers. |
@iiif/helpers/thumbnail |
Yes | Best-thumbnail resolution for collections, manifests, canvases, annotations, and resources. |
@iiif/helpers/annotation-targets |
Yes | Selector and target parsing for IIIF and W3C annotations. |
@iiif/helpers/content-state |
Yes | Content State parsing, encoding, decoding, and normalization. |
@iiif/helpers/fetch |
Yes | Fetch and upgrade Presentation manifests and collections. |
@iiif/helpers/painting-annotations |
Yes | Painting annotation flattening and choice/media extraction. |
@iiif/helpers/ranges |
Yes | Range traversal, TOC generation, and range selection helpers. |
@iiif/helpers/sequences |
Yes | Visible-canvas and page-order helpers for paged/continuous/individuals behavior. |
@iiif/helpers/search1 |
Yes | Search API 1 search and autocomplete stores. |
@iiif/helpers/search2 |
Yes | Search API 2 search and autocomplete stores. |
@iiif/helpers/image-service |
Yes | Image-service loading, prediction, candidate extraction, and selection. |
@iiif/helpers/nav-date |
Yes | Date-navigation trees from navDate values. |
@iiif/helpers/vault |
Yes | Core Vault class, types, and singleton helper. |
@iiif/helpers/vault-4 |
No | Native Presentation 4 Vault and profile discriminator. |
@iiif/helpers/vault-node |
No | Node-oriented Vault with fetch support. |
@iiif/helpers/vault/global-vault |
No | Global singleton bridge for browser-global environments. |
@iiif/helpers/vault/actions |
No | Action constants and creators for vault state updates. |
@iiif/helpers/vault/store |
No | Zustand-based store factory and reducers used by the vault. |
@iiif/helpers/vault/utility |
No | Lower-level helpers for fetch pipelines and store access. |
@iiif/helpers/transcriptions |
Yes | Transcript detection and extraction helpers. |
@iiif/helpers/scenes |
Yes | Presentation 4 Scene paintables, targets, and transforms. |
@iiif/helpers/activations |
Yes | Presentation 4 Activation discovery and transaction parsing. |
The root entry point is a convenience export for the most commonly used helpers. It re-exports:
eventsactivationsannotation-valuesstylesthumbnaili18ncontent-stateannotation-targetspainting-annotationsrangessequencesvaultfetchsearch1search2scenesnav-dateimage-servicetranscriptions
Use it when you want a single import surface for most browser-side work.
This package normalizes IIIF and W3C annotation targets into a predictable SpecificResource-shaped structure.
Key exports:
expandTargetparseSelectorparseRotationsplitCanvasFragmentisImageApiSelectorisValidCanvasFragmentisRectanglepolygonToBoundingBoxserializeSupportedSelector- selector and target result types such as
SupportedTarget,BoxSelector,PointSelector,SvgSelector,TemporalSelector, andTemporalBoxSelector
What it handles:
- plain canvas ids
#xywh=fragments- temporal fragments such as
#t=... SpecificResourcetargets- Image API selectors
- SVG selectors with normalized geometry and extracted style data
- targets with embedded selector fragments in
source.id
Important notes:
- SVG selector parsing is best in the browser or with a provided
DOMParser. - The returned structure is designed to be easier to render than raw W3C selectors.
expandTarget()always returns atype: 'SpecificResource'shape withsource,selector, andselectors.
Example:
import { expandTarget } from "@iiif/helpers/annotation-targets";
const target = expandTarget("https://example.org/canvas-1#xywh=100,200,300,400");
console.log(target.selector?.type);
// BoxSelectorSelector-only example:
import { parseSelector } from "@iiif/helpers/annotation-targets";
const parsed = parseSelector({
type: "FragmentSelector",
value: "xywh=10,20,300,400&t=5,12.5",
});
console.log(parsed.selector);
// {
// type: 'TemporalBoxSelector',
// spatial: { x: 10, y: 20, width: 300, height: 400, unit: 'pixel' },
// temporal: { startTime: 5, endTime: 12.5 }
// }This package works with IIIF Content State values.
Key exports:
parseContentStateserialiseContentStateencodeContentStatedecodeContentStatevalidateContentStatenormaliseContentState- related types such as
ContentState,StateSource, andNormalisedContentState
What it does:
- parses already-decoded JSON strings
- parses base64url-encoded content state strings
- optionally fetches remote content state resources when called asynchronously
- encodes and decodes content state payloads
- normalizes different content state shapes into a consistent annotation form
Important notes:
- Validation is intentionally light-weight at the moment. It should be treated as a sanity check rather than full spec enforcement.
- Normalization uses the annotation-target helpers internally, so spatial selectors become structured selector objects instead of raw fragments.
Example:
import {
parseContentState,
serialiseContentState,
validateContentState,
normaliseContentState,
} from "@iiif/helpers/content-state";
const encoded = serialiseContentState({
id: "https://example.org/canvas-1#xywh=100,200,300,400",
type: "Canvas",
partOf: [{ id: "https://example.org/manifest-1", type: "Manifest" }],
});
const parsed = parseContentState(encoded);
const [valid, error] = validateContentState(parsed, true);
if (!valid) {
throw new Error(error?.reason ?? "Invalid content state");
}
const normalized = normaliseContentState(parsed);
console.log(normalized.target[0].selector);This is a small vault-backed event registry for IIIF resources.
Key exports:
createEventsHelper
The helper gives you:
addEventListener(resource, event, listener, scope?)removeEventListener(resource, event, listener)getListenersAsProps(resourceOrId, scope?)
It stores event handlers in vault meta so you can attach UI behavior to resources before they are rendered. This is especially useful in component-based UIs where passing callbacks down through many layers would otherwise be awkward.
Example:
import { Vault } from "@iiif/helpers";
import { createEventsHelper } from "@iiif/helpers/events";
const vault = new Vault();
const events = createEventsHelper(vault);
const annotation = { id: "https://example.org/annotation/1", type: "Annotation" };
events.addEventListener(annotation, "onClick", (_event, resource) => {
console.log("Clicked", resource.id);
});
const props = events.getListenersAsProps(annotation);
// React:
// <div {...props} />
// DOM:
// element.addEventListener('click', props.onClick);This package exports a single helper:
fetch
It fetches a Manifest or Collection and upgrades the response through @iiif/parser/upgrader, which makes it convenient when you want to support both Presentation 2 and Presentation 3 sources before loading them into a vault.
Example:
import { fetch as fetchIiif } from "@iiif/helpers/fetch";
const resource = await fetchIiif("https://example.org/manifest.json");This package contains language-map helpers and a simple template-tag helper for localized strings.
Key exports:
getClosestLanguagebuildLocaleStringgetValuegetAvailableLanguagesFromResourcecreateStringHelperiiifString
Typical uses:
- select the best value from an IIIF language map
- apply explicit fallback languages
- detect languages used anywhere inside a collection, manifest, canvas, or range
- build localized labels inline with template literals
Example:
import { getValue, iiifString } from "@iiif/helpers/i18n";
const label = getValue({ fr: ["Titre"], en: ["Title"] }, { language: "fr" });
const summary = iiifString`Label: ${{ en: ["An English label"] }}`;Fallback example:
import { buildLocaleString } from "@iiif/helpers/i18n";
const label = buildLocaleString({ en: ["English title"], fr: ["Titre francais"], none: ["Untitled"] }, "cy-GB", {
fallbackLanguages: ["en-GB", "en"],
defaultText: "Untitled manifest",
});This is the low-level image-service package used by the thumbnail helpers and useful on its own when you need to inspect or select image delivery options.
Key exports:
ImageServiceLoadercreateImageServiceStoreimageServicesgetImageCandidatesgetImageCandidatesFromServicegetFixedSizeFromImagegetFixedSizesFromServicegetCustomSizeFromServicegetImageFromTileSourcegetImageServerFromIdgetSmallestScaleFactorAsSingleImagepickBestFromCandidatesimageSizesMatchinferImageSizeFromUrlsampledTilesToTilesisImage3isBestMatch- image candidate/result types
What it is good for:
- turning content resources and image services into a ranked set of image candidates
- picking the best candidate for a desired width/height or min/max constraints
- preloading or sampling image service responses
- predicting compatible services on known image servers to reduce repeated network work
- exposing service-loading state through a vanilla Zustand store
Important notes:
ImageServiceLoaderhas both synchronous and asynchronous loading paths.- The store returned by
createImageServiceStore()includes both a Zustandstoreand an event emitter. - The candidate model distinguishes fixed images, fixed-size services, variable services, and unknown-size images.
Example:
import { ImageServiceLoader, getImageCandidates, pickBestFromCandidates } from "@iiif/helpers/image-service";
const loader = new ImageServiceLoader();
const resource = {
id: "https://example.org/image/full/max/0/default.jpg",
type: "Image",
width: 1200,
height: 800,
service: [
{
id: "https://example.org/image",
type: "ImageService3",
profile: "level1",
},
],
} as any;
const candidates = getImageCandidates(resource, false, loader);
const choice = pickBestFromCandidates({ width: 300, height: 200 }, [() => candidates]);
console.log(choice.best);
console.log(choice.fallback);This package builds navigation trees from navDate.
Key exports:
createDateNavigation- date-navigation types such as
DateNavigationCentury,DateNavigationDecade,DateNavigationYear,DateNavigationMonth, andDateNavigationDay
How it behaves:
- it reads
navDatevalues from items in a Collection or Manifest - it groups them into century, decade, year, month, and day buckets
- if you do not pass an explicit level, it automatically collapses through single-item levels and returns the most useful level
Use it when you want archive-style time browsing or date facets built directly from IIIF metadata.
Example:
import { Vault } from "@iiif/helpers";
import { createDateNavigation } from "@iiif/helpers/nav-date";
const vault = new Vault();
const collection = vault.loadSync("https://example.org/collection", {
id: "https://example.org/collection",
type: "Collection",
label: { en: ["Archive"] },
items: [
{
id: "https://example.org/manifest/1986",
type: "Manifest",
label: { en: ["Issue 1986"] },
navDate: "1986-01-01T00:00:00+00:00",
},
{
id: "https://example.org/manifest/1987",
type: "Manifest",
label: { en: ["Issue 1987"] },
navDate: "1987-01-01T00:00:00+00:00",
},
],
});
const years = createDateNavigation(vault, collection!, "year");
for (const year of years) {
console.log(year.label.en?.[0], year.count);
}This package is for flattening painting annotations into a simpler renderable structure.
Key exports:
createPaintingAnnotationsHelperparseSpecificResourcePaintables- choice-related types such as
SingleChoice,ComplexChoice, andChoiceDescription
The helper gives you:
getAllPaintingAnnotations(canvasOrId)getPaintables(canvasOrAnnotations, enabledChoices?)extractChoices(canvasOrAnnotations)
What it does:
- resolves all painting annotations on a canvas
- unwraps
SpecificResourcebodies - handles
Choicebodies and records the available/selected choices - returns a list of paintable resources with their original annotation, target, selector, and normalized type
This is useful when you are rendering mixed media, alternative image choices, or need a flat list of paintable bodies from a canvas.
Example:
import { Vault } from "@iiif/helpers";
import { createPaintingAnnotationsHelper } from "@iiif/helpers/painting-annotations";
const vault = new Vault();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
if (!manifest) {
throw new Error("Manifest failed to load");
}
const canvas = vault.get(manifest.items[0]);
const painting = createPaintingAnnotationsHelper(vault);
const paintables = painting.getPaintables(canvas);
console.log(paintables.types);
console.log(paintables.choice);
for (const item of paintables.items) {
console.log(item.type, item.resource.id, item.selector);
}This package covers traversal and interpretation of IIIF ranges.
Key exports:
createRangeHelperfindFirstCanvasFromRangefindFirstCanvasFromRangeWithSelectorfindAllCanvasesInRangefindManifestSelectedRangefindSelectedRangerangeToTableOfContentsTreerangesToTableOfContentsTreeisRangeContiguousRangeTableOfContentsNode
What it is good for:
- finding the first canvas represented by a range
- flattening all canvases found inside nested ranges
- determining which range contains a given canvas
- building a nested table-of-contents tree with canvas leaves and parent links
- checking whether a range is contiguous within a known canvas order, including gap and subset reporting
Important notes:
rangeToTableOfContentsTree()creates a structured tree that is ready to render.rangesToTableOfContentsTree()creates a virtual root when you pass multiple top-level ranges.isRangeContiguous()can optionally return detailed diagnostics about invalid ranges, missing canvases, and gaps.
Example:
import { Vault } from "@iiif/helpers";
import { rangeToTableOfContentsTree, isRangeContiguous } from "@iiif/helpers/ranges";
const vault = new Vault();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
if (!manifest || !manifest.structures?.length) {
throw new Error("Manifest has no ranges");
}
const range = vault.get(manifest.structures[0]);
const toc = rangeToTableOfContentsTree(vault, range);
const [isContiguous, detail] = isRangeContiguous(vault, range, manifest.items, { detail: true });
console.log(toc?.items?.map((item) => item.label));
console.log(isContiguous, detail?.gaps);This package contains helpers for IIIF Search API 1.
Key exports:
findSearch1ServicefindAutocompleteServicecreateSearch1StorecreateSearch1AutocompleteStore- store types such as
Search1Store,Search1AutocompleteStore,Search1Service, andSingleSearchHit
What it provides:
- a search store that queries a Search API 1 service, keeps
resourcesandhits, and supports hit highlighting - an autocomplete store that queries autocomplete endpoints and keeps a result list
- automatic aborting of in-flight requests when a new search replaces an old one
Example:
import { createSearch1Store, createSearch1AutocompleteStore } from "@iiif/helpers/search1";
const service = {
"@id": "https://example.org/search",
profile: "http://iiif.io/api/search/1/search",
service: {
"@id": "https://example.org/search/autocomplete",
profile: "http://iiif.io/api/search/1/autocomplete",
},
} as any;
const store = createSearch1Store(service);
await store.getState().search({ q: "1615" });
store.getState().highlightHit(0);
const autocomplete = createSearch1AutocompleteStore(service);
await autocomplete.getState().search("wunder");This package contains helpers for IIIF Search API 2.
Key exports:
findSearch2ServicefindSearch2AutocompleteServicecreateSearch2StorecreateSearch2AutocompleteStore- Search 2 service, query, result, and store types
The stores manage search and autocomplete requests, abort superseded requests, expose ignored query parameters, and support navigating highlighted hits.
Example:
import { createSearch2Store, findSearch2Service } from "@iiif/helpers/search2";
const service = findSearch2Service(manifest);
if (service) {
const store = createSearch2Store(service);
await store.getState().search({ q: "example", motivation: "commenting" });
store.getState().highlightHit(0);
console.log(store.getState().resources);
}This package calculates visible canvases and page order from IIIF behavior.
Key exports:
createSequenceHelpergetVisibleCanvasesFromCanvasIdgetManifestSequence
It understands manifest and range behaviors such as:
pagedcontinuousindividuals- canvas-level
facing-pages - canvas-level
non-paged
Use it when you need to answer questions like:
- which canvases should be visible when canvas
Xis selected? - what are the page groups for a paged interface?
- should this manifest render as single pages, spreads, or one continuous strip?
Example:
import { Vault } from "@iiif/helpers";
import { getManifestSequence, getVisibleCanvasesFromCanvasId } from "@iiif/helpers/sequences";
const vault = new Vault();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
if (!manifest) {
throw new Error("Manifest failed to load");
}
const [items, pageGroups] = getManifestSequence(vault, manifest);
const visible = getVisibleCanvasesFromCanvasId(vault, manifest, items[0].id);
console.log(pageGroups);
console.log(visible);This is a small vault-backed style metadata helper.
Key exports:
createStylesHelperStyleDefinitionStyledHelper
The helper gives you:
applyStyles(resource, scope, styles)getAppliedStyles(resource)
It is useful when style information lives outside the IIIF resource but still needs to follow the same resource ids, for example annotation colors, highlight state, or editor-only presentation metadata.
Example:
import { Vault } from "@iiif/helpers";
import { createStylesHelper } from "@iiif/helpers/styles";
const vault = new Vault();
const styles = createStylesHelper(vault);
const annotation = { id: "https://example.org/annotation/1", type: "Annotation" };
vault.batch(() => {
styles.applyStyles(annotation, "selected", { background: "red" });
styles.applyStyles(annotation, "hovered", { background: "pink" });
});
console.log(styles.getAppliedStyles(annotation));This package is the high-level thumbnail helper built on top of the image-service utilities.
Key exports:
getThumbnailcreateThumbnailHelperimageServiceLoader- thumbnail input/output types
What it accepts:
- string ids or URLs
- collection, manifest, canvas, annotation, annotation page, and content-resource references
- normalized IIIF resources
What it returns:
best: the chosen thumbnail candidatefallback: other viable candidateslog: explanation messages from the candidate-selection process
What it does:
- follows explicit
thumbnailproperties where present - falls back to painted resources when needed
- dereferences and inspects image services when appropriate
- chooses the best available result for a requested size or constraint set
Example:
import { Vault } from "@iiif/helpers";
import { createThumbnailHelper, getThumbnail } from "@iiif/helpers/thumbnail";
const vault = new Vault();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
if (!manifest) {
throw new Error("Manifest failed to load");
}
const canvas = vault.get(manifest.items[0]);
const helper = createThumbnailHelper(vault);
const result = await helper.getBestThumbnailAtSize(canvas, { width: 256, height: 256 });
const quickResult = await getThumbnail(canvas, {
vault,
width: 512,
height: 512,
returnAllOptions: true,
});
console.log(result.best);
console.log(quickResult.fallback);This package is focused on transcript detection and extraction.
Key exports:
canvasHasTranscriptionSynccanvasLoadExternalAnnotationPagesgetCanvasTranscriptionmanifestHasTranscriptionsannotationPageToTranscriptionvttToTranscriptiontimeStampToSeconds
What it supports:
- plain text renderings on canvases
text/vttsupplemental bodies on AV canvasesTextualBodyandtext/plainsupplemental annotations- external annotation pages that need to be loaded before transcript detection
Returned transcription objects include:
plaintextsegments- selector/timing information for segments when available
Important notes:
- VTT files are parsed into segment records with temporal selectors.
- External annotation pages can be loaded on demand with
canvasLoadExternalAnnotationPages(). - ALTO renderings are detected, but ALTO parsing is not implemented yet, so those paths currently return
null.
Example:
import { Vault } from "@iiif/helpers";
import { manifestHasTranscriptions, getCanvasTranscription } from "@iiif/helpers/transcriptions";
const vault = new Vault();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
if (!manifest) {
throw new Error("Manifest failed to load");
}
const hasTranscriptions = await manifestHasTranscriptions(vault, manifest);
if (hasTranscriptions) {
const canvas = vault.get(manifest.items[0]);
const transcription = await getCanvasTranscription(vault, canvas);
console.log(transcription?.plaintext);
console.log(transcription?.segments[0]?.selector);
}This is the core stateful package in the library.
Key exports:
VaultglobalVault- vault types such as
VaultOptions,Entities,IIIFStore,RequestState,MetaState, and related action/type helpers
The Vault is a normalized Presentation 3 resource store with loading, mutation, serialization, subscription, and helper methods. It accepts Presentation 2.1 and 3.0 input, plus supported non-3D Presentation 4 input through the parser's Presentation 3 compatibility projection. Its profile never changes after construction.
Major capabilities:
- load manifests, collections, and other IIIF resources with
load(),loadManifest(), andloadCollection() - load synchronously from already-available JSON with
loadSync() - access normalized resources with
get()andhydrate() - navigate safely with
deep() - subscribe to store changes with
subscribe() - listen to dispatched actions with
on() - batch state changes with
batch()andasyncBatch() - serialize resources back to Presentation 2 or Presentation 3 with
toPresentation2()andtoPresentation3() - manage per-resource meta state with
setMetaValue()andgetResourceMeta() - paginate paged collections with
getPaginationState()andloadNextPage() - inspect the detected source version and normalization diagnostics with
getLoadReport()
The Vault also includes higher-level object wrappers:
getObject()loadObject()loadManifestObject()loadCollectionObject()wrapObject()andisWrapped()methods on the class
Those wrapped objects resolve nested references through getters and expose helper methods like refresh(), reactive(), unwrap(), toJSON(), toPresentation2(), and toPresentation3().
Example:
import { Vault } from "@iiif/helpers/vault";
const vault = new Vault();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
if (!manifest) {
throw new Error("Manifest failed to load");
}
const firstCanvas = vault.get(manifest.items[0]);
vault.setMetaValue([firstCanvas.id, "ui", "selected"], true);
console.log(vault.getResourceMeta(firstCanvas.id, "ui"));
const wrapped = vault.getObject(manifest);
console.log(wrapped.items[0].id);
console.log(wrapped.items[0].unwrap());This package provides the explicit Presentation 4 Vault contract.
Key exports:
Vault4isVault4- Presentation 4-specific option, reference, and result types
Vault4 shares the core Vault runtime while using Presentation 4 normalization and serialization. It preserves Canvas, Timeline, Scene, CollectionPage, and other Presentation 4 identities, and can serialize loaded resources with toPresentation2(), toPresentation3(), or toPresentation4().
Use isVault4() instead of instanceof Vault4 when a Vault may come from another installed copy of @iiif/helpers.
Example:
import { isVault4, Vault4 } from "@iiif/helpers/vault-4";
const vault = new Vault4();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
if (manifest && isVault4(vault)) {
console.log(vault.getLoadReport(manifest.id));
console.log(vault.toPresentation4(manifest));
}This is the Node-oriented vault package.
Key exports:
VaultglobalVault- the same core types re-exported by
@iiif/helpers/vault
The Vault class in this package extends the core vault and configures a Node-friendly fetch path. Use it when you want vault behavior in server-side code and you do not want to wire a custom fetcher yourself.
Example:
import { Vault } from "@iiif/helpers/vault-node";
const vault = new Vault();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
console.log(manifest?.id);This package exposes the vault's Redux-style action constants and action creators.
Examples include:
entityActionsmappingActionsmetaActionsrequestActionsaddReferencemodifyEntityFieldmoveEntitymoveEntitiesupdateReferencechangeReferenceIdentifier- request lifecycle constants such as
REQUEST_RESOURCE,RESOURCE_READY, andRESOURCE_ERROR
Use it when you are integrating the vault store directly or building custom tooling on top of the same reducer model.
Example:
import { Vault } from "@iiif/helpers/vault";
import { entityActions } from "@iiif/helpers/vault/actions";
const vault = new Vault();
const manifest = await vault.loadManifest("https://example.org/manifest.json");
if (!manifest) {
throw new Error("Manifest failed to load");
}
vault.dispatch(
entityActions.modifyEntityField({
id: manifest.id,
type: "Manifest",
key: "label",
value: { en: ["Updated title"] },
})
);This package exposes the underlying store factory and reducers used by the vault.
Key exports:
createStorereducersVaultStoreStateVaultZustandStore
The store is built with vanilla Zustand plus Redux-style reducers. Use it when you want to embed the vault state model into an existing application architecture rather than always going through the Vault class.
Example:
import { emptyManifest } from "@iiif/parser";
import { createStore } from "@iiif/helpers/vault/store";
import { entityActions } from "@iiif/helpers/vault/actions";
const store = createStore();
const manifestId = "https://example.org/manifest-1";
store.dispatch(
entityActions.importEntities({
entities: {
Manifest: {
[manifestId]: {
...emptyManifest,
id: manifestId,
type: "Manifest",
items: [],
},
},
},
})
);
store.dispatch(
entityActions.modifyEntityField({
id: manifestId,
type: "Manifest",
key: "label",
value: { en: ["An example label"] },
})
);
console.log(store.getState().iiif.entities.Manifest[manifestId]);This package contains lower-level utility helpers used by the vault internals and occasionally useful for advanced integrations.
Key exports:
actionListFromResourceareInputsEqualcreateFetchHelpergetDefaultEntitiesresolveIfExistsresolveList
These are useful when you want to:
- build your own fetch-to-actions pipeline
- inspect or recreate the vault's normalization defaults
- resolve reference lists against a store directly
- reuse the vault's selector-equality logic
Example:
import { Vault } from "@iiif/helpers/vault";
import { createFetchHelper } from "@iiif/helpers/vault/utility";
const vault = new Vault();
const fetchManifest = createFetchHelper(vault, async (url: string) => {
return {
id: url,
type: "Manifest",
label: { en: ["Generated manifest"] },
items: [],
};
});
const manifest = await fetchManifest("https://example.org/manifest.json");
console.log(manifest?.id);This is a small compatibility bridge for global/browser-based setups.
Key export:
getGlobalVault
It returns globalThis.IIIF_VAULT if one already exists, and if not, it will try to initialize one from a global IIIFVault object. In modern module-based applications, globalVault() from @iiif/helpers/vault is usually the clearer API. This subpath is mostly useful when working with browser globals or legacy integration points.
Example:
import { getGlobalVault } from "@iiif/helpers/vault/global-vault";
const vault = getGlobalVault();
console.log(vault);If you are not sure where to start:
- start with
@iiif/helpersfor general browser-side helper work - use
@iiif/helpers/vaultwhen you want normalized loading and resource access - use
@iiif/helpers/vault-4when your application needs a native Presentation 4 model - use
@iiif/helpers/thumbnailand@iiif/helpers/image-servicefor image selection and delivery logic - use
@iiif/helpers/annotation-targetsand@iiif/helpers/content-statefor viewer/deeplink interoperability - use
@iiif/helpers/ranges,@iiif/helpers/sequences, and@iiif/helpers/nav-datefor navigation structures - use
@iiif/helpers/search1,@iiif/helpers/search2, and@iiif/helpers/transcriptionsfor discovery and text experiences - use
@iiif/helpers/scenesand@iiif/helpers/activationsfor the tested Presentation 4 3D helper surface
There are also focused notes and examples in this repository:
Presentation 4 uses an explicit, lifetime-stable Vault contract. Choose the normalized model when the application is created; loading a resource never replaces the Vault or changes its profile.
| API | Accepted input | Normalized application model | Intended use |
|---|---|---|---|
Vault |
Presentation 2.1, 3.0, supported non-3D 4.0 | Presentation 3 | Existing applications; Presentation 4 Timeline resources are projected to Canvas |
Vault4 |
Presentation 2.1, 3.0, and 4.0 | Presentation 4 | New applications that need Timeline, Scene identity, CollectionPage, or other v4 concepts |
A Vault's normalized version is fixed for its lifetime. Parser conversion happens once when a resource is loaded; helpers consume the resulting model.
No version branch is needed:
import { Vault } from "@iiif/helpers/vault";
const vault = new Vault();
const manifest = await vault.loadManifest(manifestUrl);
// Canvas for v2/v3 input and for a compatible v4 Timeline projection.
const firstCanvas = vault.get(manifest.items[0]);A Presentation 4 Scene cannot be represented faithfully as Presentation 3.
The compatibility path throws a
Presentation4CompatibilityError with structured diagnostics instead of
silently dropping it.
import { Vault4 } from "@iiif/helpers/vault-4";
const vault = new Vault4();
const manifest = await vault.loadManifest(manifestUrl);
const firstContainer = vault.get(manifest.items[0]);
const presentation4 = vault.toPresentation4(manifest);firstContainer remains a Canvas, Timeline, or Scene. Vault4 also accepts
Presentation 2.1 and 3.0 input and upgrades it once during ingestion.
Both Vaults retain the detected source version. Vault4 also retains parser
normalization warnings such as deterministic ID minting:
const report = vault.getLoadReport(manifestUrl);
// {
// sourceVersion: 2 | 3 | 4 | "unknown",
// diagnostics: [...]
// }Reports are stored for both the requested URL and the canonical resource ID when they differ.
Loading a Collection or AnnotationCollection does not implicitly fetch its pages. Page chains are an explicit Vault operation:
const collection = await vault.loadCollection(collectionUrl);
const state = await vault.loadPageChain(collection);
const members = vault.getPaginatedItems(collection);CollectionPage and AnnotationPage entities remain independently addressable.
The derived member list is ordered and deduplicated. Cycles and broken links
stop safely and are exposed on state.error; loaded prefixes are retained.
Shared helpers accept either fixed Vault profile. Canvas-specific functions remain Canvas-specific; findAllContainersInRange and the Container transcription APIs cover Canvas, Timeline, and Scene without relabeling them. Painting, thumbnail, transcription, and Scene helpers consistently resolve nested Choice, Composite, List, Independents, and SpecificResource bodies, including authored Choice defaults.
Two optional subpaths expose the first tested 3D vertical slices:
import { createSceneHelper } from "@iiif/helpers/scenes";
import { createActivationsHelper } from "@iiif/helpers/activations";
const scenes = createSceneHelper(vault4);
const paintables = scenes.getPaintables(scene);
const activations = createActivationsHelper(vault4);
const annotations = activations.getAllActivatingAnnotations(manifest);
const transaction = activations.parseActivatingAnnotation(annotations[0]);The initial supported surface is intentionally narrow:
- Model paintables are returned as a known Scene paintable type.
- SpecificResource selectors, transforms, and action order are preserved.
- Activating annotations can be collected and parsed into ordered steps.
- Unrecognized Scene body types remain available as
unknownwithrawType.
The helpers do not compose or render Scenes, apply transforms, choose cameras, calculate lighting, spatialize audio emitters, or execute activation actions. Camera, light, audio-emitter, animation, and nested extension subtrees are not normalized into helper-specific models unless a tested vertical slice is added.
This checkout pins the exact Parser preview used for development and testing. Install from the lockfile and run the coordinated gates directly:
pnpm install --frozen-lockfile
pnpm run fmt:check
pnpm run lint
pnpm run lint:pkg
pnpm run typecheck
pnpm exec vitest runRun pnpm fmt to apply the shared oxfmt configuration. The checked-in Zed settings use oxfmt on save and oxlint on type.
The packed gate installs the pinned Parser preview and a packed Helpers tarball
into a fresh consumer, imports every public subpath through ESM and CommonJS,
and compiles NodeNext and Bundler consumers with skipLibCheck: false.
MIT