@inneropen/marvin-sdk
Marvin TypeScript SDK
๐ Enterprise-Grade Security | โ Zero Vulnerabilities | ๐ Production-Ready
The official TypeScript SDK for Marvin CMS. A modern, workspace-first SDK for building applications that integrate with Marvin.
Security First
The Marvin SDK has undergone a comprehensive security audit and achieved zero security vulnerabilities. All 20 identified issues have been fixed, including:
- โ 8 HIGH priority vulnerabilities eliminated
- โ 7 MEDIUM priority security issues resolved
- โ 5 LOW priority code quality improvements implemented
View Complete Security Audit โ
Key Security Features
- ๐ Token Sanitization - Automatic redaction of sensitive data in logs/errors
- ๐ก๏ธ Input Validation - Path injection, XSS, and SSRF prevention
- ๐ง Email Validation - RFC 5322 compliant validation
- ๐ File Upload Security - Size, type, and filename validation
- ๐ Network Resilience - Automatic retry with exponential backoff
- ๐ CSRF Protection - Session authentication security
- โ
Type Safety - 100% TypeScript with zero
anytypes
View Security Best Practices โ
Why This SDK?
Marvin is becoming a platform, not simply a CMS. This SDK is the primary way to integrate with Marvin across:
- Static Site Generators (Astro, Next.js, Nuxt)
- Server Applications (Express, Fastify, Hono)
- CLI Tools & Build Scripts
- Automation (n8n, GitHub Actions)
- AI Agents & Custom Integrations
The SDK provides:
- ๐ฏ Workspace-First Architecture - Work with Marvin concepts (Workspace, Entry, Collection)
- ๐ Performance Optimized - Built-in caching for build-time usage
- ๐ Fully Typed - Complete TypeScript support
- ๐ Backwards Compatible - Works with existing publishing APIs
- ๐จ Object-Oriented - Rich objects instead of raw JSON
- ๐ Enterprise Security - Production-ready with comprehensive security features
Installation
npm install @inneropen/marvin-sdk@^2.0.1
Latest stable version: v2.0.1 with complete security fixes and enterprise-grade quality.
Entry Points
The SDK provides three distinct entry points for different use cases:
๐ Publishing API (/publish)
Read-only access to published content (frontends, websites)
import { createMarvinClient } from '@inneropen/marvin-sdk/publish';
Auth: Site client tokens (MARVIN_SITE_CLIENT_TOKEN)
โ๏ธ Platform API (/platform)
Full CRUD admin operations (backend, CLI, automation)
import { createPlatformClient } from '@inneropen/marvin-sdk/platform';
Auth: User tokens (MARVIN_USER_TOKEN) or session cookies
๐ Auth API (Default export)
Public registration, login, password reset
import { createAuthClient } from '@inneropen/marvin-sdk';
Auth: None required (public endpoints)
๐ See complete authentication guide โ
Quick Start - Publishing API
1. Configure Environment
MARVIN_API_URL=https://marvin.example.com
MARVIN_SITE_CLIENT_TOKEN=your-site-client-token
MARVIN_WORKSPACE_SLUG=your-workspace
2. Create Client
import { createMarvinClient } from '@inneropen/marvin-sdk/publish';
const marvin = createMarvinClient();
3. Fetch Content
// Get workspace
const workspace = await marvin.getWorkspace();
console.log(workspace.site?.title);
// Get entries
const entries = await marvin.entries.list();
// Get a specific entry
const entry = await marvin.entry('about-us');
console.log(entry.title, entry.contentMarkdown);
// Get collection entries
const projects = await marvin.collection('projects');
const projectEntries = await projects.entries();
Quick Start - Platform API
Programmatic Admin Access
import { createPlatformClient } from '@inneropen/marvin-sdk/platform';
// From environment (MARVIN_USER_TOKEN)
const platform = createPlatformClient();
// Or explicit token
const platform = createPlatformClient({
userToken: 'your-user-token'
});
// Full CRUD operations
await platform.entries.create({
title: 'New Post',
content: 'Content here...',
collection_id: collectionId
});
await platform.entries.update(entryId, {
title: 'Updated Title'
});
await platform.entries.delete(entryId);
Architecture
Workspace-First Design
Everything starts with the Workspace:
const workspace = await marvin.getWorkspace();
// Access workspace modules
workspace.entries // Entries module
workspace.collections // Collections module
workspace.renderers // Renderers module (entry type rendering metadata)
workspace.site // Site configuration (cached)
Modular Structure
The SDK is organized into focused modules:
@marvin/sdk
โโโ client/ โ Core client & HTTP
โโโ workspaces/ โ Workspace object
โโโ entries/ โ Entries module
โโโ collections/ โ Collections module
โโโ renderers/ โ Renderers module (entry type rendering metadata)
โโโ auth/ โ Authentication (future)
โโโ types/ โ TypeScript types
โโโ utils/ โ Cache & utilities
API Styles
The SDK supports three API styles:
1. Workspace API (Recommended)
const workspace = await marvin.getWorkspace();
const entries = await workspace.entries.list();
const collection = await workspace.collections.get('featured');
2. Convenience API
const entry = await marvin.entry('about');
const projects = await marvin.projects();
const pages = await marvin.pages();
3. Backwards-Compatible API
// Still works! (Deprecated but supported)
const site = await marvin.getSite();
const entries = await marvin.getEntries();
const entry = await marvin.getEntry('about');
Core Concepts
Workspace
The workspace is the root object representing your Marvin workspace.
const workspace = await marvin.getWorkspace();
// Cached site configuration
workspace.site?.title
workspace.site?.description
// Access modules
workspace.entries.list()
workspace.collections.get('slug')
Content Primitives
Marvin has four first-class content primitives:
- Entries - Content you create (pages, blog posts, projects)
- Collections - Organize and group entries
- Assets - Binary files and media (images, videos, documents). Delivered inline on entries
- Resources - Reusable structured objects (fabrics, tools, suppliers, APIs). Delivered inline on entries
Entries
Rich entry objects with methods and properties:
const entry = await marvin.entry('about-us');
// Properties
entry.title
entry.slug
entry.contentMarkdown
entry.metadata
entry.publishedAt
// Relationships
entry.assets // MarvinAsset[]
entry.collections // MarvinCollection[]
entry.resources // MarvinResource[]
// Methods (future)
await entry.relatedEntries()
Collections
Collections as first-class objects:
const collection = await marvin.collection('projects');
// Properties
collection.name
collection.slug
collection.isSmart
collection.smartRules
// Methods
const entries = await collection.entries();
// Future
await collection.metadata()
Assets and Resources
The publishing API no longer has asset or resource endpoints. A single entry
(marvin.entry(slug)) carries full assets and resources objects; list endpoints return
assetSlugs/resourceSlugs plus a resolved featuredAsset. The site config (getSite())
returns the logo, favicon and SEO image as resolved URLs.
Entries carry public asset URLs, so there is no separate download call.
Examples of Resources:
- Fabrics (denim, canvas, etc.)
- Tools (sewing machine, cutting tool)
- Suppliers (fabric mills, button manufacturers)
- Git repositories
- APIs and services
- Books and documents
Note: Resources are not binary files. Use Assets for media files.
Usage Examples
Astro Static Site
// src/pages/[slug].astro
---
import { marked } from 'marked';
import { createMarvinClient } from '@/lib/marvin';
const marvin = createMarvinClient();
export async function getStaticPaths() {
const pages = await marvin.pages();
return pages.map((page) => ({
params: { slug: page.slug },
props: { page },
}));
}
const { page } = Astro.props;
const contentHtml = marked.parse(page.contentMarkdown ?? '');
---
<article>
<h1>{page.title}</h1>
<div set:html={contentHtml} />
</article>
Next.js App
// app/blog/page.tsx
import { createMarvinClient } from '@/lib/marvin';
const marvin = createMarvinClient();
export default async function BlogPage() {
const posts = await marvin.posts();
return (
<div>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.summary}</p>
</article>
))}
</div>
);
}
Express Server
import express from 'express';
import { createMarvinClient } from './marvin-sdk';
const app = express();
const marvin = createMarvinClient();
app.get('/api/posts', async (req, res) => {
const posts = await marvin.posts();
res.json(posts);
});
app.get('/api/posts/:slug', async (req, res) => {
const entry = await marvin.entry(req.params.slug);
res.json(entry.toJSON());
});
CLI Tool
#!/usr/bin/env node
import { createMarvinClient } from '@marvin/sdk';
const marvin = createMarvinClient();
async function main() {
await marvin.initialize();
console.log('Site:', marvin.site?.title);
const entries = await marvin.entries.list();
console.log(`Found ${entries.length} entries`);
for (const entry of entries) {
console.log(`- ${entry.title} (${entry.status})`);
}
}
main().catch(console.error);
Initialization & Caching
Auto-Initialize
const marvin = createMarvinClient({
autoInitialize: true,
});
// Site is preloaded
console.log(marvin.site?.title);
Manual Initialize
const marvin = createMarvinClient();
await marvin.initialize();
// Now site is cached
console.log(marvin.site?.title);
Cache Duration
const marvin = createMarvinClient({
cacheDuration: 10 * 60 * 1000, // 10 minutes
});
Convenience Methods
Quick access to common content types:
// Pages
const pages = await marvin.pages();
const about = await marvin.entry('about');
// Blog posts
const posts = await marvin.posts({ limit: 10 });
// Projects
const projects = await marvin.projects();
// Collections
const featured = await marvin.collection('featured');
const entries = await featured.entries();
// Assets and resources are carried inline on entries
const entry = await marvin.entry('about-us');
const assets = entry.assets;
const resources = entry.resources;
API Reference
Client Methods
createMarvinClient(config?)
Create a new Marvin client instance.
Config Options:
apiUrl?: string- API URL (default:process.env.MARVIN_API_URL)siteClientToken?: string- Site client token (default:process.env.MARVIN_SITE_CLIENT_TOKEN)workspaceSlug?: string- Workspace slug (default:process.env.MARVIN_WORKSPACE_SLUG)autoInitialize?: boolean- Auto-initialize on creation (default:false)cacheDuration?: number- Cache duration in ms (default:300000/ 5 min)
marvin.initialize()
Initialize the SDK and preload workspace data.
marvin.getWorkspace()
Get the workspace object.
marvin.getWorkspaceInfo()
Get basic workspace information (name and slug only).
Returns:
{
slug: string;
name: string;
}
marvin.entry(slug)
Get a single entry by slug (returns Entry object).
marvin.collection(slug)
Get a single collection by slug (returns Collection object).
marvin.pages(options?)
Get all pages.
marvin.posts(options?)
Get all blog posts.
marvin.projects(options?)
Get all projects.
Workspace
workspace.slug
The workspace slug (string).
workspace.site
Site configuration (cached after initialize()).
workspace.getInfo()
Get workspace info (name and slug).
workspace.loadSite()
Load/reload site configuration from the API.
workspace.entries
Entries module.
workspace.collections
Collections module.
Entries Module
entries.list(options?)
Get all entries with optional filtering.
Options:
entryType?: string- Filter by entry type slugcollection?: string- Filter by collection slugstatus?: string- Filter by status (default:'published')limit?: number- Max resultsoffset?: number- Pagination offset
entries.get(slug)
Get a single entry (returns Entry object).
entries.pages(options?)
Get all pages.
entries.posts(options?)
Get all blog posts.
entries.projects(options?)
Get all projects.
Entry Object
Properties:
id,title,slug,summary,descriptioncontentMarkdown- Raw Markdown contentmetadata- Custom metadata objectstatus,publishedAt,createdAt,updatedAtentryTypeId,entryTypeassets- Array of related assetscollections- Array of related collections
Methods:
entry.toJSON()- Get raw entry data
Collections Module
collections.list({ limit?, offset? })
Get collections. The server returns 20 per request by default (at most 100); pass limit and offset to page through more.
collections.get(slug)
Get a single collection (returns Collection object).
collections.entries(slug)
Get entries in a collection.
Collection Object
Properties:
id,name,slug,descriptionicon,color,sortOrderisSmart,smartRulescreatedAt,updatedAt
Methods:
collection.entries()- Get entries in collectioncollection.toJSON()- Get raw collection data
Renderers Module
renderers.list()
Get all entry types with their rendering and capability metadata.
Returns: PublishedEntryType[]
interface PublishedEntryType {
slug: string;
name: string;
isRendered: boolean;
rendering?: {
renderer?: string;
package?: string;
version?: string;
config?: Record<string, unknown>;
};
capabilities?: {
publishable?: boolean;
submittable?: boolean;
routable?: boolean;
};
}
Example:
const workspace = await marvin.getWorkspace();
const renderers = await workspace.renderers.list();
const rendered = renderers.filter(r => r.isRendered);
console.log(`${rendered.length} entry types have renderers`);
Backend API Endpoints
The SDK uses these Marvin publishing API endpoints:
| Endpoint | Method | Purpose | Status |
|---|---|---|---|
/api/publish/{workspace} |
GET | Workspace info (name, slug) | โ Implemented |
/api/publish/{workspace}/site |
GET | Site configuration | โ Implemented |
/api/publish/{workspace}/entries |
GET | List entries | โ Implemented |
/api/publish/{workspace}/entries/{slug} |
GET | Get entry | โ Implemented |
/api/publish/{workspace}/collections |
GET | List collections | โ Implemented |
/api/publish/{workspace}/collections/{slug} |
GET | Get collection | โ Implemented |
/api/publish/{workspace}/collections/{slug}/entries |
GET | Collection entries | โ Implemented |
/api/publish/{workspace}/entry-types |
GET | List entry types (renderers) | โ Implemented |
Query Parameters:
- Entries:
entry_type,collection,slug,updated_since,page,limit
Security
Security Features
The SDK includes comprehensive security protections:
Input Validation
- Path Injection Prevention - Validates all path parameters to prevent
../attacks - Email Validation - RFC 5322 compliant email format checking
- Webhook URL Validation - SSRF prevention for webhook endpoints
- File Upload Validation - Size limits (10MB), type checking, and filename sanitization
- Form Submission Validation - XSS prevention with script tag detection
Data Protection
- Token Sanitization - Automatic redaction of sensitive data in debug logs and errors
- Tokens, passwords, secrets, API keys automatically masked
- Recursive sanitization for nested objects and arrays
- Email Password Security - Passwords never returned in API responses
- CSRF Protection - Session authentication with CSRF token support
Network Security
- Retry Logic - Automatic exponential backoff for transient failures (3 retries, configurable)
- Timeout Limits - Maximum 2-minute timeout to prevent resource exhaustion
- Error Handling - Comprehensive error types with actionable messages
Complete Security Documentation โ
Site Client Tokens
Always use site client tokens, never user tokens.
Get a site client token:
- Log into Marvin
- Go to Settings โ Publishing โ Site Clients
- Create a new client
- Copy the token
Server-Side Only
Never expose tokens in browser code:
// โ
Good - Server/build time
const marvin = createMarvinClient();
// โ Bad - Browser code with exposed token
<script>
const marvin = createMarvinClient({
siteClientToken: '{TOKEN}' // DON'T DO THIS!
});
</script>
Security Best Practices
- Environment Variables - Never commit tokens to version control
- Token Rotation - Regenerate user tokens periodically
- Debug Mode - Tokens are automatically sanitized in logs, but still use caution
- Input Validation - The SDK validates all inputs, but validate on your side too
- HTTPS Only - Always use HTTPS in production (enforced by default)
- CSRF Tokens - Enable CSRF protection for browser-based admin UIs
Complete Authentication Guide โ
Future Modules
The SDK architecture supports future expansion:
- โ Publishing - Implemented
- ๐ Authentication - User authentication
- ๐ Users - User management
- ๐ Workspaces - Workspace management
- ๐ Events - Event streams
- ๐ Webhooks - Webhook management
- ๐ Search - Full-text search
- ๐ AI - AI-powered features
Design Philosophy
The SDK is inspired by:
Simple to start, powerful as you grow.
Consumers think in Marvin concepts (Workspace, Entry, Collection), not raw REST endpoints.
Troubleshooting
"MARVIN_API_URL is required"
Make sure your .env file exists with all required variables.
"401 Unauthorized"
- Verify your site client token is valid
- Check that you're using a site client token, not a user token
- Ensure the token hasn't expired
TypeScript Errors
Make sure your tsconfig.json includes:
{
"compilerOptions": {
"moduleResolution": "bundler",
"types": ["node"]
}
}
License
MIT - Part of the Marvin project.
Documentation
- ๐ Security Audit Report - Complete security fixes (20 issues resolved)
- ๐ Security Fixes Summary - Key improvements and migration guide
- ๐ Authentication Guide - Complete auth documentation and security best practices
- โ ๏ธ Error Handling Guide - Comprehensive error handling patterns
- ๐งช Testing Guide - Testing strategy and security test examples
- ๐ Migration Guide v2.0 - Upgrade from v1.x to v2.0
Support
- ๐ Full Documentation
- ๐ Quick Start Guide
- ๐ Report Issues
- ๐ Security Policy - Vulnerability reporting and security policy
Related Packages
Prepr personalization, A/B testing and preview toolkit for React, Next.js, Nuxt,...
Official SDK and CLI tools for LeadCMS - framework-agnostic content management f...
Official JavaScript SDK for Cosmic. Fetch content, manage media, and generate AI...
Version History
| Version | Released | License |
|---|---|---|
| 7.12.0 | MIT | |
| 7.11.0 | MIT | |
| 7.10.0 | MIT | |
| 7.9.0 | MIT | |
| 7.8.0 | MIT | |
| 7.7.0 | MIT | |
| 7.6.0 | MIT | |
| 7.5.1 | MIT | |
| 7.5.0 | MIT | |
| 7.4.0 | MIT | |
| 7.3.1 | MIT | |
| 7.3.0 | MIT | |
| 7.2.0 | MIT | |
| 7.1.0 | MIT | |
| 7.0.0 | MIT | |
| 6.2.0 | MIT | |
| 6.1.0 | MIT | |
| 6.0.0 | MIT | |
| 5.29.0 | MIT | |
| 5.28.1 | MIT | |
| 5.28.0 | MIT | |
| 5.27.0 | MIT | |
| 5.26.0 | MIT | |
| 5.25.0 | MIT | |
| 5.24.0 | MIT | |
| 5.23.0 | MIT | |
| 5.22.0 | MIT | |
| 5.21.0 | MIT | |
| 5.20.0 | MIT | |
| 5.19.0 | MIT |
Showing the latest 30 of 90. See every release on npm
Pre-releases (57)
| Version | Released | License |
|---|---|---|
| 3.2.0-next.28 | MIT | |
| 3.2.0-next.27 | MIT | |
| 3.2.0-next.26 | MIT | |
| 3.2.0-next.25 | MIT | |
| 3.2.0-next.24 | MIT | |
| 3.2.0-next.23 | MIT | |
| 3.2.0-next.22 | MIT | |
| 3.2.0-next.21 | MIT | |
| 3.2.0-next.20 | MIT | |
| 3.2.0-next.19 | MIT | |
| 3.2.0-next.18 | MIT | |
| 3.2.0-next.17 | MIT | |
| 3.2.0-next.16 | MIT | |
| 3.2.0-next.15 | MIT | |
| 3.2.0-next.14 | MIT | |
| 3.2.0-next.13 | MIT | |
| 3.2.0-next.12 | MIT | |
| 3.2.0-next.11 | MIT | |
| 3.2.0-next.10 | MIT | |
| 3.2.0-next.9 | MIT | |
| 3.2.0-next.8 | MIT | |
| 3.2.0-next.7 | MIT | |
| 3.2.0-next.6 | MIT | |
| 3.2.0-next.5 | MIT | |
| 3.2.0-next.4 | MIT | |
| 3.2.0-next.3 | MIT | |
| 3.2.0-next.2 | MIT | |
| 3.2.0-next.1 | MIT | |
| 3.1.0-next.1 | MIT | |
| 2.0.0-next.25 | MIT |
Showing the latest 30 of 57. See every release on npm