Vibe Coding with Murit CMS
Modern developers and vibe coders build fast using AI tools like Cursor, Windsurf, Claude Code, OpenCode, and Antigravity. Don't spend hours asking AI to scaffold custom Postgres tables, rich-text serializers, and image upload handlers. Give your AI our Content API spec and connect an enterprise-grade blog in 30 seconds.
1. The 30-Second Drop-In AI Prompt
Copy and paste this prompt into Cursor Composer, Windsurf Cascade, OpenCode, Claude, or ChatGPT. Your AI will generate the entire blog listing, dynamic article detail page, and sitemap in one shot:
You are integrating a modern blog into this project using Murit CMS (a headless publishing platform).
Workspace media totals use database aggregates across all matching media records, with one grouped query for the authorized website list. Totals sum original-file sizes and exclude BYOS sites from the platform pool; they are not cloud-billing measurements. Public post-summary and sitemap queries omit article bodies from database reads. Response shapes, permissions, cache headers, and MCP tools are unchanged.
Legacy media: migration supports 100% self-serve in-browser media ingestion directly in the Admin Console (one-click "Ingest All Media Assets into Storage" trigger) and REST API (GET/POST /api/workspace/migrate-media with resilient batchSize: 5 and skipUrls). CLI ingestion is also available via "npm run media:migrate -- --website <ID> --rewrite-mode relative". Set sourceOrigin for root-relative paths; preview/import return mediaPreservation { strategy: "preserve-urls", sourceOrigin, urls, hostingVerified: false, notice }. Keep original paths serving or reverse-proxy them on the frontend (/wp-content/uploads/:path* -> S3/R2 bucket) for 100% SEO preservation with zero redirects. Featured references are managed in Media; inline images use externalImage blocks. Export HTML or Lexical with populated public image URLs, not foreign IDs. Public API contracts and read-only MCP queries keep their existing shapes.
Use these configuration environment variables:
- CMS_BASE_URL: Murit CMS origin (e.g. "http://localhost:3000" or production URL)
- CMS_SITE_KEY: Public website key (e.g. "site_your_site_key")
Public Content API v1 Endpoints:
1. List published posts:
GET ${CMS_BASE_URL}/api/content/v1/sites/${CMS_SITE_KEY}/posts?limit=12
Envelope: { data: [{ id, title, slug, excerpt, publishedAt, modifiedAt, featuredMedia, categories, tags, seo: { title, description, socialImage, canonicalURL, keywords } }], meta: { limit, nextCursor } }
2. Single post by slug:
GET ${CMS_BASE_URL}/api/content/v1/sites/${CMS_SITE_KEY}/posts/${slug}
Envelope: { data: { id, title, slug, excerpt, publishedAt, modifiedAt, featuredMedia, contentHtml, categories, tags, seo } }
Note: Automatically responds with HTTP 308 Permanent Redirect when queried with an entry from redirectUrls or legacyUrl.
3. Legacy & Astro Optimized Feed (for SSG or root-level blog migrations):
GET ${CMS_BASE_URL}/api/content/v1/sites/${CMS_SITE_KEY}/legacy-posts
Returns: { posts: [...] } envelope containing Astro-ready posts with preserved slugs, seoTitle/seoDescription, redirectUrls, and rich media fields (image, imageAlt, imageWidth, imageHeight).
4. Sitemap URLs:
GET ${CMS_BASE_URL}/api/content/v1/sites/${CMS_SITE_KEY}/sitemap?limit=500
Envelope: { data: [{ url, lastModified }], meta }
Requirements:
- Create the blog listing page and article detail page.
- In Next.js App Router, use /api/content/v1/sites/${CMS_SITE_KEY}/posts.
- In Astro 5 SSG, use /api/content/v1/sites/${CMS_SITE_KEY}/legacy-posts in getStaticPaths().
- CMS operators must deploy the registered 20260909_122610_legacy_redirects schema migration for legacy URL fields; frontend clients do not run CMS database migrations.
- Render post.contentHtml directly as it is sanitized server-side by the CMS. Social Embeds (X/Twitter, Instagram, LinkedIn, and Facebook with responsive 50% compact sizing), video embeds (YouTube, Vimeo, direct/uploaded), Also Read cards, checklists, code blocks (<pre><code class="language-{lang}">, HTML-escaped), inline images, and responsive column grids arrive pre-rendered; add CSS for the .content-columns--* and .content-embed--social layout classes. Links keep SEO rel tokens nofollow/sponsored/ugc (from the editor or imported rel), with noopener noreferrer appended for target="_blank" links — do not strip rel when rendering. Media resized in the editor keep their saved percentage — images via an inline style="width:{n}%" attribute and videos via a .content-video-wrap wrapper; full width emits no width.
- Imported articles: images may carry a <figcaption> and a link; captioned YouTube/Vimeo embeds are wrapped in figure.content-embed-figure; markup with no native block (tables, galleries, buttons, plugin widgets) arrives as allowlist-sanitized HTML inside div.content-legacy-html (no scripts, event handlers or javascript: URLs; iframes only from YouTube, Vimeo, Spotify, SoundCloud, Google Maps and the social networks). Style these like ordinary article HTML.
- Media assets are hosted on Murit Cloud Storage (200 MB free starter workspace allowance) or tenant-connected Cloudflare R2 / S3 / Google Cloud Storage (BYOS) with provider-specific allowances and billing.
- Architecture: Murit CMS utilizes a Thin Index Database + Cloud JSON Storage model. Article content and SEO metadata are saved in tenant cloud storage (sites/${CMS_SITE_KEY}/posts/${postId}/thick_content.json) with central database rows thinned out. Queries dynamically hydrate content with zero frontend overhead.
- Caching & Performance: Set up caching with { next: { revalidate: 60 } } or stale-while-revalidate headers.
- Migration limitations (tell your users): importing creates a copy in Murit CMS and never modifies the source post. Conversion is best-effort: only markup with a reliable structural, platform-class, or schema.org signal becomes a native block; everything else is preserved in a sanitized Legacy HTML block and flagged for review. Detection can be wrong, so review the full article before publishing, keep a source backup, and state clearly that re-publishing an imported post without review may surface an incorrect or unusual result. Inline images stay hotlinked until media ingestion runs.
- HTML Entity Sanitization & Rich Block Conversion: All articles imported via migration or authored in Payload have HTML entities (such as ' -> ', " -> ", & -> &, and typographic quotes ‘/’/“/”) decoded automatically across titles, excerpts, Lexical editor AST text nodes, categories, and tags. Migration automatically normalizes block aliases from other Payload 3 blogs (twitter, alsoRead, youtube, divider), converts legacy Also Read links into native RelatedArticle blocks, converts social embeds into native SocialEmbed (xEmbed) blocks with live editor previews, and preserves unsupported blocks as formatted Lexical elements or Legacy HTML so editor stability and content are guaranteed. The same normalization runs whenever a post is opened or saved in the editor, so supported blocks stored as plain paragraphs (Also Read links, bare social/YouTube/Vimeo URLs) are promoted to editable native blocks. Existing databases can be repaired with "npm run repair:entities".
- Migration (CMS-side, POST /api/workspace/migrate): fully supported sources are WordPress (source "wordpress", siteURL + page, 25 posts per page with _embed), Shopify (source "shopify-admin" with shopDomain *.myshopify.com + read_content Admin API token + cursor, or "shopify-feed" with feedURL), and Payload CMS (source "payload", siteURL/payloadSiteURL + page, 100 posts per page; supporting standard /api/posts and custom RJLS /rjls-api/posts endpoints), plus Murit/Payload JSON. Rendered HTML is parsed as a document tree: images (captions, links), YouTube/Vimeo/MP4 video, X/Instagram/Facebook/LinkedIn embeds, Also Read links, Gutenberg columns, separators and <pre> code (code block { code, language }, language kept, unknown -> plaintext) become native blocks; link rel nofollow/sponsored/ugc is kept on link nodes (fields.rel); anything else becomes a legacyHtml block instead of flattened text. Responses include report { articles, cleanArticles, elements, preserved, needsReview } and skippedCount; import returns postIds, and action "resolve-links" with postIds links Also Read blocks across batches. Block nodes are always stored as Lexical version 2 (version-1 nodes are upgraded on read).
- Handle 404s gracefully if post is unpublished or missing.
- Add canonical SEO tags and OpenGraph image metadata.
- Create a sitemap generator that pulls from the /sitemap endpoint.
- Project Deletion & Grace Period: Tenants can request project deletion with an automatic 48-hour grace period via POST /api/workspace/websites/{id}/delete-request (confirming with project name or DELETE). During the 48 hours, public Content API requests return 404 while all articles and media remain safe. Tenants can reverse the decision and restore the project at any time via POST /api/workspace/websites/{id}/restore. Once the deadline passes, restore returns HTTP 410, the project is permanently purged (website, posts, media including cloud-storage objects, categories, tags, memberships), and it disappears from GET /api/workspace.2. Machine-Readable Documentation: llms.txt
Murit CMS provides dedicated endpoints formatted specifically for Large Language Model context windows:
/llms.txt
A concise, markdown-formatted summary of Murit CMS, architecture rules, and endpoint contracts designed for system prompts and cursor rules.
/llms-full.txt
The complete API specification including all JSON request/response DTO schemas, TypeScript type definitions, and copy-paste Next.js/Astro templates.
Tip for Cursor & Windsurf: Add @https://your-cms-url/llms.txt directly into your chat or add it to your project's .cursorrules file to give the model immediate zero-shot awareness of your CMS.
3. Model Context Protocol (MCP) Server
Murit CMS includes a built-in stdio Model Context Protocol (MCP) server. This lets AI coding assistants like Claude Desktop, Cursor, and Antigravity call tools directly against your Murit CMS instance while you code.
verify_connectivity
Check connectivity to the Murit CMS server and verify the configured site key.
get_site_info
Retrieve public metadata and configuration for a registered Murit CMS website.
list_posts
List published post summaries with pagination and optional category/tag filters.
get_post_by_slug
Retrieve a single published post by slug (supports automatic resolution of legacy & redirect URLs), including full pre-rendered sanitized HTML.
get_sitemap
Fetch all published post URLs and last-modified dates for SEO sitemap generation.
get_legacy_posts
Fetch all published posts as an Astro/flat legacy feed with preserved URLs, redirects, and rich HTML. Images may remain on their original host; retain those URLs unchanged. This tool does not copy or verify media.
Add this configuration to your Claude Desktop config (claude_desktop_config.json), Cursor (.cursor/mcp.json), or OpenCode (opencode.json):
{
"mcpServers": {
"murit-cms": {
"command": "npx",
"args": ["-y", "tsx", "scripts/mcp-server.ts"],
"env": {
"CMS_BASE_URL": "http://localhost:3000",
"CMS_SITE_KEY": "site_your_site_key"
}
}
}
}4. Agent System Rules (.cursorrules / CLAUDE.md / OPENCODE.md / AGENTS.md)
Drop these rules into your project's root directory as .cursorrules, CLAUDE.md, OPENCODE.md, or AGENTS.md. When you prompt your AI, it will automatically follow these rules without needing repeated instructions:
# Murit CMS Agent Integration Rules
You are integrating or maintaining content features powered by Murit CMS.
## Configuration
- CMS Base URL: process.env.CMS_BASE_URL (or central instance origin)
- CMS Site Key: process.env.CMS_SITE_KEY (public site identifier, safe in client code)
## Endpoints
- List posts: GET ${CMS_BASE_URL}/api/content/v1/sites/${CMS_SITE_KEY}/posts?limit=12
- Single post: GET ${CMS_BASE_URL}/api/content/v1/sites/${CMS_SITE_KEY}/posts/${slug}
- Legacy / Astro posts: GET ${CMS_BASE_URL}/api/content/v1/sites/${CMS_SITE_KEY}/legacy-posts
- Sitemap: GET ${CMS_BASE_URL}/api/content/v1/sites/${CMS_SITE_KEY}/sitemap?limit=500
- OpenAPI 3.1 Spec: GET ${CMS_BASE_URL}/openapi.json
- LLM System Context: GET ${CMS_BASE_URL}/llms.txt
## Best Practices
1. Post content is sanitized server-side: render `post.contentHtml` directly with dangerouslySetInnerHTML or Astro set:html.
2. For Astro static generation, call `/legacy-posts` to map `getStaticPaths()` and preserve legacy URL structures and 301 redirects.
3. HTML Entity Sanitization: Legacy blog imports and editor inputs automatically decode all named, decimal, and hex HTML entities (e.g. `'` -> `'`, `"` -> `"`, `&` -> `&`, and typographic quotes) across post titles, excerpts, Lexical AST text nodes, taxonomies, and media alt texts. Existing databases can be batch-normalized via `npm run repair:entities`.
4. Never create custom database tables or migrations for editorial content; articles are authored in Murit CMS Payload Admin Console.
5. Content API responses provide HTTP caching: set `s-maxage=60, stale-while-revalidate=300` or Next.js `{ next: { revalidate: 60 } }`.
6. Project setup: General includes default 200 MB workspace storage or optional BYOS. Next opens review with optional webhooks, SEO and localization. Only Create project and site key creates the project. Provider guides: /docs/storage (R2 recommended; S3 and GCS supported; Azure Blob unsupported).
7. Media & Content Storage Architecture: Workspaces start with a 200 MB free platform storage pool. To use their own storage allowance and retain total data sovereignty, tenants connect Bring Your Own Storage (BYOS) with Cloudflare R2 / AWS S3 / GCS. Murit CMS uses a Thin Index + Cloud JSON Storage model where full post bodies and SEO live in tenant storage, and the central database stores lightweight operational indexes.
8. Cross-Origin Resource Sharing (CORS): Public Content API and media endpoints return `Access-Control-Allow-Origin: *` and support `OPTIONS` preflight, enabling direct client-side fetch from browsers and SPAs.