Back to Arky

Media

Uploaded images, videos and PDFs with named smaller versions, alt text per language, and replacement

A media record is one public file in the store’s library: an image, a video or a PDF. An image comes with four named smaller versions, always WebP. Every file carries its public url; storage keys are never exposed.

import type { EpochMilliseconds } from "arky-sdk";

interface Media {
	id: string;
	store_id: string;
	file_name: string;
	alt: Record<string, string> | null;
	generation_id: string;
	content:
		| {
				type: "image";
				format: "jpeg" | "png" | "webp" | "gif";
				original: MediaImage;
				thumbnail: MediaImage;
				small: MediaImage;
				medium: MediaImage;
				large: MediaImage;
		  }
		| { type: "video"; format: "mp4" | "webm" | "quicktime"; original: MediaFile }
		| { type: "pdf"; original: MediaFile };
	status: { type: "active" } | { type: "deleting" };
	created_at: EpochMilliseconds;
	updated_at: EpochMilliseconds;
}

interface MediaFile {
	size_bytes: number;
	sha256: string;
	url: string;
}

interface MediaImage extends MediaFile {
	width_px: number;
	height_px: number;
}

Upload

PUT /v1/stores/{store_id}/media/{id}
SDK: sdk.media.create()

The app picks the media id and sends exactly one file or one source URL, as multipart form data:

const media = await sdk.media.create({
	store_id: storeId,
	id: crypto.randomUUID(),
	file: fileInput.files[0],
	alt: { en: "Linen shirt on a wooden table" },
});

const imported = await sdk.media.create({
	store_id: storeId,
	id: crypto.randomUUID(),
	source_url: "https://example.com/product-hero.jpg",
});

Parameters

Name Type Description
id required string The media id the app picks (UUID v4); retry with the same id
file optional File One uploaded file; not with source_url
source_url optional string A public URL Arky downloads; not with file
alt optional Record<string, string> | null Alt text per store language

A retry with the same id and the same bytes returns the existing media; another file under that id is refused. Uploads count against the plan’s media limit, which counts files, not bytes.

  • A source_url must be an https address that stays public at every hop, with at most three redirects, within the size limit.
  • The record commits only after every stored file is proven by its length and SHA-256. An image’s smaller versions are WebP within their bounding boxes.

Read, Find, Update, Replace

GET /v1/stores/{store_id}/media/{id}
SDK: sdk.media.get()
GET /v1/stores/{store_id}/media
SDK: sdk.media.find()
PATCH /v1/stores/{store_id}/media/{id}
SDK: sdk.media.update()
PUT /v1/stores/{store_id}/media/{id}/content
SDK: sdk.media.replaceContent()
const page = await sdk.media.find({
	store_id: storeId,
	type: "image",
	sort_field: "created_at",
	sort_direction: "desc",
	limit: 50,
});

await sdk.media.update({
	store_id: storeId,
	id: media.id,
	expected_updated_at: media.updated_at,
	alt: { en: "Linen shirt", de: "Leinenhemd" },
});

await sdk.media.replaceContent({
	store_id: storeId,
	id: media.id,
	expected_updated_at: media.updated_at,
	file: newFile,
});

Replacing the file keeps the id and its URLs, so everything that points at the media, sent emails and built pages included, shows the new file. The new files go into a new generation_id, whose ?v= token is part of each URL, and the old files are deleted after the change commits. Replacing with the same bytes changes nothing.

A public read streams the stored file itself, never a storage redirect, and answers Cache-Control: public, max-age=31536000, immutable when ?v= names the current generation and public, max-age=60 otherwise, so an old link shows a replacement within a minute.

Delete

DELETE /v1/stores/{store_id}/media/{id}?expected_updated_at=
SDK: sdk.media.delete()

Deleting is refused while any record points at the file: a block, an email template body ({{media "<id>"}}) or a broadcast body. Otherwise the media becomes deleting, its files are removed from storage, and then the record goes. Webhooks post media.created, media.updated and media.deleted.

Storefront

import { getImageUrl } from "arky-sdk/storefront";

const {
	items: [hero],
} = await arky.media.find({ ids: [heroMediaId] });
const url = getImageUrl(hero, "large");
const alt = hero.alt?.en ?? "";

getImageUrl returns the named smaller version of an image and the original file of a video or a PDF.

Note

Files customers send with a form aren’t media. They are stored privately with the form submission; see Forms.