Copied to clipboard!

The Vibe CMS TypeScript SDK provides a powerful, framework-agnostic way to query and manage content. This chapter covers installation, basic usage, and integration patterns across popular frameworks.

SDK Overview

The Vibe CMS SDK is a lightweight TypeScript library that:

  • Works in any JavaScript/TypeScript environment
  • Provides type-safe content queries
  • Supports real-time caching
  • Handles file assets and transformations
  • Integrates with React, Vue, Svelte, Astro, Next.js, and more

Key Features:

  • Chainable query API
  • Automatic response parsing
  • Built-in browser caching
  • Asset URL generation and transformation
  • TypeScript types for all responses
  • Zero dependencies

Installation

npm

npm install @vibe-cms/sdk

yarn

yarn add @vibe-cms/sdk

pnpm

pnpm add @vibe-cms/sdk

CDN (browser)

<script src="https://cdn.jsdelivr.net/npm/@vibe-cms/sdk@latest/dist/index.min.js"></script>
<script>
  const cms = window.VibeCMS.createVibeCMS({ projectId: 'xxx' });
</script>

Basic Setup

Creating a Client

import { createVibeCMS } from '@vibe-cms/sdk';

// Minimal configuration
const cms = createVibeCMS({
  projectId: 'your-project-id'
});

Configuration Options

const cms = createVibeCMS({
  // Required
  projectId: 'your-project-id',
  
  // Optional - API configuration
  baseUrl: 'https://api.vibe-cms.com',  // Default
  apiKey: 'vib_...',                     // For authenticated calls
  
  // Optional - Caching
  cache: {
    enabled: true,
    storage: 'localStorage',  // or 'sessionStorage'
    ttl: 300000,             // 5 minutes
  },
  
  // Optional - Localization
  defaultLocale: 'en-US',
  fallbackLocale: 'en-US',
  fallbackChain: ['fr-CA', 'fr', 'en-US'],
  
  // Optional - Behavior
  throwOnValidationError: false,  // Return errors instead of throwing
  strictMode: false,              // Strict field validation
});

Environment Variables

Astro (.env):

PUBLIC_CMS_PROJECT_ID=ead059e8-bbc0-4a4c-a273-b8f9510d9511
CMS_API_KEY=vib_sk_12345...

Next.js (.env.local):

NEXT_PUBLIC_CMS_PROJECT_ID=ead059e8-bbc0-4a4c-a273-b8f9510d9511
CMS_API_KEY=vib_sk_12345...

React/Vue/Svelte (.env):

VITE_CMS_PROJECT_ID=ead059e8-bbc0-4a4c-a273-b8f9510d9511
VITE_CMS_API_KEY=vib_sk_12345...

Initialize from environment:

const cms = createVibeCMS({
  projectId: import.meta.env.VITE_CMS_PROJECT_ID,
  apiKey: import.meta.env.VITE_CMS_API_KEY,
});

Collection Queries

Query Methods

The SDK provides four main query methods:

.first() - Single Item (or First)

Get a single content item (usually the only item in a collection):

// Get landing hero (assumes only 1 item)
const hero = await cms.collection('landing-hero').first();
console.log(hero.data.title);  // Access nested content data

// Result structure
// {
//   id: 'item-id',
//   data: {
//     title: 'Welcome',
//     description: '...',
//     image_url: '...'
//   },
//   locale: 'en-US',
//   assetManager: { ... }
// }

.all() - All Items

Fetch all items in a collection:

const allPosts = await cms.collection('blog-posts').all();

console.log(allPosts.length);  // Array length
console.log(allPosts[0].data.title);  // First item data

// Result structure
// [
//   { id: '...', data: { title: '...', ... }, locale: 'en-US' },
//   { id: '...', data: { title: '...', ... }, locale: 'en-US' },
//   ...
// ]

.many() - Paginated Items

Fetch items with pagination:

// Get items 0-19
const page1 = await cms.collection('blog-posts').many({
  limit: 20,
  offset: 0
});

