Skip to content

IIIF Collections

As introduced on the IIIF Presentation API support page, Collections come in two flavours:

  • Storage Collections act as containers — like folders, organising the storage of IIIF resources within the platform, in an arbitrary hierarchy. A Storage Collection doesn’t have a rich stored JSON representation, and its items property is always generated by the platform from what the collection actually contains.
  • IIIF Collections are stored JSON documents — IIIF Presentation 3.0 Collections that can carry anything valid IIIF can carry, whose items you edit yourself, referencing any Manifests or Collections whether the platform manages them or not.

This page describes both, along with ordering, paging and search. The URL forms, the X-IIIF-CS-Show-Extras header, write semantics and error conventions shared by all IIIF resources are described once, on the parent page.

Customer 99 is used as an example throughout.

Both the API view and the public view of a Storage Collection are valid IIIF Collections as defined by the IIIF Presentation API 3.0. But there are obvious differences between the resource you operate on via the API, and the resource served to the public. Here is the API view of a Storage Collection containing five children — two public Storage Collections, one non-public Storage Collection, a IIIF Collection and a Manifest:

{
"@context": [
"http://tbc.org/iiif-repository/1/context.json",
"http://iiif.io/api/presentation/3/context.json"
],
"id": "https://iiif.dlcs.example/99/collections/d44eeb7a",
"type": "Collection",
"slug": "manuscripts",
"parent": "https://iiif.dlcs.example/99/collections/root",
"publicId": "https://iiif.dlcs.example/99/manuscripts",
"flatId": "d44eeb7a",
"behavior": [
"storage-collection",
"public-iiif"
],
"label": {"en": ["Manuscripts"]},
"created": "2026-09-09T13:31:01Z",
"createdBy": "Admin",
"modified": "2026-09-09T13:31:01Z",
"modifiedBy": null,
"totals": {
"childStorageCollections": 3,
"childIIIFCollections": 1,
"childManifests": 1
},
"totalItems": 5,
"view": {
"@id": "https://iiif.dlcs.example/99/collections/d44eeb7a?page=1&pageSize=100",
"@type": "PartialCollectionView",
"page": 1,
"pageSize": 100,
"totalPages": 1
},
"partOf": [
{
"id": "https://iiif.dlcs.example/99/collections/root",
"type": "Collection",
"label": {"en": ["IIIF Home"]}
}
],
"seeAlso": [
{
"id": "https://iiif.dlcs.example/99/manuscripts",
"type": "Collection",
"profile": "public-iiif",
"label": {"en": ["Manuscripts"]}
},
{
"id": "https://iiif.dlcs.example/99/manuscripts",
"type": "Collection",
"profile": "api-hierarchical",
"label": {"en": ["Manuscripts"]}
}
],
"items": [
{
"id": "https://iiif.dlcs.example/99/collections/wvdx22gecu7e",
"type": "Collection",
"label": {"en": ["14th Century"]},
"behavior": ["public-iiif", "storage-collection"]
},
{
"id": "https://iiif.dlcs.example/99/collections/mfstw2c3fyty",
"type": "Collection",
"label": {"en": ["15th Century"]},
"behavior": ["public-iiif", "storage-collection"]
},
{
"id": "https://iiif.dlcs.example/99/collections/nrbew3hg67dz",
"type": "Collection",
"label": {"en": ["Working files"]},
"behavior": ["storage-collection"]
},
{
"id": "https://iiif.dlcs.example/99/collections/pqcfx4jh78ea",
"type": "Collection",
"label": {"en": ["Highlights"]},
"behavior": ["public-iiif"]
},
{
"id": "https://iiif.dlcs.example/99/manifests/gb799m5z",
"type": "Manifest",
"label": {"en": ["MS 125"]}
}
]
}

