{entry.data.title}
Published on: {new Date(entry.data.publishedAt).toLocaleDateString()}
Learn how to integrate your content management system...
", "coverImage": "https://cdn.marblecms.com/images/cms-guide.webp", "description": "A comprehensive guide to integrating your CMS...", "publishedAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-20T14:22:33.456Z", "authors": [ { "id": "author_xyz789", "name": "John Smith", "image": "https://cdn.marblecms.com/avatars/john.jpg" } ], "category": { "id": "cat_tutorials123", "name": "Tutorials", "slug": "tutorials" }, "tags": [ { "id": "tag_integration456", "name": "Integration", "slug": "integration" } ] } ], "pagination": { "limit": 5, "currentPage": 1, "nextPage": null, "previousPage": null, "totalPages": 1, "totalItems": 2 } } ```Hello world
" } } ``` ## Next stepsHello world
", "description": "What changed this week", "slug": "launch-notes", "categoryId": "cat_123", "status": "draft", "fields": { "audience": ["developers", "founders"] } } ``` Unknown field keys, wrong value types, and option values that do not match a configured `select` or `multiselect` option are rejected with `400` responses. ## API response shape Custom fields are exposed on posts as `post.fields` (single post) and `posts[].fields` (list posts). In the current public API, custom fields are returned in `GET` post responses. ### Example response ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "post": { "id": "cm9x...", "slug": "shipping-custom-fields", "title": "Shipping Custom Fields", "fields": { "release_date": "2026-04-02", "priority_score": 7, "evergreen": true, "target_channels": ["blog", "newsletter"], "editor_note": "Republish in Q3
" } } } ``` ### Unset fields If a field exists in your workspace but a post has no value for it yet, Marble returns that key with `null`. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "fields": { "release_date": null, "priority_score": null } } ``` ## Important behavior * Field keys are workspace-scoped and must be unique. * Post writes never create fields implicitly. Create or update field definitions first, then write values to posts. * Field type and options are effectively schema choices. Plan them early to avoid migration work later. * Deleting a field removes that field and its stored values from all posts. # Marble's Editor Source: https://docs.marblecms.com/features/editor Marble's distraction-free editor with inline formatting, image and video embeds, metadata sidebar, and Content AI readability scoring and writing suggestions. Marble's editor is designed to get out of your way. A clean, Notion-like writing experience with no clutter — just you and your content. All metadata and settings live in a collapsible sidebar so you can focus when you need to. ## Writing Experience The editor opens empty and distraction-free. Start typing and your content takes center stage. Formatting options appear when you need them, and disappear when you don't. ### Formatting Standard formatting options are available inline: * **Bold** and *italic* text * Text highlights and colors * Headings (H1 through H6) * Bullet and numbered lists * Blockquotes * Code blocks and inline code * Links ### Nodes Beyond text, the editor supports rich content nodes for embedding media:Published on: {new Date(entry.data.publishedAt).toLocaleDateString()}
...
const codeBlockRegex =
/]*>([\s\S]*?)<\/code><\/pre>/g;
return htmlContent.replace(codeBlockRegex, (match, language, code) => {
try {
// Decode HTML entities
const decodedCode = code
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/&/g, "&")
.replace(/"/g, '"')
.replace(/'/g, "'");
const lang = language || "text";
const supported = hl.getLoadedLanguages();
const finalLang = supported.includes(lang) ? lang : "text";
return hl.codeToHtml(decodedCode, {
lang: finalLang,
theme: "github-dark",
});
} catch (error) {
console.warn("Failed to highlight code block:", error);
return match;
}
});
}
```
## Use with the SDK
Apply the highlighter when fetching posts:
```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Marble } from "@usemarble/sdk";
import { highlightContent } from "./lib/highlight";
const marble = new Marble({
apiKey: process.env["MARBLE_API_KEY"] ?? "",
});
async function getPost(slug: string) {
const { post } = await marble.posts.get({ identifier: slug });
return {
...post,
content: await highlightContent(post.content),
};
}
```
## Configuration
Shiki supports many themes and languages. Update the `createHighlighter` options to match your needs:
```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
highlighter = await createHighlighter({
// Add more themes
themes: ["github-dark", "github-light", "dracula", "nord"],
// Add more languages
langs: ["javascript", "typescript", "python", "rust", "go"],
});
```
For dual themes (light/dark mode), use:
```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
return hl.codeToHtml(decodedCode, {
lang: finalLang,
themes: {
dark: "github-dark",
light: "github-light",
},
});
```
For more configuration options, themes, and languages, see the [Shiki
documentation](https://shiki.style).
# Reading Time
Source: https://docs.marblecms.com/recipes/reading-time
Calculate and display estimated reading time for Marble posts with a small TypeScript helper that strips HTML and counts words per minute.
Some blogs like to show an estimated reading time for each post. Marble provides these estimates inside the editor interface, but you can also display them on the frontend of your site.
## How It Works
The math behind reading time is fairly simple: it's based on an average reading speed of 238 words per minute (often rounded to 200 for simplicity).
## Implementation
This is a simple function you can use to calculate reading time for any given content:
```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
export function calculateReadTime(content: string): number {
const wordsPerMinute = 238;
const plainText = content.replace(/<[^>]*>/g, "").trim();
const wordCount = plainText.split(/\s+/).length;
const readingTime = Math.ceil(wordCount / wordsPerMinute);
return readingTime;
}
```
To then use this function, simply pass in the HTML content of your post, and it will return the estimated reading time in minutes.
```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
const readTime = calculateReadTime("Your post content goes here...
");
```
You can then use that number to display the reading time on your post pages. Feel free to tweak the words-per-minute value to better suit your audience!
# Marble MCP Server
Source: https://docs.marblecms.com/tools/mcp
Connect Claude, Cursor, and other Model Context Protocol clients to your Marble workspace to read, create, and manage content with AI agents.
Use Marble's Model Context Protocol server to let AI agents read and manage
content in your workspace through the Marble API.
You need a Marble API key. Write tools such as create, update, and delete
require a private API key.
## Installation
The quickest way to add Marble to supported MCP clients is with the `add-mcp`
CLI.
```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
npx add-mcp https://mcp.marblecms.com/mcp \
--header "Mcp-Marble-Api-Key: $MCP_MARBLE_API_KEY" \
-g
```
This installs Marble globally for the agents you select. Without `-g`,
`add-mcp` writes project-level MCP config.
Open Cursor with the Marble MCP server pre-filled.
Click **Install in Cursor** and allow your browser to open Cursor.
Cursor installs the server with this header:
```txt theme={"theme":{"light":"github-light","dark":"github-dark"}}
Mcp-Marble-Api-Key: ${MCP_MARBLE_API_KEY}
```
Set `MCP_MARBLE_API_KEY` in your environment before starting Cursor.
Open Cursor's MCP settings and confirm that the `marble` server is
enabled. The server URL should end with `/mcp`.
You can also add the server manually in Cursor:
```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
"mcpServers": {
"marble": {
"type": "streamableHttp",
"url": "https://mcp.marblecms.com/mcp",
"headers": {
"Mcp-Marble-Api-Key": "${MCP_MARBLE_API_KEY}"
}
}
}
}
```
Open VS Code with the Marble MCP server pre-filled.
Click **Install in VS Code** and allow your browser to open VS Code.
Set `MCP_MARBLE_API_KEY` in your environment before starting VS Code.
Use the MCP controls in VS Code to start or restart the `marble` server.
You can also create `.vscode/mcp.json` manually:
```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
"servers": {
"marble": {
"type": "http",
"url": "https://mcp.marblecms.com/mcp",
"headers": {
"Mcp-Marble-Api-Key": "${MCP_MARBLE_API_KEY}"
}
}
}
}
```
Add the remote MCP server with:
```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
claude mcp add --transport http marble https://mcp.marblecms.com/mcp \
--header "Mcp-Marble-Api-Key: ${MCP_MARBLE_API_KEY}"
```
Set your API key in your shell before starting Claude Code:
```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
export MCP_MARBLE_API_KEY=""
```
Add Marble to your Codex MCP configuration:
```toml theme={"theme":{"light":"github-light","dark":"github-dark"}}
[mcp_servers.marble]
command = "npx"
args = [
"mcp-remote",
"https://mcp.marblecms.com/mcp",
"--header",
"Mcp-Marble-Api-Key:${MCP_MARBLE_API_KEY}"
]
[mcp_servers.marble.env]
MCP_MARBLE_API_KEY = ""
```
Some MCP clients support remote Streamable HTTP servers directly:
```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
"mcpServers": {
"marble": {
"type": "streamableHttp",
"url": "https://mcp.marblecms.com/mcp",
"headers": {
"Mcp-Marble-Api-Key": "${MCP_MARBLE_API_KEY}"
}
}
}
}
```
Clients that only support local stdio servers can connect through
`mcp-remote`:
```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
"mcpServers": {
"marble": {
"command": "npx",
"args": [
"mcp-remote",
"https://mcp.marblecms.com/mcp",
"--header",
"Mcp-Marble-Api-Key:${MCP_MARBLE_API_KEY}"
],
"env": {
"MCP_MARBLE_API_KEY": ""
}
}
}
}
```
## Available Tools
Tool badges describe how clients may present or approve tool calls:
* `READ-ONLY`: reads data without changing your workspace
* `DESTRUCTIVE`: may update or delete existing content
| Tool | Description | Badges |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `get_posts` | Get a paginated list of published posts with optional filtering. | `READ-ONLY` |
| `search_posts` | Search posts by title and content. Use this when an agent needs to find content before editing or linking to it. | `READ-ONLY` |
| `get_post` | Get a single post by ID or slug, with optional status and content format filtering. | `READ-ONLY` |
| `create_post` | Create a new post. Category is required. If authors are not provided, the first workspace author is used. Requires a private API key. | |
| `update_post` | Update an existing post by ID or slug. All fields are optional, and only provided fields are updated. Requires a private API key. | `DESTRUCTIVE` |
| `delete_post` | Delete a post by ID or slug. Requires a private API key. | `DESTRUCTIVE` |
| Tool | Description | Badges |
| ----------------- | -------------------------------------------------------------------------------------------------------------------- | ------------- |
| `get_categories` | Get a paginated list of categories. | `READ-ONLY` |
| `get_category` | Get a single category by ID or slug. | `READ-ONLY` |
| `create_category` | Create a new category. Requires a private API key. | |
| `update_category` | Update an existing category by ID or slug. Requires a private API key. | `DESTRUCTIVE` |
| `delete_category` | Delete a category by ID or slug. Cannot delete a category that has posts assigned to it. Requires a private API key. | `DESTRUCTIVE` |
| Tool | Description | Badges |
| ------------ | ----------------------------------------------------------------- | ------------- |
| `get_tags` | Get a paginated list of tags. | `READ-ONLY` |
| `get_tag` | Get a single tag by ID or slug. | `READ-ONLY` |
| `create_tag` | Create a new tag. Requires a private API key. | |
| `update_tag` | Update an existing tag by ID or slug. Requires a private API key. | `DESTRUCTIVE` |
| `delete_tag` | Delete a tag by ID or slug. Requires a private API key. | `DESTRUCTIVE` |
| Tool | Description | Badges |
| --------------- | ----------------------------------------------------------------------------------------------- | ------------- |
| `get_authors` | Get a paginated list of authors who have published posts. | `READ-ONLY` |
| `get_author` | Get a single author by ID or slug. | `READ-ONLY` |
| `create_author` | Create a new author. Hobby plan workspaces are limited to 1 author. Requires a private API key. | |
| `update_author` | Update an existing author by ID or slug. Requires a private API key. | `DESTRUCTIVE` |
| `delete_author` | Delete an author by ID or slug. Requires a private API key. | `DESTRUCTIVE` |
| Tool | Description | Badges |
| ----------------------- | -------------------------------------------------------------------------------------- | ------------- |
| `get_media` | Get a paginated list of media assets with optional type, search, and sort filters. | `READ-ONLY` |
| `get_media_asset` | Get a single media asset by ID, including its CDN URL and metadata. | `READ-ONLY` |
| `upload_media_from_url` | Upload media from a public URL into your Marble workspace. Requires a private API key. | |
| `update_media` | Update media metadata such as name and alt text. Requires a private API key. | `DESTRUCTIVE` |
| `delete_media` | Delete a media asset and its stored file. Requires a private API key. | `DESTRUCTIVE` |
| Tool | Description | Badges |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `get_fields` | Get all custom field definitions, including select and multiselect options. | `READ-ONLY` |
| `get_field` | Get a single custom field by ID or key. | `READ-ONLY` |
| `create_field` | Create a custom field definition. Requires a private API key. | |
| `update_field` | Update a custom field by ID or key. Type and options cannot be changed after values have been saved. Requires a private API key. | `DESTRUCTIVE` |
| `delete_field` | Delete a custom field and its saved values. Requires a private API key. | `DESTRUCTIVE` |
## Authentication
The MCP server accepts your Marble API key through one of these headers:
```txt theme={"theme":{"light":"github-light","dark":"github-dark"}}
Mcp-Marble-Api-Key:
X-Marble-Api-Key:
Authorization: Bearer
```
Keep private API keys out of client-side code and public repositories.
## Troubleshooting
If your client cannot connect, verify that:
* The URL ends with `/mcp`
* The API key header is present
* Your key has the permissions needed for the tool you are calling
* Write operations use a private Marble API key
# Marble TypeScript SDK
Source: https://docs.marblecms.com/tools/sdk
Official Marble TypeScript SDK with full type safety, automatic retries, pagination helpers, and framework-agnostic access to your workspace content.
Developer-friendly & type-safe TypeScript SDK for the Marble API. Built with automatic retries, pagination helpers, and full TypeScript support.
## Installation
```bash npm theme={"theme":{"light":"github-light","dark":"github-dark"}}
npm install @usemarble/sdk
```
```bash pnpm theme={"theme":{"light":"github-light","dark":"github-dark"}}
pnpm add @usemarble/sdk
```
```bash yarn theme={"theme":{"light":"github-light","dark":"github-dark"}}
yarn add @usemarble/sdk
```
```bash bun theme={"theme":{"light":"github-light","dark":"github-dark"}}
bun add @usemarble/sdk
```
This package is published with CommonJS and ES Modules (ESM) support.
## Quick Start
```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Marble } from "@usemarble/sdk";
const marble = new Marble({
apiKey: process.env["MARBLE_API_KEY"] ?? "",
});
async function run() {
const result = await marble.posts.list({
limit: 10,
page: 1,
});
for await (const page of result) {
console.log(page);
}
}
run();
```
## Authentication
The SDK uses API key authentication. Pass your API key when initializing the client:
```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Marble } from "@usemarble/sdk";
const marble = new Marble({
apiKey: process.env["MARBLE_API_KEY"] ?? "",
});
```
Set the `MARBLE_API_KEY` environment variable and the SDK will automatically
use it.
## Available Resources
* List all posts
* Get a single post
* List all categories
* Get a single category
* List all tags
* Get a single tag
* List all authors
* Get a single author
* List custom fields
* Create and update field definitions
* Write field values on posts
## Filtering Posts
Filter posts by categories, tags, or featured status:
```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const result = await marble.posts.list({
featured: "true",
categories: "tech,news",
excludeCategories: "drafts",
tags: "javascript,react",
excludeTags: "outdated",
});
```
For a complete guide on filtering options and behavior, see the [Filtering documentation](/api/filtering).
## Pagination
Paginated endpoints return an async iterable. Use `for await...of` to iterate through pages:
```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const result = await marble.posts.list({
limit: 10,
page: 1,
});
for await (const page of result) {
console.log(page.posts);
console.log(page.pagination);
}
```
For full pagination details and response fields, see the [Pagination documentation](/api/pagination).
## Error Handling
The SDK provides typed error classes for different error scenarios:
```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Marble } from "@usemarble/sdk";
import * as errors from "@usemarble/sdk/models/errors";
const marble = new Marble({
apiKey: process.env["MARBLE_API_KEY"] ?? "",
});
try {
const result = await marble.posts.get("non-existent-slug");
} catch (error) {
if (error instanceof errors.NotFoundError) {
console.log("Post not found");
} else if (error instanceof errors.MarbleError) {
console.log(error.message);
console.log(error.statusCode);
}
}
```
## Retries
The SDK automatically retries failed requests. You can customize retry behavior:
```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const marble = new Marble({
apiKey: process.env["MARBLE_API_KEY"] ?? "",
retryConfig: {
strategy: "backoff",
backoff: {
initialInterval: 1,
maxInterval: 50,
exponent: 1.1,
maxElapsedTime: 100,
},
retryConnectionErrors: false,
},
});
```
## Framework Guides
Use Content Collections with Astro.
Sync content to Framer CMS with the Marble plugin.
Build static and server-rendered pages.
Server functions with TanStack Start.
## Additional Resources
View the package on npm.
View the source code.