All skills

Read and write Cosmic CMS content with the @cosmicjs/sdk JavaScript/TypeScript SDK - objects, queries, media, imgix image transforms, and AI text/image/video/audio generation. Also covers provisioning a free Cosmic project when the user has no credentials yet. Use when the user is building against Cosmic, mentions @cosmicjs/sdk, createBucketClient, COSMIC_BUCKET_SLUG, COSMIC_READ_KEY, or COSMIC_WRITE_KEY, when fetching or mutating content from a Cosmic bucket, or when they want to add a headless CMS to an app and have no Cosmic account yet.

  • 2 files
  • 15.5 KB
  • MIT
  • Updated 2 months ago
  • GitHub

Use this Skill: https://skilld.dev/gh/cosmicjs/cosmic-agent-plugin/cosmic-sdk

This session only. Nothing lands on disk.

SKILL.md

≈140 tokens always: the name and description. ≈2.8k when used: this file. ≈1k more on demand in 1 file.

Cosmic SDK

Read and write content in Cosmic, an AI-powered headless CMS. Cosmic exposes a REST API; @cosmicjs/sdk is the supported JavaScript and TypeScript client for it.

For designing object types and metafields, see the cosmic-content-modeling skill.

SDK-first principle

Always use the @cosmicjs/sdk package rather than hand-rolled fetch calls against the REST API. The SDK gives you types, error handling, and a chainable query builder.

bun add @cosmicjs/sdk
# or: npm install @cosmicjs/sdk

Setup

import { createBucketClient } from '@cosmicjs/sdk'

const cosmic = createBucketClient({
  bucketSlug: process.env.COSMIC_BUCKET_SLUG!,
  readKey: process.env.COSMIC_READ_KEY!,
  writeKey: process.env.COSMIC_WRITE_KEY, // Required for create/update/delete
})

Keys live in the Cosmic dashboard under Bucket → Settings → API Access.

Key rules:

  • The read key authorizes reads. The write key authorizes writes.
  • Never expose the write key client-side. Construct any client that carries a write key in server-only code: a server component, route handler, server action, or backend service. A write key shipped to the browser lets anyone modify the bucket.

No Cosmic account yet?

If the user has no COSMIC_* env vars, no .env, and no account on app.cosmicjs.com, do not send them off to sign up by hand. The Cosmic free plan is free forever and needs no credit card, and one API call provisions a project and returns working bucket keys.

Read references/agent-signup.md for the full flow: POST /v3/agents/sign-up, the email OTP the human has to relay back, and POST /v3/agents/verify to lift the restricted-mode limits.

If the user already has Cosmic credentials, skip that entirely.

Objects

Objects are the content units. Every object belongs to an object type such as posts or products.

Read

// Multiple objects
const { objects: posts } = await cosmic.objects
  .find({ type: 'posts' })
  .props(['id', 'title', 'slug', 'metadata'])
  .limit(10)

// Single object by slug
const post = await cosmic.objects
  .findOne({ type: 'posts', slug: 'hello-world' })
  .props(['title', 'slug', 'metadata'])

// Single object by id
const obj = await cosmic.objects.findOne({ id: 'object-id' }).props(['title', 'metadata'])

Nested props syntax

props() also accepts a brace-delimited string to select nested fields, which avoids over-fetching on deep relationships:

const props = `{
  id
  slug
  title
  metadata {
    content
    author {
      title
      metadata {
        avatar { imgix_url }
      }
    }
  }
}`
await cosmic.objects.find({ type: 'posts' }).props(props)

Create

await cosmic.objects.insertOne({
  title: 'My Post',
  type: 'posts', // object type SLUG, not title
  metadata: {
    content: 'Post content here...',
    author: 'author-object-id',        // object relationship: use id
    featured_image: 'image-name.jpg',  // media: use the name property
  },
})

Update

await cosmic.objects.updateOne('object-id', {
  title: 'Updated Title',
  metadata: {
    featured: true,
    categories: ['cat1-id', 'cat2-id'], // multiple objects: array of ids
  },
})

Delete

await cosmic.objects.deleteOne('object-id')

Batch

Create, update, and delete multiple objects in one call, up to 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' },
])
// result.operations: [{ method, status, object/message }, ...]

Queries

Filter with MongoDB-style query operators:

// Basic filter
await cosmic.objects.find({ type: 'products', 'metadata.category': 'electronics' })

// Comparison: $lt, $gt, $gte, $lte, $eq, $ne
await cosmic.objects.find({ type: 'products', 'metadata.price': { $lt: 100 } })

// Arrays: $in (any), $all (all), $nin (none)
await cosmic.objects.find({ type: 'products', 'metadata.tags': { $in: ['sale', 'featured'] } })

// Logical: $and, $or, $not, $nor
await cosmic.objects.find({
  type: 'products',
  $and: [{ 'metadata.price': { $lte: 50 } }, { 'metadata.in_stock': true }],
})