// Get next page (items 20-39)
const page2 = await cms.collection('blog-posts').many({
  limit: 20,
  offset: 20
});

// With sorting
const sorted = await cms.collection('blog-posts').many({
  limit: 20,
  offset: 0,
  sort_by: 'created_at',
  sort_direction: 'desc'  // Newest first
});

.slug() - By Slug

Fetch a single item by slug (URL-friendly identifier):

// Get post by slug
const post = await cms.collection('blog-posts').slug('my-first-post');

console.log(post.data.title);  // Direct access to data

// Results in 404 error if not found (handle with try/catch)
try {
  const post = await cms.collection('blog-posts').slug('nonexistent');
} catch (error) {
  console.error('Post not found');
}

Query Chaining

Combine query methods for flexibility:

// Filter and get specific locale
const frenchPosts = await cms.collection('blog-posts')
  .locale('fr-FR')  // Set locale
  .many({ limit: 10 });  // Get 10 items

// Get content and filter by status
const draftPosts = await cms.collection('blog-posts')
  .status('draft')  // Draft content
  .all();

// Complex query
const recentPosts = await cms.collection('blog-posts')
  .locale('en-US')
  .status('published')
  .many({
    limit: 5,
    offset: 0,
    sort_by: 'created_at',
    sort_direction: 'desc'
  });

Response Data Extraction

Vibe CMS SDK returns responses with nested data structures. Use the extractData helper functions:

Using Helper Functions (Recommended)

import { createVibeCMS, extractData, extractCollection } from '@vibe-cms/sdk';

const cms = createVibeCMS({ projectId: 'xxx' });

// For .first() queries
const heroResult = await cms.collection('landing-hero').first();
const hero = extractData(heroResult);
console.log(hero.title);  // Direct access, no nesting

// For .all() or .many() queries
const postsResult = await cms.collection('blog-posts').all();
const posts = extractCollection(postsResult);
console.log(posts[0].title);  // Each item has data extracted

Manual Extraction

If not using helpers:

// .first() query
const result = await cms.collection('landing-hero').first();
const data = result.data.data;  // Navigate nested structure

// .all() query
const results = await cms.collection('blog-posts').all();
const posts = results.map(item => item.data);

TypeScript Types

Defining Collection Types

// Define your content structure
interface BlogPost {
  title: string;
  slug: string;
  content: string;
  excerpt: string;
  featured_image: string;
  author: string;
  published_at: string;
  tags: string[];
}

interface LandingHero {
  title: string;
  subtitle: string;
  call_to_action: string;
  background_image: string;
  button_url: string;
}

Typed Queries

import { extractData, extractCollection } from '@vibe-cms/sdk';

// Typed .first() query
const heroResult = await cms.collection('landing-hero').first();
const hero = extractData<LandingHero>(heroResult);
// hero is typed as LandingHero - full IDE autocomplete

// Typed .all() query
const postsResult = await cms.collection('blog-posts').all();
const posts = extractCollection<BlogPost>(postsResult);
// posts is typed as BlogPost[] - full IDE autocomplete

// Usage with types
console.log(hero.title);           // ✓ Works
console.log(posts[0].featured_image);  // ✓ Works
console.log(hero.nonexistent);     // ✗ TypeScript error

Generated Types from API

# Generate TypeScript types from your project's schema
npx vibe-cms-sdk generate-types --project-id xxx --output ./src/types/cms.ts

Generated output (src/types/cms.ts):

export interface BlogPost {
  title: string;
  slug: string;
  content: string;
  featured_image?: string;
  // All fields from your collection schema
}

export interface LandingHero {
  title: string;
  subtitle: string;
  call_to_action: string;
  // ...
}

Asset Management

Asset URLs

const post = await cms.collection('blog-posts').slug('my-post');
const imageUrl = post.assetManager.getAssetUrl(
  post.data.featured_image,
  { width: 800, height: 600 }
);

// Or use SDK directly
const imageUrl = cms.assetUrl('asset-id', {
  width: 800,
  height: 600,
  format: 'webp',
  quality: 80
});

Asset Transformations

