Chapter 10: SDK Integration
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 existVALIDATION_ERROR: Invalid query parametersUNAUTHORIZED: Invalid API keyRATE_LIMITED: Too many requestsNETWORK_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
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');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();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
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 };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);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 }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
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.