Guides · Modelling
Assets
Uploading images, video and documents to a space, alt text per locale, tags, replacing a file, private files, how files are served, and when an asset can be deleted.
Assets are uploaded files: images, video, documents. They belong to a space and are shared by all its environments, so promoting content never copies files.
Uploading
Upload in the Media library (media.obelism.studio, under Products in the sidebar), or from an asset field's picker in the entry editor. Files live in folders there; the picker opens the Space's folders too and uploads into the one it shows. Both list files newest first, a page at a time, and a search by file name looks in every folder. Agents upload with POST …/assets (multipart with a file field, or base64 JSON) through the agent API or the uploadAsset MCP tool; a file too big for the call goes to POST …/uploads/assets, whose uploadUrl takes the bytes with a PUT, then POST …/assets/from-upload.
- Files uploaded from Content or by an agent are at most 50 MB; over 25 MB, Content warns that the file adds to your storage quota. The Media library uploads larger files in parts (uploads).
- The type is read from the file's bytes, not from its name or what the upload claims. A file claiming to be a browser-renderable image (PNG, JPEG, GIF, WebP, AVIF) whose bytes prove nothing image-like is refused.
- Images get their
widthandheightmeasured — inline, multipart and signed uploads alike — for thewidthandheightattributes on the site. - Each asset has an alt text and a title per locale. The delivery API answers them in the requested locale.
Tags
Give files tags in the Media library (comma separated, up to 20 per file) to find them again: the tags above the library filter it, with how many files have each. Tags are only for finding files; they aren't part of delivery responses. Agents set tags with PATCH …/assets/{id} and filter with GET …/assets?tag=….
Replacing a file
Media → Replace file puts a new file under an existing asset, so every entry using it shows the new one without being edited: a new logo, a corrected PDF. The new file must be the same kind (an image for an image, a PDF stays an application file), since asset fields may only allow some types. Alt text, title, focal point and tags stay; size and dimensions follow the new file.
The asset's url changes (/assets/{id}/v2/{filename}), because files are cached for a year: environments whose content uses the asset get a new content version and an asset.updated webhook, so sites pick up the new URL. Old URLs keep working and serve the new file. Agents use PUT …/assets/{id}/file with the same body as uploading (multipart or base64 JSON; a filename field names the file).
Using them
An asset field stores an asset's id (a list with multiple), optionally limited to some types with mimeTypes (["image/"]). Rich text embeds assets too. In delivery responses the id is replaced by the asset:
{
"id": "asset_…",
"url": "https://content.obelism.studio/assets/asset_…/photo.jpg?w=1600&q=80",
"filename": "photo.jpg",
"mimeType": "image/jpeg",
"size": 482113,
"width": 1600,
"height": 900,
"title": "Our office",
"alt": "The office from the canal side",
"focalPoint": { "x": 0.42, "y": 0.3 }
}focalPoint is where crops should keep the image (fractions of its width and height from the top left), or null. Set it by clicking the image in its details: open the file in the Media library, or click a picked file in the entry form, and crop with it as focus (see images).
Alt text and focal point for one use
A file's alt text and focal point are its own, wherever it is used. One use can have its own instead: For this use on a picked file in the entry form, or in an asset field's value { "asset": "asset_…", "alt": { "en": "…" }, "focalPoint": { "x": 0.2, "y": 0.8 } } (either key may be left out, and alt falls back to the file's in a language it doesn't name). In rich text, select an embedded file and choose Alt for its alt text there, or send alt and focalPoint on the embeddedAsset node. In Markdown, an image's alt text is the use's: .
Under the alt text, in the media picker's pane, For this use and a file's details, Suggest alt text asks Obelism for one plain sentence describing the image in the language shown. Keep it puts it in the field, still unsaved; Try another asks again; Dismiss drops it. It takes an editor role and counts towards the same per-person limit as prompt editing (20 requests a minute). Media's library has the same button: see a file's details.
Delivery answers the same asset object with the use's alt text and focal point in place, and its url carries the focal point as focus, so the crop follows it. The file and its other uses keep their own.
Products in a file
With a Shopify store connected to the space, files in Media can be tagged with its products: Products on a file adds them, and clicking the image places each one where it is. The asset picker's product filter finds a product's files.
A use shows them only when you ask: Show its products in For this use, 🛍 on a file selected in rich text, or products: true in the value ({ "asset": "asset_…", "products": true }, or the embeddedAsset node's attribute). Delivery then adds them to that asset, in the order they were tagged:
"products": [
{ "id": "gid://shopify/Product/1001", "handle": "denim-jacket", "position": { "x": 0.42, "y": 0.6 } }
]position is the product's point in the picture (fractions of its width and height from the top left, as focalPoint), or null when it was tagged without one. How products are shown is up to your site: hotspots on the points, a list next to the image, or both. Read prices and stock from the Storefront API by id, as for a shopify field. Other uses of the file, and uses without the switch, have no products; while the space has no store connected, the list is empty.
Private files
A file only some visitors should get (a members' PDF, a price list) can be made private under Media → Make private. Its file is then only served with a signed URL: the delivery API, the agent API and the dashboard hand out its url with ?expires=…&signature=…, valid until the end of the current hour plus a day. Without a valid signature, or after it expires, the file answers 404. So keep the delivery token on your server, and only put the URL in pages the right visitors see.
- Signed URLs work with image transforms: add the query to them (the SDK's
imageUrldoes). - Private files are never kept in the shared edge cache; browsers may keep one privately until its link expires.
- Revoke links gives the file a new key: every signed URL handed out so far stops working at once, and new ones are signed with the new key.
- Making a file private moves it to a new URL and announces it like an edit (
asset.updated). The old public URL stops within a day, once the edge's copy expires, like a deleted file. Make public serves it without a signature again. - Agents use
PATCH …/assets/{id}with{ "private": true }, or{ "revokeLinks": true }.
Serving
Sites consume /assets/{id}/{filename}: the URL the delivery API hands out. An image's URL names its width and quality (?w=1600&q=80), which the route forces to what Content decides a site should get: a modern format chosen from Accept, metadata stripped, the asset's focal point for crops. Both are optional: without w (or h) an image gets its own width, and without q a quality of 80. w is never past 3840 pixels and nothing is enlarged past the original. An AVIF upload is served as it is, already small. Files that aren't images answer as they are, so they can be linked and seeked like a raw file would be. The image query parameters images documents (h, fit, format, focus) work here too.
The other files are answered from the same place as they always were:
/files/{id}/{filename}(and/files/{id}/v{n}/{filename}) serves the raw file, public and immutable, cached for a year. Files that existed before media took over keep answering raw here exactly as they did; files uploaded after get the forced optimizations above, on this host too./filesis the raw file: don't link it for images — the delivery URL is what Content decides a site should consume.- Both carry an
ETag; a request with a matchingIf-None-Matchgets304 Not Modified. - A
Range: bytes=…request for one part of a file gets206 Partial Content, so video can be seeked in Safari and on iOS. Optimized images ignoreRange. - Files are kept at Cloudflare's edge for a day;
X-Cache: HIT | MISSshows whether a response came from there.
Only types a browser shows passively are served inline when opened directly: PNG, JPEG, GIF, WebP and AVIF images, PDF, and common video and audio. Everything else (SVG included) downloads, so nothing uploaded can run as a page. Embedding is unaffected: an SVG still shows in an <img>.
In a storefront
Delivery hands out absolute URLs on https://content.obelism.studio, so any host uses them as they come: no proxy, no client SDK, and no image service of your own. Content does the resizing and re-encoding, on its own edge.
- Images: use the
urlas it comes, and build asrcsetfrom it withimageSrcSetor by replacing itswandq(the firstwin the URL wins). In a React Router or Hydrogen storefront that is a plain<img>: Hydrogen's<Image>only transforms Shopify CDN URLs with its default loader, so a Content URL gains nothing from it. - Allow the host in the CSP:
img-src https://content.obelism.studio. Only addconnect-srcfor it if a browser reads the delivery API (the headers on Oxygen). - Private files: the signed URL lasts until the end of the current hour plus a day, so don't cache a page that carries one for longer, and keep the delivery token on the server (private files).
- Products: prices and stock come from the Storefront API by
id, as for ashopifyfield; the points are in the asset when the use asks for them withproducts: true(products in a file).
Deleting
An asset can be deleted while no entry uses it, in any environment, as a draft or published. The dashboard says which entries still do. Its URL stops working within a day, once the edge's copy expires; browsers that already loaded it may keep their own copy.