| name | cosmic |
| description | Build applications with Cosmic headless CMS using the JavaScript SDK. Covers Objects, Media, AI generation (text, images, video), content modeling with Metafields, and queries. Use when working with Cosmic CMS, @cosmicjs/sdk, headless content, or building JAMstack applications. Use when this capability is needed. |
| metadata | {"author":"cosmicjs"} |
Cosmic CMS
Build content-powered applications with the Cosmic headless CMS.
SDK-First Principle
Always use the @cosmicjs/sdk package. The SDK provides type-safe methods, better error handling, and cleaner syntax than raw REST calls.
bun add @cosmicjs/sdk
Setup
import { createBucketClient } from '@cosmicjs/sdk'
const cosmic = createBucketClient({
bucketSlug: 'BUCKET_SLUG',
readKey: 'BUCKET_READ_KEY',
writeKey: 'BUCKET_WRITE_KEY'
})
Find keys in Cosmic Dashboard → Bucket → Settings → API Access.
Objects (Content)
Objects are the core content units. Each Object belongs to an Object type (like "posts", "products").
Read Objects
const posts = await cosmic.objects
.find({ type: 'posts' })
.props(['id', 'title', 'slug', 'metadata'])
.limit(10)
const post = await cosmic.objects
.findOne({ type: 'posts', slug: 'hello-world' })
.props(['title', 'slug', 'metadata'])
const obj = await cosmic.objects
.findOne({ id: 'object-id' })
.props(['title', 'metadata'])
Props Syntax
Use GraphQL-style syntax for nested properties:
const props = `{
id
slug
title
metadata {
content
author {
title
metadata {
avatar { imgix_url }
}
}
}
}`
await cosmic.objects.find({ type: 'posts' }).props(props)
Create Object
await cosmic.objects.insertOne({
title: 'My Post',
type: 'posts',
metadata: {
content: 'Post content here...',
author: 'author-object-id',
featured_image: 'image-name.jpg'
}
})
Update Object
await cosmic.objects.updateOne('object-id', {
title: 'Updated Title',
metadata: {
featured: true,
categories: ['cat1-id', 'cat2-id']
}
})
Delete Object
await cosmic.objects.deleteOne('object-id')
Batch Operations
Create, update, and delete multiple Objects in a single call (max 25 operations). Each operation succeeds or fails independently.
const result = await cosmic.objects.batch([
{ method: 'add', object: { title: 'Post 1', type: 'posts', metadata: { content: '...' } } },
{ method: 'add', object: { title: 'Post 2', type: 'posts', metadata: { content: '...' } } },
{ method: 'edit', object_id: 'OBJECT_ID', object: { title: 'Updated' } },
{ method: 'delete', object_id: 'OBJECT_ID_2' },
])
Object Types
Define content structure with Object types.
const types = await cosmic.objectTypes.find()
const blogType = await cosmic.objectTypes.findOne('posts')
await cosmic.objectTypes.insertOne({
title: 'Blog Posts',
slug: 'posts',
singular: 'Post',
emoji: '📝',
metafields: [
{ title: 'Content', key: 'content', type: 'markdown', required: true },
{ title: 'Image', key: 'image', type: 'file', media_validation_type: 'image' },
{ title: 'Author', key: 'author', type: 'object', object_type: 'authors' },
{ title: 'Tags', key: 'tags', type: 'objects', : }
]
})
Metafield Types
| Type | Description | Value Format |
|---|
text | Single line text | string |
textarea | Multi-line text | string |
markdown | Markdown editor | string |
html-textarea | Rich HTML editor | string |
number | Numeric value | number |
date | Date picker | "YYYY-MM-DD" |
switch | Boolean toggle | true/false |
select | Single selection (preferred) | string |
multi-select | Multiple selection | string[] |
select-dropdown | Dropdown selection (deprecated, use select) | {key: string, value: string} |
radio-buttons | Radio selection | string |
check-boxes | Multiple selection | string[] |
file | Single media | Media name |
files | Multiple media | Media name[] |
object | Single relation | Object id |
objects | Multiple relations | Object id[] |
json | JSON data | object |
color |
Metafield Validation
Metafields support validation properties to enforce data quality:
| Property | Type | Description |
|---|
required | boolean | A value must be provided |
unique | boolean | Value must be unique across all Objects of the same type. Applies to top-level text, textarea, number, date, select Metafields (not supported inside Parent or Repeater groups) |
show_when | object | Conditional visibility: { key, op, value }. Show field when sibling field matches condition. Ops: eq, neq, exists, not_exists. Hidden fields skip required validation. Top-level only. |
regex | string | Restrict value to match a regular expression |
regex_message | string | Message shown when regex validation fails |
minlength | number | Minimum character length (text, textarea) |
maxlength | number | Maximum character length (text, textarea) |
await cosmic.objectTypes.insertOne({
title: 'Contacts',
slug: 'contacts',
metafields: [
{ title: 'Email', key: 'email', type: 'text', required: true, unique: true },
{ title: 'Name', key: 'name', type: 'text', required: true, minlength: 2 }
]
})
Queries
Filter content using MongoDB-style queries:
await cosmic.objects.find({
type: 'products',
'metadata.category': 'electronics'
})
await cosmic.objects.find({
type: 'products',
'metadata.price': { $lt: 100 }
})
await cosmic.objects.find({
type: 'products',
'metadata.tags': { $in: ['sale', 'featured'] }
})
await cosmic.objects.find({
type: 'products',
$and: [
{ 'metadata.price': { $lte: 50 } },
{ 'metadata.in_stock': true }
]
})
await cosmic.objects.find({
type: 'posts',
title: { : , : }
})
Query Options
await cosmic.objects.find({ type: 'posts' })
.props(['title', 'slug', 'metadata'])
.sort('-created_at')
.limit(10)
.skip(20)
.depth(2)
.status('any')
Media
const media = await cosmic.media
.find({ folder: 'images' })
.props(['url', 'imgix_url', 'alt_text'])
.limit(20)
const uploaded = await cosmic.media.insertOne({
media: { originalname: 'photo.jpg', buffer: fileBuffer },
folder: 'uploads',
alt_text: 'Description of image',
metadata: { caption: 'Photo caption' }
})
await cosmic.media.updateOne('media-id', {
alt_text: 'Updated alt text',
folder: 'new-folder'
})
await cosmic.media.deleteOne('media-id')
imgix Image Processing
All images have imgix_url for transformations:
const optimized = `${media.imgix_url}?w=800&auto=format,compress`
const thumbnail = `${media.imgix_url}?w=100&h=100&fit=crop`
AI Generation
Cosmic provides built-in AI capabilities for text, images, and video.
Generate Text
const text = await cosmic.ai.generateText({
prompt: 'Write a product description for a coffee mug',
model: 'claude-sonnet-4-5-20250929',
max_tokens: 500
})
console.log(text.text)
const chat = await cosmic.ai.generateText({
messages: [
{ role: 'user', content: 'Tell me about coffee' },
{ role: 'assistant', content: 'Coffee is a beverage...' },
{ role: 'user', content: 'What about espresso?' }
]
})
const analysis = await cosmic.ai.generateText({
prompt: 'Describe this image',
media_url: 'https://cdn.cosmicjs.com/image.jpg'
})
const stream = await cosmic.ai.stream({
prompt: 'Write a blog post',
max_tokens: 1000
})
( chunk stream) {
process..(chunk. || )
}
Generate Image
const image = await cosmic.ai.generateImage({
prompt: 'Mountain landscape at sunset',
model: 'gemini-3-pro-image-preview',
size: '1024x1024',
folder: 'ai-generated',
alt_text: 'AI mountain landscape'
})
console.log(image.media.url)
const styled = await cosmic.ai.generateImage({
prompt: 'Same style but with ocean',
reference_images: ['https://cdn.cosmicjs.com/style-ref.jpg']
})
Generate Video
const video = await cosmic.ai.generateVideo({
prompt: 'A kitten playing with yarn in sunlight',
model: 'veo-3.1-fast-generate-preview',
duration: 8,
resolution: '720p',
folder: 'videos'
})
console.log(video.media.url)
const productVideo = await cosmic.ai.generateVideo({
prompt: 'Product rotates smoothly',
reference_images: ['https://cdn.cosmicjs.com/product.jpg'],
duration: 6
})
const extended = await cosmic.ai.extendVideo({
media_id: video.media.id,
prompt: 'The kitten walks away into the garden'
})
Available AI Models
Text: claude-sonnet-4-5-20250929 (recommended), claude-opus-4-5-20251101, gpt-5, gemini-3-pro-preview
Image: gemini-3-pro-image-preview (default, up to 4K), dall-e-3
Video: veo-3.1-fast-generate-preview (recommended), veo-3.1-generate-preview (premium)
Framework Patterns
Next.js App Router
import { createBucketClient } from '@cosmicjs/sdk'
const cosmic = createBucketClient({
bucketSlug: process.env.COSMIC_BUCKET_SLUG!,
readKey: process.env.COSMIC_READ_KEY!
})
export default async function Posts() {
const { objects: posts } = await cosmic.objects
.find({ type: 'posts' })
.props(['title', 'slug', 'metadata.excerpt'])
.limit(10)
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Server Actions
'use server'
export async function createPost(formData: FormData) {
const cosmic = createBucketClient({
bucketSlug: process.env.COSMIC_BUCKET_SLUG!,
readKey: process.env.COSMIC_READ_KEY!,
writeKey: process.env.COSMIC_WRITE_KEY!
})
await cosmic.objects.insertOne({
title: formData.get('title') as string,
type: 'posts',
metadata: {
content: formData.get('content')
}
})
}
Common Patterns
Pagination
const page = 1
const limit = 10
const { objects, total } = await cosmic.objects
.find({ type: 'posts' })
.skip((page - 1) * limit)
.limit(limit)
const totalPages = Math.ceil(total / limit)
Draft Preview
const post = await cosmic.objects
.findOne({ type: 'posts', slug })
.status('any')
Localized Content
const post = await cosmic.objects
.findOne({ type: 'posts', slug, locale: 'es' })
Select and Multi-Select Values
For new content models, prefer select (single selection) and multi-select (multiple selection) over the legacy select-dropdown type.
select returns a plain string from the API. No helper needed.
multi-select returns a string[] from the API. No helper needed.
select-dropdown (deprecated) returns {key: string, value: string} objects, which can cause "Objects are not valid as a React child" errors in JSX.
<span>{product.metadata?.status}</span>
{product.metadata?.tags?.map(tag => <span key={tag}>{tag}</span>)}
If your project still uses legacy select-dropdown fields, include this helper in your project (e.g., lib/cosmic.ts):
export function getMetafieldValue(field: unknown): string {
if (field === null || field === undefined) return '';
if (typeof field === 'string') return field;
if (typeof field === 'number' || typeof field === 'boolean') return String(field);
if (typeof field === 'object' && field !== null && 'value' in field) {
return String((field as { value: unknown }).value);
}
if (typeof field === 'object' && field !== null && 'key' in field) {
return String((field as { key: unknown }).key);
}
return '';
}
The function is safe for all types (strings, numbers, booleans pass through unchanged), so wrap legacy select-dropdown metadata values rendered in JSX:
<span>{product.metadata?.category}</span>
<span>{getMetafieldValue(product.metadata?.category)}</span>
Key Reminders
- Object type is the SLUG - Use
type: 'blog-posts', not type: 'Blog Posts'
- Media uses
name - Reference media by name property, not URL
- Relations use
id - Reference related Objects by id, not slug
- Never expose
writeKey - Keep it server-side only
- Use
props() - Always specify needed properties for performance
- imgix for images - Use
imgix_url with query params for optimizations
- Use
select over select-dropdown - New content models should use select (returns plain strings) instead of select-dropdown (returns objects)
- Wrap legacy metadata in JSX - Use
getMetafieldValue() for select-dropdown metadata values rendered in JSX
Resources
Converted and distributed by TomeVault — claim your Tome and manage your conversions.