Media
Use Media to manage uploaded images and files, generated image variants, accessibility text, tags, and external video records. Media separates reusable assets from Content and Collection records, which reference media by ID.
Media fields
| Field | Purpose |
|---|---|
sourceType |
upload or external |
url |
Primary delivery URL |
variants |
Generated named image sizes with URL, width, height, format, and byte size |
mimeType, size |
File type and size metadata |
width, height |
Intrinsic image dimensions |
altText |
Required accessibility description for meaningful images |
caption, description |
Editorial presentation metadata |
tags |
Asset organization and filtering |
status, isPublic |
Asset lifecycle and delivery intent |
provider, providerId, externalUrl |
External video or provider metadata |
Upload flow
Uploads are a trusted server or admin operation. Use a write-scoped token with an author-or-higher role.
- Request a presigned upload URL.
- Upload the bytes directly to the returned URL.
- Complete the media record with the returned
key.
const presign = await fetch('https://api.ctxhub.net/api/media/presign', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CTX_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fileName: 'team-photo.jpg',
contentType: 'image/jpeg',
size: file.size,
}),
}).then((response) => response.json())
await fetch(presign.uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': 'image/jpeg' },
body: file,
})
const { media } = await fetch('https://api.ctxhub.net/api/media', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CTX_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
key: presign.key,
originalName: 'team-photo.jpg',
mimeType: 'image/jpeg',
size: file.size,
altText: 'ContextHub team in the Istanbul office',
tags: ['team'],
}),
}).then((response) => response.json())
Never send the API token to the browser. If browser uploads are required, your backend should authorize the user and request the presigned URL.
Choose an image variant
Prefer a named variant appropriate to the layout, then fall back to large, medium, the first variant, and finally the root URL.
function pickMediaUrl(media, preferred = 'large') {
const variants = media?.variants || []
return variants.find((item) => item.name === preferred)?.url
|| variants.find((item) => item.name === 'large')?.url
|| variants.find((item) => item.name === 'medium')?.url
|| variants[0]?.url
|| media?.url
}
Always render known width and height to prevent layout shift, and preserve altText.
External media
Register YouTube, Vimeo, or another supported URL with POST https://api.ctxhub.net/api/media/external. Store provider metadata and a thumbnail, but embed only approved providers with a restrictive CSP.