8.8 KiB
8.8 KiB
Ghost Content Formats Documentation
Overview
Ghost supports multiple content formats for creating and managing posts and pages. Understanding these formats is crucial for proper MCP tool implementation.
Content Format Evolution
Historical Context
- Mobiledoc (Legacy): Ghost's previous JSON-based content format
- HTML: Traditional markup format
- Lexical (Current): Ghost's current standardized JSON content format
Current Content Formats
1. Lexical Format (Primary)
Status: Current standard format Type: JSON-based structured content Usage: Default format for all new content
Structure
{
"lexical": "{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Welcome to Ghost!\",\"type\":\"text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}"
}
Benefits
- Rich content representation
- Structured, parseable format
- Supports complex layouts and content blocks
- Platform-agnostic content storage
2. HTML Format (Optional)
Status: Available for output/input Type: Rendered HTML markup Usage: For compatibility and direct HTML input
Structure
{
"html": "<p>Welcome to Ghost!</p><p>This is a simple HTML paragraph.</p>"
}
Use Cases
- Content migration from HTML-based systems
- Direct HTML content creation
- Output for web rendering
3. Mobiledoc Format (Legacy)
Status: Legacy support (deprecated) Type: JSON-based (older format) Usage: Existing content only
Note: New content should use Lexical format. Mobiledoc is maintained for backward compatibility.
API Content Handling
Content Retrieval
Default Behavior
- API returns Lexical format by default
- HTML format requires explicit request
Format Selection
// Get only Lexical (default)
GET /ghost/api/content/posts/
// Get both Lexical and HTML
GET /ghost/api/content/posts/?formats=html,lexical
// Get only HTML
GET /ghost/api/content/posts/?formats=html
Response Examples
Lexical Only (Default):
{
"posts": [
{
"id": "post_id",
"title": "My Post",
"lexical": "{\"root\":{...}}",
"slug": "my-post"
}
]
}
HTML and Lexical:
{
"posts": [
{
"id": "post_id",
"title": "My Post",
"lexical": "{\"root\":{...}}",
"html": "<p>Post content</p>",
"slug": "my-post"
}
]
}
Content Creation & Updates
Admin API Content Fields
// Creating a post with Lexical
const postData = {
posts: [{
title: "New Post",
lexical: JSON.stringify(lexicalContent),
status: "draft"
}]
};
// Creating a post with HTML
const postData = {
posts: [{
title: "New Post",
html: "<p>HTML content here</p>",
status: "draft"
}]
};
// Creating with both (Lexical takes precedence)
const postData = {
posts: [{
title: "New Post",
lexical: JSON.stringify(lexicalContent),
html: "<p>Fallback HTML</p>",
status: "draft"
}]
};
Content Format Priorities
Create/Update Priority Order
- Lexical (highest priority)
- HTML (fallback if no Lexical)
- Mobiledoc (legacy fallback)
Conversion Behavior
- HTML → Lexical: Ghost converts HTML to Lexical automatically
- Lexical → HTML: Ghost renders Lexical to HTML
- Mobiledoc → Lexical: Ghost migrates existing Mobiledoc content
Implementation for MCP Server
Content Format Detection
class GhostContentHandler {
detectContentFormat(content) {
if (typeof content === 'object' && content.root) {
return 'lexical';
}
if (typeof content === 'string' && content.startsWith('<')) {
return 'html';
}
if (typeof content === 'object' && content.version) {
return 'mobiledoc';
}
return 'unknown';
}
prepareContentForAPI(content, preferredFormat = 'lexical') {
const format = this.detectContentFormat(content);
switch (format) {
case 'lexical':
return {
lexical: typeof content === 'string' ? content : JSON.stringify(content)
};
case 'html':
return {
html: content
};
case 'mobiledoc':
return {
mobiledoc: typeof content === 'string' ? content : JSON.stringify(content)
};
default:
// Assume plain text, wrap in HTML
return {
html: `<p>${content}</p>`
};
}
}
}
MCP Tool Content Parameters
Create Post Tool
const createPostTool = {
name: "ghost_admin_create_post",
parameters: {
title: { type: "string", required: true },
// Content format options (one required)
lexical: { type: "string", description: "Lexical JSON content" },
html: { type: "string", description: "HTML content" },
mobiledoc: { type: "string", description: "Mobiledoc JSON content (legacy)" },
// Other parameters...
status: { type: "string", enum: ["draft", "published"] }
}
};
Content Validation
function validateContentInput(params) {
const contentFormats = ['lexical', 'html', 'mobiledoc'].filter(
format => params[format] !== undefined
);
if (contentFormats.length === 0) {
throw new Error('At least one content format (lexical, html, or mobiledoc) is required');
}
if (contentFormats.length > 1) {
console.warn('Multiple content formats provided. Lexical will take precedence.');
}
// Validate Lexical JSON if provided
if (params.lexical) {
try {
JSON.parse(params.lexical);
} catch (error) {
throw new Error('Invalid Lexical JSON format');
}
}
return true;
}
Content Format Examples
1. Simple Lexical Content
{
"root": {
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Hello World!",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "paragraph",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "root",
"version": 1
}
}
2. Rich HTML Content
<h1>My Blog Post</h1>
<p>This is a <strong>rich</strong> HTML post with <em>formatting</em>.</p>
<ul>
<li>First item</li>
<li>Second item</li>
</ul>
<blockquote>
<p>This is a quote block.</p>
</blockquote>
3. Content Conversion Utility
class ContentConverter {
// Convert HTML to simple Lexical
htmlToLexical(html) {
// Basic implementation - in practice, use Ghost's conversion utilities
const paragraphs = html.split('</p>').filter(p => p.trim());
const children = paragraphs.map(p => {
const text = p.replace(/<[^>]*>/g, '').trim();
return {
children: [{
detail: 0,
format: 0,
mode: "normal",
style: "",
text: text,
type: "text",
version: 1
}],
direction: "ltr",
format: "",
indent: 0,
type: "paragraph",
version: 1
};
});
return {
root: {
children: children,
direction: "ltr",
format: "",
indent: 0,
type: "root",
version: 1
}
};
}
// Convert Lexical to simple HTML
lexicalToHtml(lexical) {
const content = typeof lexical === 'string' ? JSON.parse(lexical) : lexical;
const paragraphs = content.root.children.map(child => {
if (child.type === 'paragraph') {
const text = child.children.map(textNode => textNode.text).join('');
return `<p>${text}</p>`;
}
return '';
});
return paragraphs.join('\n');
}
}
Best Practices
For MCP Implementation
- Default to Lexical: Use Lexical format for new content creation
- Support HTML Input: Allow HTML for ease of use and migration
- Validate JSON: Always validate Lexical JSON before sending to API
- Handle Conversion: Provide utilities for format conversion if needed
- Graceful Fallback: Handle legacy Mobiledoc content gracefully
Content Creation Guidelines
- Use Lexical for Rich Content: Complex layouts, cards, embeds
- Use HTML for Simple Content: Basic text formatting
- Provide Format Options: Let users choose their preferred input format
- Validate Before Submission: Check content validity before API calls
Implementation Status
- ✅ Content formats identified and documented
- ✅ API handling patterns documented
- ✅ Implementation strategy planned
- ✅ Validation approaches defined
- ⏳ Content conversion utilities pending implementation
- ⏳ Real format testing pending API access