Content
Collections with typed schemas, entries with slugs per language, and the block types every record uses
A collection is a content type. Stores use it two ways: a list with a schema, such as a
reviews collection whose entries each have a name, a quote and a title; and site-wide text
with no schema, such as a website collection whose text sits in the collection’s own blocks.
Blocks
Every record with blocks (collections, entries, products, variants, booking services and resources, store locations, companies, categories, customer groups and their offerings) uses the same block types:
type Block =
| { id: string; key: string; type: "text"; value: string | null }
| { id: string; key: string; type: "localized_text"; value: Record<string, string> | null }
| { id: string; key: string; type: "markdown"; value: string | null }
| { id: string; key: string; type: "localized_markdown"; value: Record<string, string> | null }
| { id: string; key: string; type: "number"; value: number | null }
| { id: string; key: string; type: "boolean"; value: boolean | null }
| { id: string; key: string; type: "date"; value: string | null }
| { id: string; key: string; type: "date_time"; value: EpochMilliseconds | null }
| { id: string; key: string; type: "geo_location"; value: { lat: number; lon: number } | null }
| { id: string; key: string; type: "media" | "entry" | "form" | "product"; value: string | null }
| { id: string; key: string; type: "array"; value: Block[] }
| { id: string; key: string; type: "object"; value: Block[] };
localized_textandlocalized_markdownhold one text per language, each one of the store’s languages.dateis a day (YYYY-MM-DD);date_timeis a moment in epoch milliseconds.media,entry,formandproducthold the id of the record they point at, at any depth: a record of the same store that exists and isn’t being deleted, never the record itself. The pointed-at record can’t be deleted while the block points at it.objectholds a list of fields, like the top level does.- Under a schema, each level’s keys are unique and defined by the schema, an array item matches its
item type by key,
requiredapplies to single values, and a missing list or object counts as empty, somin_itemsandmin_fieldscatch it.
const title: Block = {
id: crypto.randomUUID(),
key: "title",
type: "localized_text",
value: { en: "Hello", de: "Hallo" },
};
Collections
/v1/stores/{store_id}/collections sdk.content.collection.create() /v1/stores/{store_id}/collections sdk.content.collection.find() /v1/stores/{store_id}/collections/{id} sdk.content.collection.get() /v1/stores/{store_id}/collections/by-key/{key} sdk.content.collection.getByKey() /v1/stores/{store_id}/collections/{id} sdk.content.collection.update() /v1/stores/{store_id}/collections/{id}?expected_updated_at= sdk.content.collection.delete() const reviews = await sdk.content.collection.create({
store_id: storeId,
id: crypto.randomUUID(),
key: "reviews",
schema: [
{ id: crypto.randomUUID(), key: "name", type: { type: "text", required: true, min_length: 1, max_length: 80, pattern: null } },
{ id: crypto.randomUUID(), key: "quote", type: { type: "localized_markdown", required: true } },
{ id: crypto.randomUUID(), key: "photo", type: { type: "media", required: false } },
],
blocks: [],
status: { type: "active" },
});
Each schema type carries only its own rules:
| Schema type | Rules |
|---|---|
text, markdown | required, min_length, max_length, pattern |
localized_text, localized_markdown, boolean, date, date_time, geo_location, media, form, product | required |
number | required, min, max |
entry | required, collections: { type: "all" } or { type: "only", collection_ids } |
array | items (the item types, matched by key), min_items, max_items |
object | fields, min_fields, max_fields |
A schema field keeps its key and type for good, at any depth: a change of either under the same
field id is refused. A required localized_text or localized_markdown field needs text in every
store language, because no language stands in for another. An entry field limited to collections
names collections of the same store. Every other schema edit is allowed, with or without entries.
Entries that no longer fit, including ones without text in a language the store added later, keep
their blocks and stay readable; Admin flags them, their next edit must fix them, and storefront
reads leave out blocks whose field is gone.
A collection’s key is unique in the store (409 COLLECTION.KEY_TAKEN). A collection is active,
draft (being set up) or archived; draft and archived hide the collection and its entries from
storefronts. Deleting a collection deletes its entries: it
becomes deleting, its entries are removed in batches, then the collection. It is refused while
another collection’s schema limits an entry field to it or a webhook filter names it, and an entry
that a record outside the collection points at stops the cleanup until that pointer is gone.
Entries
/v1/stores/{store_id}/entries sdk.content.entry.create() /v1/stores/{store_id}/entries?collection_id= sdk.content.entry.find() /v1/stores/{store_id}/entries/{id} sdk.content.entry.get() /v1/stores/{store_id}/entries/{id} sdk.content.entry.update() /v1/stores/{store_id}/entries/{id}?expected_updated_at= sdk.content.entry.delete() const review = await sdk.content.entry.create({
store_id: storeId,
id: crypto.randomUUID(),
collection_id: reviews.id,
key: "ana-k",
slugs: { en: "ana-k", de: "ana-k" },
blocks: [
{ id: crypto.randomUUID(), key: "name", type: "text", value: "Ana K." },
{ id: crypto.randomUUID(), key: "quote", type: "localized_markdown", value: { en: "Lovely fabrics.", de: "Schöne Stoffe." } },
],
status: { type: "active" },
});
An entry is { id, store_id, collection_id, key, slugs, blocks, status, created_at, updated_at }.
Its blocks follow its collection’s schema, and entries are created and edited only in an active
collection. Keys and slugs are unique per collection (slugs per language; 409 ENTRY.KEY_TAKEN and
409 ENTRY.SLUG_TAKEN), so a blog and a news collection can both have launch. Updates and deletes carry the expected_updated_at they read;
deleting an entry is refused while another record points at it.
An entry is active, draft, scheduled with a future publish_at, or archived. It is
published only while it and its collection are active. A scheduled entry stays unpublished and
editable until publish_at, when Arky makes it active as an ordinary update; setting it back to
draft cancels the schedule. Publishing doesn’t re-check the schema.
Storefront Reads
import { initialize, collectBlockReferences } from "arky-sdk/storefront";
import { createOrderToken } from "arky-sdk/utils";
const arky = initialize("arky_pk_...", {
locale: "en",
createVisitorIdentity: () => ({
customer_id: crypto.randomUUID(),
session_id: crypto.randomUUID(),
}),
createSessionId: () => crypto.randomUUID(),
createId: () => crypto.randomUUID(),
createOrderToken,
});
const website = await arky.content.collection.getByKey({ key: "website" });
const page = await arky.content.entry.find({ collection_id: blog.id, limit: 20 });
const {
items: [post],
} = await arky.content.entry.find({ collection_id: blog.id, slug: "launch" });
const references = collectBlockReferences(post?.blocks ?? []);
const { items: media } = await arky.media.find({ ids: references.media_ids });
findtakescollection_idwithkey,slug,queryorfilters, or a list ofids. A slug’s language is the client’s locale (X-Arky-Locale).collection.getreads by{ id }andcollection.getByKeyby{ key }.- Responses never load what blocks point at. Collect the ids with
collectBlockReferencesand read each kind once;media.findtakes any number of ids and reads them 100 at a time.content.load({ collection })orcontent.load({ entry })reads one record with its media and answers its blocks in the locale, andcontent.loadEntries({ collection_id })reads every entry of a collection that way. - Read localized text with the SDK’s block helpers, which select the requested language and never fall back to another one:
import { getBlockTextValue } from "arky-sdk/storefront";
const titleBlock = post?.blocks.find((block) => block.key === "title");
const text = getBlockTextValue(titleBlock, "en");Arky never falls back to another store language. Write every localized block in each language the site shows, or handle the missing text in the frontend.