Skip to content

Assets in Content

The asset library holds your images, videos and documents. Content pages use them as references: an author picks an asset in a Component, and delivery returns that asset’s details beside the rest of the page. The file is never copied into the page, so replacing a file in the library changes every page that uses it, with nothing republished.

An asset reference is an expanded reference like any other, and its provider is the name you gave the Assets registration in Content. What tells you the kind of asset is the Contract it points to:

contract.externalId Holds
ebitex-image An image, with its pixel size
ebitex-video A video, with its duration and a poster image
ebitex-document A document such as a PDF

A field that allows more than one kind delivers all of them through the same shape. Check contract.externalId, or let the SDK’s isAssetValue do it:

src/Media.tsx
// Renders a reference to an image, video or document picked from the asset library. A field can allow
// several kinds, and every kind arrives through the same shape, so the check is on the value itself.
import { isAssetValue } from '@ebitex/content-sdk'
export function Media({ value }: { value: unknown }) {
// `isAssetValue` looks at the Contract the reference points to: ebitex-image, ebitex-video or
// ebitex-document. A reference that cannot be resolved arrives without `content`, and is not an asset
// value, so it renders nothing.
if (!isAssetValue(value)) return null
const { content } = value
switch (content.kind) {
case 'image':
// `file` is already the right file for the requested locale. Its width and height are the
// pixel size of that file, so the browser can reserve the space before it loads.
return <img src={content.file.url} width={content.file.width} height={content.file.height} alt={content.alt ?? content.name} />
case 'video':
// `file.url` is a streaming manifest (HLS). Pass it to a player that plays HLS; the poster is a
// plain image.
return content.file.posterUrl ? <img src={content.file.posterUrl} alt={content.name} /> : null
case 'document':
return <a href={content.file.url}>{content.name}</a>
}
}

The content of a reference holds these members:

Member Meaning
name The asset’s name in the library
kind image, video or document
visibility public or private
thumbnail-url A small preview image, when the asset has one, as an external link: { "kind": "external", "url": "…" }
file The file itself: url, contentType and sizeBytes, plus width and height for an image, or durationSeconds and posterUrl for a video
alt Alternative text, for images
caption A caption

An image’s file starts with the same three members as a Blob (url, contentType, sizeBytes), so code that reads a Blob reads an asset’s file unchanged.

Rendering an image that may be an asset or a Blob

Section titled “Rendering an image that may be an asset or a Blob”

A field that allows both a Blob image Contract and the asset library’s ebitex-image Contract delivers two shapes. They agree on file.url, file.contentType and file.sizeBytes, and differ in two places:

Blob image Asset image
Alternative text alt-text alt
Width and height top level, width and height inside file, file.width and file.height

Switch on contract.externalId, and treat a missing dimension as absent rather than as 0:

import { isAssetValue } from '@ebitex/content-sdk'
function readImage(value) {
if (isAssetValue(value)) {
if (value.contract.externalId !== 'ebitex-image') return null // a video or document
const { file, alt } = value.content
return { url: file.url, alt: alt ?? '', width: file.width, height: file.height }
}
const { file, width, height, 'alt-text': alt } = value.content
return { url: file.url, alt: alt ?? '', width, height }
}

A video’s file.url is an HLS manifest. Safari and iOS play it in a plain <video src>; other browsers need a player such as hls.js. A document’s file.url is a direct link, so show a download link with the name, the extension of the URL and file.sizeBytes.

Pass width and height to the <img> when you have them, so the browser can reserve the space before the file loads.

An asset has a default file and, optionally, a file for any locale in your organization’s locale tree. alt and caption can differ per locale too. Delivery chooses one for the locale you ask for, along the locale tree: a request for de-AT gets the de-AT file if there is one, then the de file, then the default. It never matches by the look of the code, so de-CH gets the default unless your tree makes it a child of de. See Localization for the tree.

file is chosen as a whole: its URL and its dimensions always come from the same rendition.

With raw=true you receive the asset as all of its renditions instead: file becomes an object with a default and a locales map. An author can also set alt and caption for one placement, and a placement’s value wins over the asset’s own for that page.

A public asset’s URL is stable and open. Put it straight in an <img>.

A private asset is reachable only through a key whose channel allows private assets, and its URLs are signed: they stop working after one hour. Do not store them and do not cache a response that holds one for longer than a page view. A private asset can be made public in the library, and that is a one-way change. Revoking a public asset means deleting it.

An organization can add its own fields to each kind of asset: a photographer, a licence, an expiry date. A field is either delivered or internal. Internal fields never leave the asset library. Delivered fields are returned on every channel, as members of the asset’s document beside name and file, named by their keys:

{
"name": "Harbour at dawn",
"photographer": "Maya Ortiz",
"licence": { "provider": "core", "key": "9d4c…" },
"source-url": { "kind": "external", "url": "https://harbour.example/press" }
}

A field the asset has no value for is left out. A link is always { "kind": "external", "url": … }, a category is a reference to the organization’s shared vocabulary, a date is a UTC instant, and a number or a yes/no is the plain value. GET /assets/delivery/v1/contracts describes every delivered field, so a client can read the exact shape instead of guessing. Content reads the same description, so a delivered field appears on an asset picked in a Component after Content’s next sync.

Every asset always matches its kind’s fields. Creating or editing an asset through the management API without a value for a required field is refused with a 400, whether the asset is private or public.

Assets are not read with a Content delivery key. In the library, a channel names the folders and categories a consumer may see and whether private assets are included, and each key you mint belongs to one channel. You register the Assets provider in Content with a channel’s key, so Content sees what that channel allows, and an asset outside the channel behaves exactly as if it did not exist. Use one channel per site or audience.

Content reads the asset when a page is delivered, so a rename, a new rendition or a visibility change reaches your pages without a republish. It is not instant: the Assets registration keeps what it read for a few minutes (the registration’s cache time, 5 minutes by default), and delivery responses are cached for a short time as well. Plan for a change to appear within about ten minutes.

Delete an asset and every reference to it is delivered as unresolvable, the same descriptor any missing reference becomes. Render nothing for it, and have an author pick another asset.

The Assets Delivery API serves the same assets to your own code with a channel key. Its responses are never cached, because they can contain signed links.