# 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 1. **Mobiledoc** (Legacy): Ghost's previous JSON-based content format 2. **HTML**: Traditional markup format 3. **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 ```json { "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 ```json { "html": "
Welcome to Ghost!
This is a simple HTML paragraph.
" } ``` #### 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 ```javascript // 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)**: ```json { "posts": [ { "id": "post_id", "title": "My Post", "lexical": "{\"root\":{...}}", "slug": "my-post" } ] } ``` **HTML and Lexical**: ```json { "posts": [ { "id": "post_id", "title": "My Post", "lexical": "{\"root\":{...}}", "html": "Post content
", "slug": "my-post" } ] } ``` ### Content Creation & Updates #### Admin API Content Fields ```javascript // 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: "HTML content here
", status: "draft" }] }; // Creating with both (Lexical takes precedence) const postData = { posts: [{ title: "New Post", lexical: JSON.stringify(lexicalContent), html: "Fallback HTML
", status: "draft" }] }; ``` ## Content Format Priorities ### Create/Update Priority Order 1. **Lexical** (highest priority) 2. **HTML** (fallback if no Lexical) 3. **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 ```javascript 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: `${content}
` }; } } } ``` ### MCP Tool Content Parameters #### Create Post Tool ```javascript 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 ```javascript 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 ```json { "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 ```htmlThis is a rich HTML post with formatting.
``` ### 3. Content Conversion Utility ```javascript class ContentConverter { // Convert HTML to simple Lexical htmlToLexical(html) { // Basic implementation - in practice, use Ghost's conversion utilities const paragraphs = html.split('').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 `This is a quote block.
${text}
`; } return ''; }); return paragraphs.join('\n'); } } ``` ## Best Practices ### For MCP Implementation 1. **Default to Lexical**: Use Lexical format for new content creation 2. **Support HTML Input**: Allow HTML for ease of use and migration 3. **Validate JSON**: Always validate Lexical JSON before sending to API 4. **Handle Conversion**: Provide utilities for format conversion if needed 5. **Graceful Fallback**: Handle legacy Mobiledoc content gracefully ### Content Creation Guidelines 1. **Use Lexical for Rich Content**: Complex layouts, cards, embeds 2. **Use HTML for Simple Content**: Basic text formatting 3. **Provide Format Options**: Let users choose their preferred input format 4. **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