// Text search
await cosmic.objects.find({ type: 'posts', title: { $regex: 'hello', $options: 'i' } })

Query options

await cosmic.objects
  .find({ type: 'posts' })
  .props(['title', 'slug', 'metadata'])
  .sort('-created_at') // descending by created date
  .limit(10)
  .skip(20)            // pagination
  .depth(2)            // resolve nested object relationships
  .status('any')       // 'published' | 'draft' | 'any'

Media

// List
const media = await cosmic.media
  .find({ folder: 'images' })
  .props(['url', 'imgix_url', 'alt_text'])
  .limit(20)

// Upload
const uploaded = await cosmic.media.insertOne({
  media: { originalname: 'photo.jpg', buffer: fileBuffer },
  folder: 'uploads',
  alt_text: 'Description of image',
  metadata: { caption: 'Photo caption' },
})

// Update
await cosmic.media.updateOne('media-id', { alt_text: 'Updated alt text', folder: 'new-folder' })

// Delete
await cosmic.media.deleteOne('media-id')

imgix transforms

Every image has an imgix_url that accepts transform query parameters:

const optimized = `${media.imgix_url}?w=800&auto=format,compress`
const thumbnail = `${media.imgix_url}?w=100&h=100&fit=crop`

AI generation

Text

// Prompt
const text = await cosmic.ai.generateText({
  prompt: 'Write a product description for a coffee mug',
  model: 'claude-sonnet-4-5-20250929', // optional
  max_tokens: 500,
})
console.log(text.text)

// Chat messages
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?' },
  ],
})

// Analyze an image or document
const analysis = await cosmic.ai.generateText({
  prompt: 'Describe this image',
  media_url: 'https://cdn.cosmicjs.com/image.jpg',
})

// Streaming
const stream = await cosmic.ai.stream({ prompt: 'Write a blog post', max_tokens: 1000 })
for await (const chunk of stream) {
  process.stdout.write(chunk.text || '')
}

Images

const image = await cosmic.ai.generateImage({
  prompt: 'Mountain landscape at sunset',
  model: 'gemini-3-pro-image-preview', // default, supports up to 4K
  size: '1024x1024',
  folder: 'ai-generated',
  alt_text: 'AI mountain landscape',
})
console.log(image.media.url)

// With reference images (Gemini only)
const styled = await cosmic.ai.generateImage({
  prompt: 'Same style but with ocean',
  reference_images: ['https://cdn.cosmicjs.com/style-ref.jpg'],
})

Video

const video = await cosmic.ai.generateVideo({
  prompt: 'A kitten playing with yarn in sunlight',
  model: 'veo-3.1-fast-generate-preview', // fast: 30-90s generation
  duration: 8,        // 4, 6, or 8 seconds
  resolution: '720p', // or '1080p'
  folder: 'videos',
})
console.log(video.media.url)

// Reference image as first frame
const productVideo = await cosmic.ai.generateVideo({
  prompt: 'Product rotates smoothly',
  reference_images: ['https://cdn.cosmicjs.com/product.jpg'],
  duration: 6,
})

// Extend an existing video
const extended = await cosmic.ai.extendVideo({
  media_id: video.media.id,
  prompt: 'The kitten walks away into the garden',
})

Audio

Text to speech. The generated file is uploaded to the media library like images and video.

const audio = await cosmic.ai.generateAudio({
  prompt: 'Welcome to the show. Today we are talking about coffee.',
  voice: 'nova', // default
  model: 'tts-1', // or 'tts-1-hd'
  folder: 'audio',
})
console.log(audio.media.url)

Voices: alloy, ash, coral, echo, fable, nova, onyx, sage, shimmer.

Available 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)

Audio: tts-1 (default), tts-1-hd

Framework patterns

Next.js App Router

// app/posts/page.tsx
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.slug}>{post.title}</li>
      ))}
    </ul>
  )
}

This client has no write key, so it is safe to render from.

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') },
  })
}

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' })

CLI

@cosmicjs/cli covers bucket and project tasks from the terminal:

npx @cosmicjs/cli --help

Key reminders

  1. No credentials? Provision a project. See references/agent-signup.md. Never tell the user to go sign up by hand.
  2. Object type is the slug. Use type: 'blog-posts', not type: 'Blog Posts'.
  3. Media is referenced by name, not by URL.
  4. Relationships are referenced by id, not by slug.
  5. Never expose the write key client-side. Server-side only.
  6. Always pass props() so you fetch only the fields you render.
  7. Use imgix_url with query parameters for image sizing and format.

Resources

Source: SKILL.md on GitHub

No third-party reports yet.

Signed by skilld at b2c4ac8. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Steadyupdated 2 months ago

README badge

README badge for cosmicjs/cosmic-agent-plugin/cosmic-sdk