The API Storage Collection has one or two additional behavior property values:

  • The storage-collection behavior identifies the Collection as a Storage Collection rather than a regular IIIF Collection. This means you can only edit its label property (a language map), and its slug and parent. You cannot edit the items property via HTTP operations on JSON, only by adding or deleting child Manifests or Collections with PUTs and POSTs. The items property is only ever generated by the server; you cannot supply it for a Storage Collection.
  • The public-iiif behavior indicates that a public request for this resource will be served. If the Storage Collection does not have this behavior, it won’t be visible to the public and its public URL returns HTTP 404. You may not wish for purely organisational containers in your IIIF repository to be available to the public as (potentially huge) IIIF Collections.

Notice the behavior values on the entries in items above: you can tell the three kinds of child apart — Storage Collections, IIIF Collections (no storage-collection behavior) and Manifests (no behavior at all).

The API Storage Collection has properties that govern the resource’s location in the hierarchy, and therefore the public URL of the Collection — slug, parent and publicId, as introduced on the parent page. The hierarchical URL is composed of path elements provided by the resource’s slug, its parent’s slug, and so on up to the root.

For Storage Collections, IIIF Collections and Manifests returned by the platform with Show-Extras:

  • parent is always the flat form, not the hierarchical form.
  • publicId shows what URL the public version of this resource is available on, which is also the value of id that appears in that resource when viewed publicly.
  • partOf carries the parent as a standard IIIF reference, so IIIF-aware clients can navigate upwards.

For resources supplied to the platform by POST and PUT:

  • parent may be the hierarchical form or the flat form.
  • publicId is not settable; it is read-only, and ignored if supplied.

The totals property summarises what the collection contains — the three counts of immediate children, by kind. Note that totals and totalItems count all children, including non-public ones. (totalItems and view provide paging, described in the next section.)

The API view also carries modification metadata: created, createdBy, modified and modifiedBy.

The API Storage Collection has one or two seeAlso links: one with profile api-hierarchical pointing at the hierarchical form of the API version, and — only if the Storage Collection has the public-iiif behavior — one with profile public-iiif pointing at the canonical public equivalent. In the default configuration both point at the same hierarchical URL, but they are distinct in meaning.

The public view of the same Storage Collection is a plain IIIF Presentation 3.0 Collection — just @context, id, type, label and items:

{
"@context": "http://iiif.io/api/presentation/3/context.json",
"id": "https://iiif.dlcs.example/99/manuscripts",
"type": "Collection",
"label": {"en": ["Manuscripts"]},
"items": [
{
"id": "https://iiif.dlcs.example/99/manuscripts/14th-century",
"type": "Collection",
"label": {"en": ["14th Century"]}
},
{
"id": "https://iiif.dlcs.example/99/manuscripts/15th-century",
"type": "Collection",
"label": {"en": ["15th Century"]}
},
{
"id": "https://iiif.dlcs.example/99/manuscripts/highlights",
"type": "Collection",
"label": {"en": ["Highlights"]}
},
{
"id": "https://iiif.dlcs.example/99/manuscripts/ms-125",
"type": "Manifest",
"label": {"en": ["MS 125"]}
}
]
}

Note the differences from the API view: the ids are hierarchical public URLs, and the non-public “Working files” child is simply absent — the public view lists only children that are themselves public. It is navigable by any client (an IIIF browser) that understands a IIIF Collection.

Every customer starts with a root Storage Collection that cannot be deleted (attempting to returns 400, “Cannot delete a root collection”). Its public hierarchical form is https://iiif.dlcs.example/99 and its flat form is https://iiif.dlcs.example/99/collections/root. In every other respect it behaves as any other Storage Collection.

The API Storage Collection has totalItems and view properties, which work the same way as paging resources in the rest of the API (documented in Hydra Collections). Pass page and pageSize as query parameters; view describes where you are, with first, previous, next and last links as appropriate:

{
"totalItems": 5,
"view": {
"@id": "https://iiif.dlcs.example/99/collections/d44eeb7a?page=1&pageSize=2",
"@type": "PartialCollectionView",
"page": 1,
"pageSize": 2,
"totalPages": 3,
"next": "https://iiif.dlcs.example/99/collections/d44eeb7a?page=2&pageSize=2",
"last": "https://iiif.dlcs.example/99/collections/d44eeb7a?page=3&pageSize=2"
}
}

The default page size is 100. A normal IIIF Collection has no paging features — it is intended for human interaction as a single resource, so should not be arbitrarily large. But we need paging for Storage Collections on the API view, to allow them to be used as containers and still ensure a good user experience in any interfaces built on them.

By default, the items of a Storage Collection are ordered by creation date, ascending. You can change this with the orderBy or orderByDescending query parameter, with one of three fields:

  • created — the default
  • slug — alphabetical by URL path element
  • id — by flat identifier

Any other field name is silently ignored, leaving the default order. The ordering parameter is carried through into the view paging links, so you can page through a large collection in a stable order:

GET /99/collections/d44eeb7a?orderBy=slug&pageSize=100
GET /99/collections/d44eeb7a?orderByDescending=created

The write semantics — credentials plus the Show-Extras header, the If-Match rules, and the Operations table — are described on the parent page. For Collections specifically:

If your public IIIF resources use the hierarchical scheme, you may not have any need to assign your own flat identifiers — an example might be a IIIF Manifest Editor that only presents a folder-and-file model to its users, where it would be inconvenient to have to mint identifiers yourself. POST the new Collection to the flat container URL and the platform assigns the identity, returning it in the Location header:

POST /99/collections
Host: iiif.dlcs.example
{
"type": "Collection",
"behavior": ["storage-collection", "public-iiif"],
"label": {"en": ["My first Storage Collection"]},
"slug": "my-first-storage-collection",
"parent": "https://iiif.dlcs.example/99/collections/root"
}

Returns 201 Created, with the API view of the new Collection in the body and its minted flat URL in the Location header.

PUT the same body to the flat URL you want the resource to have:

PUT /99/collections/my-flat-identifier
Host: iiif.dlcs.example

There is no Location header on a PUT response — you chose the URL. To create a Collection inside another one, use exactly the same operations with the parent set to the containing Collection (in either of its URL forms) — and the same goes for every level of the hierarchy.

Notes that apply to both forms of create:

  • The storage-collection behavior tells the platform that you are creating a Storage Collection and not a regular IIIF Collection.
  • An items property supplied for a Storage Collection is ignored — items is always generated by the platform.
  • Another resource with the same slug under the same parent returns 409 Conflict.
  • If all you have is the public hierarchical URL (e.g., you didn’t record the returned Location), you can always obtain the flat URL by requesting the hierarchical URL with the Show-Extras header and following the redirect — or from the seeAlso of any API view.

Updates are whole-resource PUTs with If-Match. For a Storage Collection you can change its label, its behavior (to publish or unpublish it), and its position in the hierarchy:

  • changing slug renames the Collection;
  • changing parent moves it.

Changing either will change the public URL of the Collection — and the public URLs of all child and descendant resources, Collections and Manifests, below it in the hierarchy. The old URLs return 404; the flat URLs of every affected resource are unchanged. This is how you move a resource.

DELETE the flat URL with If-Match. A Collection that still has children cannot be deleted — 400 Bad Request, “Cannot delete a collection with child items” (…/errors/DeleteResourceErrorType/CollectionNotEmpty) — delete the children first, or move them. The root Collection cannot be deleted at all.

So far the examples have been Storage Collections — containers to organise content. But you can also create IIIF Collections: stored JSON documents with, potentially, no platform dependencies at all. A IIIF Collection can use summary, metadata, provider, homepage, thumbnail and any other standard IIIF properties — all supported in a IIIF Collection but not in a Storage Collection — and its items are yours to edit, referencing any Manifests or Collections, on this platform or anywhere on the web.