// Generate multiple image sizes
const thumbnail = cms.assetUrl('asset-id', { width: 200, height: 200 });
const medium = cms.assetUrl('asset-id', { width: 600, height: 400 });
const large = cms.assetUrl('asset-id', { width: 1200, height: 800 });
const webp = cms.assetUrl('asset-id', { width: 1200, format: 'webp' });
const avif = cms.assetUrl('asset-id', { width: 1200, format: 'avif' });

Download Assets

// Download file content
const assetData = await cms.downloadAsset('asset-id');
console.log(assetData.content);  // File contents
console.log(assetData.mimeType);  // application/pdf, image/png, etc

Framework Integration

React

import { useEffect, useState } from 'react';
import { createVibeCMS, extractCollection } from '@vibe-cms/sdk';

const cms = createVibeCMS({ projectId: 'xxx' });

function BlogList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchPosts = async () => {
      try {
        const result = await cms.collection('blog-posts').all();
        setPosts(extractCollection(result));
      } finally {
        setLoading(false);
      }
    };
    fetchPosts();
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>
          <h2>{post.data.title}</h2>
          <p>{post.data.excerpt}</p>
        </li>
      ))}
    </ul>
  );
}

Vue 3

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { createVibeCMS, extractCollection } from '@vibe-cms/sdk';

const cms = createVibeCMS({ projectId: 'xxx' });

const posts = ref([]);
const loading = ref(true);

onMounted(async () => {
  try {
    const result = await cms.collection('blog-posts').all();
    posts.value = extractCollection(result);
  } finally {
    loading.value = false;
  }
});
</script>

<template>
  <div v-if="loading">Loading...</div>
  <ul v-else>
    <li v-for="post in posts" :key="post.id">
      <h2>{{ post.data.title }}</h2>
      <p>{{ post.data.excerpt }}</p>
    </li>
  </ul>
</template>

Astro

---
import { cms, extractCollection } from '../../lib/cms';

// Fetch at build time
const postsResult = await cms.collection('blog-posts').all();
const posts = extractCollection(postsResult);
---

<ul>
  {posts.map(post => (
    <li>
      <h2>{post.data.title}</h2>
      <p>{post.data.excerpt}</p>
    </li>
  ))}
</ul>

Next.js (App Router)

// app/blog/page.tsx
import { cms, extractCollection } from '@/lib/cms';

export default async function BlogPage() {
  const postsResult = await cms.collection('blog-posts').all();
  const posts = extractCollection(postsResult);

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>
          <h2>{post.data.title}</h2>
          <p>{post.data.excerpt}</p>
        </li>
      ))}
    </ul>
  );
}

Svelte

<script lang="ts">
  import { onMount } from 'svelte';
  import { createVibeCMS, extractCollection } from '@vibe-cms/sdk';

  const cms = createVibeCMS({ projectId: 'xxx' });

  let posts = [];
  let loading = true;

  onMount(async () => {
    try {
      const result = await cms.collection('blog-posts').all();
      posts = extractCollection(result);
    } finally {
      loading = false;
    }
  });
</script>

