Guides

Images

Upload the pictures of your content once, converted to WebP when they arrive, kept in R2 and served from your Hollware.

Images

An image is a file of a site. It is sent once, converted once when it arrives, kept in an R2 bucket of your account, and named in content by a reference that never changes: asset:ast_…. Agents upload it over MCP, people in the admin under Images.

A project needs two bindings in wrangler.jsonc, which projects made with 1.0 have:

"r2_buckets": [{ "binding": "MEDIA", "bucket_name": "my-hollware-media" }],
"images": { "binding": "IMAGES" },

hollware deploy creates the bucket the first time, as it creates the database. Without MEDIA, upload_asset answers unavailable. Without IMAGES, every file is kept as it came, unconverted. hollware doctor says which one is missing.

Upload an image

An agent calls upload_asset with the file in base64, its name and its alt, what the image shows for whoever cannot see it:

{ "filename": "continuum.png", "alt": "The Continuum logo on a dark background", "data": "iVBORw0KGgo…" }

It answers with the asset: its reference, its url, the format and size it is served at, and the format and size it was sent at. The same file sent again answers exists with the same asset, so an agent can upload without checking first. The same file in another site of the deployment is another asset, with an id of its own. list_assets lists the images of the site, newest first, and get_asset gets one by its id.

What a file is comes from its bytes, never from its name: JPEG, PNG, WebP, AVIF, GIF and SVG, at most 10 MB. Anything else is refused with what to do.

Show it

Write the reference wherever the image goes, in the frontmatter or in the Markdown:

---
title: Moving a monorepo to a new toolchain
img: asset:ast_e51afa61663c33d06276f786da
---

![The Continuum logo on a dark background](asset:ast_e51afa61663c33d06276f786da)

A Markdown image with no alt of its own takes the asset's when a site reads it, so the contract images-have-alt lets it through. What an image shows can change later: update_asset with its id and a new alt, or Edit what it shows on its card under Images. Every entry that shows it without an alt of its own reads the new one; the file never changes.

Sites read the URL, never the reference: getEntry over RPC and the read API turn every asset: into the image's URL, in the frontmatter and in the rendered document. Over MCP an agent reads what it wrote, references included.

The URL is <your Hollware>/assets/<site>/<id>, public and cached for a year: an asset never changes, so a new picture is a new asset.

Responsive images

When an image arrives, Hollware also makes its narrower copies, so a phone loads a small one: one for each of the site's media.widths narrower than the image, 640 and 1280 pixels by default. Each is served at <url>/<width>.

A site gets them without asking. In the HTML of an entry, and in the document a site reads, each image carries srcset, sizes and its width and height, so the page keeps its room while it loads:

<img src="https://cms.example.com/assets/blog/ast_e51a…" srcset="https://cms.example.com/assets/blog/ast_e51a…/640 640w, https://cms.example.com/assets/blog/ast_e51a…/1280 1280w, https://cms.example.com/assets/blog/ast_e51a… 1600w" sizes="(min-width: 1600px) 1600px, 100vw" width="1600" height="900" alt="…">

A size the author wrote is kept. For the images of the frontmatter, a cover, a summary a site reads (getEntry, listEntries) carries images: by URL, the width, height and srcset of each.

const cover = entry.images?.[entry.frontmatter.img]
// <img src={entry.frontmatter.img} srcset={cover?.srcset ?? undefined} width={cover?.width} …>

upload_asset, get_asset and list_assets answer widths and srcset too. Images uploaded before 1.0.0-rc.3 have no copies.

How images are converted

Each site says how in its media setting, part of its structure (read it with get_structure, change it with update_site):

"media": { "format": "webp", "quality": 85, "maxWidth": 2400, "widths": [640, 1280], "keepOriginals": true }

Those are the defaults, which a site that says nothing gets. In the admin, they are under Site, in Images.

Sent asKept as
JPEGformat at quality, lossy
PNGformat, lossless: screenshots, diagrams and logos keep their edges, and transparency
GIFformat with every frame, animated
WebP, AVIFas they came
SVGas it came, never converted

Wider than maxWidth (320 to 8192 pixels), an image is scaled down, never up. format takes webp, avif (smaller still, a little slower to encode) or original, which keeps every file as it came, without copies. widths takes up to four widths from 320 to 8192, smallest first, or [] for no copies. A change applies to the images uploaded after it.

Hollware keeps the original of each file too, never served, at /v1/assets/<id>/original with a personal token, so a later change of format has the originals to start from. A site that would rather take less storage sets keepOriginals to false (Keep the original of each image in the admin): it keeps only the file it serves and its copies, and the download of an original, and hollware export, give the file served instead. The images uploaded before keep theirs.

SVG

An SVG stays a vector and is never rewritten. Hollware accepts one only when nothing in it can run or reach out: no <script>, <foreignObject>, <iframe>, <embed> or <object>, no on… attribute, no javascript: or data: URL, no <!DOCTYPE> or <!ENTITY>, and no link outside the file. Character references and entities other than the five of XML (&amp;, &lt;, &gt;, &quot;, &apos;), backslashes and animations of a link (<set> or <animate> of href) are refused too, since they can spell what the other rules look for. Anything else is refused with the part to remove. An SVG optimizer cleans most files that come from a design tool.

It is served with a security policy that runs nothing even if a browser opens it on its own.

What it costs

The conversion is Cloudflare Images on R2 storage. Each new image is one transformation, two when it is wider than maxWidth, and one more for each copy; an image sent again costs nothing. When the allowance runs out while making the copies, the image keeps those already made. The free plan includes 5,000 transformations a month: past them, an upload answers unavailable until the next month, and nothing is charged. R2 keeps 10 GB free.

Your images on your site

The URLs of the assets are on the domain of your Hollware, which tells search engines not to index it. A site that wants its pictures indexed on its own domain serves them itself:

npx hollware export ./content --published --assets public/images/cms --assets-url /images/cms/ --url https://cms.example.com --token-file export.token

The token is a personal one with content:read: hollware token create --label export --scopes content:read --remote.

--assets writes each image the published entries show into that directory, as it is served, and every asset: reference in the export points at --assets-url instead. A static site builds from that export.

Without --assets, an export keeps the references as they are and writes the images, as they were uploaded, to _assets/ with an assets.json beside them. hollware import uploads them again before the entries that show them, so an export is a backup of your content and of its pictures.