Create one exactly as in the previous section — PUT to a flat URL of your choice, or POST to /99/collections for a minted one — with the mandatory parent and slug, the public-iiif behavior, and without the storage-collection behavior:

{
"type": "Collection",
"behavior": ["public-iiif"],
"label": {"en": ["Selected letters"]},
"summary": {"en": ["A IIIF Collection with ordinary IIIF properties"]},
"metadata": [{"label": {"en": ["Curator"]}, "value": {"en": ["The docs team"]}}],
"slug": "selected-letters",
"parent": "https://iiif.dlcs.example/99/collections/d44eeb7a",
"items": [
{
"id": "https://iiif.wellcomecollection.org/presentation/b29000798",
"type": "Collection",
"label": {"en": ["An external IIIF Collection"]}
},
{
"id": "https://example.org/iiif/manifest.json",
"type": "Manifest",
"label": {"en": ["An external manifest"]}
}
]
}

The public view is your document, served at its public URL — the same JSON apart from the platform-controlled id. The API view adds the extension @context and the same navigation and modification properties as a Storage Collection (slug, parent, publicId, flatId, partOf, created, createdBy, modified, modifiedBy). It does not have the view and totalItems properties — only Storage Collections are paged.

A IIIF Collection’s items are content, not containment: listing a Manifest in a IIIF Collection doesn’t move or contain that Manifest, it just references it. In the file-system analogy, a IIIF Collection is a file, not a directory: only Storage Collections can act as a parent. Attempting to create a resource whose parent is a IIIF Collection returns 409 Conflict, “The parent must be a storage collection”.

So a IIIF Collection lives in the hierarchy (it has a parent and a slug, and appears in its parent Storage Collection’s generated items), but nothing lives inside it except by reference.

You can search across your resources by label:

GET /99/collections/root/search?label=manuscripts
Host: iiif.dlcs.example
X-IIIF-CS-Show-Extras: All
Authorization: <(credentials here)>

Search is an API feature, not a public one — it requires credentials and the Show-Extras header, and it searches all your resources, including non-public ones. It is currently available on the root Storage Collection only; the search path under any other collection returns 404. The root’s API view advertises the service:

"service": [
{
"id": "https://iiif.dlcs.example/99/collections/root/search",
"type": "IIIFCS-Search",
"profile": "level0"
}
]

The response is a synthetic IIIF Collection — generated for the request, not stored, so it has no slug, parent or ETag. Its items are the matches, which can be Storage Collections, IIIF Collections and Manifests alike, identified by their flat URLs; seeAlso links back to the collection searched:

{
"@context": [
"http://tbc.org/iiif-repository/1/context.json",
"http://iiif.io/api/presentation/3/context.json"
],
"id": "https://iiif.dlcs.example/99/collections/root/search",
"type": "Collection",
"label": {"en": ["Search results for 'manuscripts' in 'root'"]},
"totalItems": 1,
"view": {
"@id": "https://iiif.dlcs.example/99/collections/root/search?page=1&pageSize=100",
"@type": "PartialCollectionView",
"page": 1,
"pageSize": 100,
"totalPages": 1
},
"seeAlso": [
{
"id": "https://iiif.dlcs.example/99/collections/root",
"type": "Collection",
"label": {"en": ["IIIF Home"]}
}
],
"items": [
{
"id": "https://iiif.dlcs.example/99/collections/d44eeb7a",
"type": "Collection",
"label": {"en": ["14th Century Manuscripts"]},
"behavior": ["public-iiif", "storage-collection"]
}
]
}

The query parameters:

  • label={terms} — matches against resource labels. At least one term must be 3 characters or more; a shorter query returns 400 Bad Request (…/errors/ModifyCollectionType/InvalidSearchQuery, “At least one search term must be 3 characters or more”).
  • page and pageSize — paging, exactly as on Storage Collections.
  • orderBy / orderByDescendingid, slug or created, exactly as on Storage Collections.