{#if loading}
  <div>Loading...</div>
{:else}
  <ul>
    {#each posts as post (post.id)}
      <li>
        <h2>{post.data.title}</h2>
        <p>{post.data.excerpt}</p>
      </li>
    {/each}
  </ul>
{/if}

Caching

Browser Caching

Enable SDK caching to reduce API calls:

const cms = createVibeCMS({
  projectId: 'xxx',
  cache: {
    enabled: true,
    storage: 'localStorage',  // Persists across sessions
    ttl: 300000,             // 5 minutes
  }
});

// First call: fetches from API
const posts1 = await cms.collection('blog-posts').all();

// Second call within 5 minutes: returns cached result
const posts2 = await cms.collection('blog-posts').all();

Cache Management

// Clear all cache
await cms.cache.clear();

// Clear specific locale cache
await cms.cache.clearLocaleCache('project-id', 'en-US');

// Get cache statistics
const stats = await cms.cache.getStats();
console.log(stats.hitRate);  // Cache hit percentage

// Disable cache for specific query
const freshPosts = await cms.collection('blog-posts')
  .cache(false)  // Skip cache
  .all();

Build-Time Fetching (Recommended for Static Sites)

No caching needed for SSG:

// Astro
---
import { cms } from '../../lib/cms';

// Disable cache - fetch fresh at build time
const cms = createVibeCMS({
  projectId: 'xxx',
  cache: { enabled: false }  // Don't cache at build time
});

const posts = await cms.collection('blog-posts').all();
---

Error Handling

Try/Catch Pattern

try {
  const post = await cms.collection('blog-posts').slug('my-post');
  console.log(post.data.title);
} catch (error) {
  if (error.code === 'NOT_FOUND') {
    console.error('Post not found');
  } else if (error.code === 'VALIDATION_ERROR') {
    console.error('Invalid request');
  } else {
    console.error('Unknown error:', error);
  }
}

Error Codes

  • NOT_FOUND: Content item doesn't exist
  • VALIDATION_ERROR: Invalid query parameters
  • UNAUTHORIZED: Invalid API key
  • RATE_LIMITED: Too many requests
  • NETWORK_ERROR: Connection failed

Non-Throwing Mode

const cms = createVibeCMS({
  projectId: 'xxx',
  throwOnValidationError: false  // Return errors instead of throwing
});

const result = await cms.collection('blog-posts').slug('my-post');

if (result.error) {
  console.error('Error:', result.error.message);
} else {
  console.log('Success:', result.data);
}

Performance Tips

  1. Use specific queries

    // Good: Query specific locale
    const posts = await cms.collection('blog-posts')
      .locale('en-US')
      .all();
    
    // Bad: Get all data then filter client-side
    const allPosts = await cms.collection('blog-posts').all();
    const filtered = allPosts.filter(p => p.locale === 'en-US');
    
  2. Paginate large collections

    // Good: Paginate
    const posts = await cms.collection('blog-posts').many({
      limit: 20,
      offset: 0
    });
    
    // Bad: Fetch all at once
    const allPosts = await cms.collection('blog-posts').all();
    
  3. Fetch in parallel

    // Good: Parallel queries
    const [hero, features, testimonials] = await Promise.all([
      cms.collection('landing-hero').first(),
      cms.collection('features').all(),
      cms.collection('testimonials').all()
    ]);
    

Best Practices

  1. Create a reusable CMS instance

    // lib/cms.ts
    import { createVibeCMS, extractData, extractCollection } from '@vibe-cms/sdk';
    
    export const cms = createVibeCMS({
      projectId: import.meta.env.PUBLIC_CMS_PROJECT_ID
    });
    
    export { extractData, extractCollection };
    
  2. Use TypeScript interfaces

    interface BlogPost {
      title: string;
      slug: string;
      content: string;
    }
    
    const post = await cms.collection('blog-posts').slug('my-post');
    const data: BlogPost = extractData(post);
    
  3. Handle errors gracefully

    try {
      const post = await cms.collection('blog-posts').slug(slug);
    } catch (error) {
      console.error('Failed to fetch post:', error);
      notFound();  // Next.js / Astro
    }
    
  4. Optimize images

    const imageUrl = cms.assetUrl('asset-id', {
      width: 800,
      height: 600,
      format: 'webp',  // Use modern formats
      quality: 80
    });
    

Debugging

Enable Debug Mode

const cms = createVibeCMS({
  projectId: 'xxx',
  debug: true  // Log all requests
});

SDK Playground

Open SDK Playground

Interactively test queries before implementing:

const cms = createVibeCMS({
  projectId: 'ead059e8-bbc0-4a4c-a273-b8f9510d9511'
});

const posts = await cms.collection('documentation-pages').all();
return posts;

Next Steps

Now that you understand SDK integration:

  • Install the SDK in your project
  • Define TypeScript interfaces for your content
  • Fetch content from collections
  • Integrate with your framework
  • Set up caching for optimal performance

Need help? Join our Discord community or check the SDK API Reference.