Content Website
Build a blog or content site with the CMS module
This guide shows how to build a content-driven website using the Arky CMS module.
Client Boundaries
Provision collections and edit content from a trusted Admin process. Keep its Personal API Token out of browser bundles. Public website reads use a separate storefront client initialized with the Store’s publishable key:
import { createAdmin } from 'arky-sdk/admin';
import { initialize } from 'arky-sdk/storefront';
const storeId = process.env.ARKY_STORE_ID!;
const sdk = createAdmin({
baseUrl: process.env.ARKY_API_URL!,
storeId,
market: 'us',
apiToken: process.env.ARKY_PERSONAL_API_TOKEN!,
});
const arky = initialize(import.meta.env.PUBLIC_ARKY_PUBLISHABLE_KEY, {
locale: 'en',
market: 'us',
});
The sdk examples below are trusted provisioning calls. The arky examples are safe public
storefront reads and do not need a Store ID or private token.
Overview
Arky CMS is built around two primitives:
- Collections define the shape of content, similar to a database table.
- Entries hold the actual content inside a collection, similar to rows.
Taxonomies stay separate. Use taxonomies for hierarchical classification and filters on products, services, providers, and content-adjacent records. Use collection schemas and blocks for CMS data modeling.
Content Structure
A blog can be modeled as a blog_posts collection:
const posts = await sdk.cms.collection.create({
store_id: storeId,
key: 'blog_posts',
schema: [
{
id: 'title',
key: 'title',
type: 'localized_text',
required: true,
properties: {},
children: []
},
{
id: 'excerpt',
key: 'excerpt',
type: 'localized_text',
required: false,
properties: {},
children: []
},
{
id: 'body',
key: 'body',
type: 'markdown',
required: true,
properties: {},
children: []
},
{
id: 'hero',
key: 'hero',
type: 'media',
required: false,
properties: { on_delete: 'set_null' },
children: []
}
]
});
Then create entries in that collection:
const post = await sdk.cms.entry.create({
store_id: storeId,
collection_id: posts.id,
key: 'launch-notes',
slug: { en: 'launch-notes' },
blocks: [
{
id: crypto.randomUUID(),
key: 'title',
type: 'localized_text',
properties: {},
value: { en: 'Launch Notes' }
},
{
id: crypto.randomUUID(),
key: 'body',
type: 'markdown',
properties: {},
value: { en: '## What changed\n\nWe shipped a cleaner CMS model.' }
}
]
});
Fetching Content
List active entries for a collection:
const { items, cursor } = await arky.cms.entry.find({
collection_id: posts.id,
status: 'active',
sort_field: 'created_at',
sort_direction: 'desc',
limit: 10
});
Get one entry by ID:
const entry = await arky.cms.entry.get({
id: post.id
});
For unique pages such as a homepage, create a pages collection and store one entry with a stable key:
const pages = await sdk.cms.collection.create({
store_id: storeId,
key: 'pages',
schema: [
{
id: 'heading',
key: 'heading',
type: 'localized_text',
required: false,
properties: {},
children: []
},
{
id: 'body',
key: 'body',
type: 'markdown',
required: false,
properties: {},
children: []
}
]
});
You do not need a special singleton type. A collection can contain one entry when the content is singular, or many entries when it is repeatable.
Entry Structure
Collections define a schema for entry blocks. Entries store blocks that are validated against that schema, while collection-level blocks can hold singleton-style content for pages or website settings.
Blocks can reference media, entries, products, and digital products by ID. Reference targets remain separate domain objects and are fetched explicitly.
Media Fields
Media stays its own primitive. CMS entries reference media through media blocks:
await sdk.cms.entry.update({
store_id: storeId,
id: post.id,
blocks: [
{
id: crypto.randomUUID(),
key: 'hero',
type: 'media',
properties: { on_delete: 'set_null' },
value: 'media_hero123'
}
]
});
Storefront responses retain the media ID. Fetch all referenced media in one explicit batch so entry reads never trigger hidden nested reads:
import { collectBlockReferences } from 'arky-sdk';
const references = collectBlockReferences(post.blocks);
const { data: media } = await arky.cms.media.findByIds({
ids: references.mediaIds
});
Search
Entries are indexed through the same event and search pipeline as the rest of the store. Use query for text search and field filters for exact CMS-specific filters:
const results = await arky.cms.entry.find({
collection_id: posts.id,
query: 'launch',
status: 'active',
limit: 20
});
Taxonomies
Use taxonomies when the value is primarily classification:
- Product categories
- Service categories
- Provider specialties
- Region or location groupings
- Filter facets that should remain outside CMS schemas
Use collections and entries when the value is content data:
- Authors
- Testimonials
- Case studies
- Page sections