<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tony Spiro</title>
    <description>The latest articles on DEV Community by Tony Spiro (@tonyspiro).</description>
    <link>https://dev.to/tonyspiro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F36636%2F37a4c910-90d9-40b1-9a8b-69e1ad31b4f6.jpeg</url>
      <title>DEV Community: Tony Spiro</title>
      <link>https://dev.to/tonyspiro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tonyspiro"/>
    <language>en</language>
    <item>
      <title>The Content Flywheel: How We Run AI Agents From Publish to Insights and Back</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Fri, 21 Aug 2026 15:46:32 +0000</pubDate>
      <link>https://dev.to/tonyspiro/the-content-flywheel-how-we-run-ai-agents-from-publish-to-insights-and-back-3m64</link>
      <guid>https://dev.to/tonyspiro/the-content-flywheel-how-we-run-ai-agents-from-publish-to-insights-and-back-3m64</guid>
      <description>&lt;p&gt;Most AI content tooling stops at the draft. You prompt a model, it produces something, a human edits it, and the loop ends there. Whatever happens to that content after publish never makes it back to the thing that wrote it.&lt;/p&gt;

&lt;p&gt;At Cosmic, we've completed the loop. Our agents publish into Cosmic, &lt;a href="https://www.cosmicjs.com/insights" rel="noopener noreferrer"&gt;Cosmic Insights&lt;/a&gt; measures what the content does, and the same agents read that data on a schedule and decide what to write, fix, or retire. That closed loop is what we call the content flywheel, and we run it on cosmicjs.com every day.&lt;/p&gt;

&lt;p&gt;Here is how it actually works, with real numbers from the project that powers content on this website.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four stages
&lt;/h2&gt;

&lt;p&gt;A content flywheel needs four things wired together. Miss one and it stops being a loop.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A content model an agent can write to.&lt;/strong&gt; Structured, queryable fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An agent with real capabilities.&lt;/strong&gt; Read the CMS, write to the CMS, browse the web, post to Slack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measurement attached to the content.&lt;/strong&gt; Traffic attributed back to the object itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A scheduled goal that closes the loop.&lt;/strong&gt; Something that wakes up, reads the data, and acts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cosmic gives you all four in one place, which is the entire reason the loop is short enough to be useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: The content model
&lt;/h2&gt;

&lt;p&gt;Our blog post type has the fields you would expect, plus the ones that make measurement possible: &lt;code&gt;seo_title&lt;/code&gt;, &lt;code&gt;seo_description&lt;/code&gt;, &lt;code&gt;teaser&lt;/code&gt;, &lt;code&gt;published_date&lt;/code&gt;, &lt;code&gt;last_updated&lt;/code&gt;, and relationship fields for author, category, and tags.&lt;/p&gt;

&lt;p&gt;That structure matters more than it sounds. Because &lt;code&gt;last_updated&lt;/code&gt; is a real date field and &lt;code&gt;tags&lt;/code&gt; is a real relationship, an agent can ask questions like "which posts tagged AI Agents have not been touched in 90 days" and get an answer it can act on. A single freeform body field leaves that question unanswerable.&lt;/p&gt;

&lt;p&gt;Agents read and write this model through the same REST API and TypeScript SDK your app uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Find posts in a topic cluster that are going stale.&lt;/span&gt;
&lt;span class="c1"&gt;// Query relationship fields by object ID, not slug: slugs can&lt;/span&gt;
&lt;span class="c1"&gt;// collide across object types and resolve to the wrong object.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata.tags&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6a21aa1ed66dd9646b9e028a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// "AI Agents" tag object ID&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata.last_updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$lt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-05-20&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id,title,slug,metadata.last_updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent uses the same query. There is no separate agent API to learn and no second copy of your content to keep in sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: The agents
&lt;/h2&gt;

&lt;p&gt;An agent in Cosmic is a team member you configure. You grant capabilities: read content, write content, browse the web, call external APIs, send messages to Slack or Telegram, read analytics, work on a connected repository. What the agent does is decided by the capabilities you give it.&lt;/p&gt;

&lt;p&gt;Ours are split by job, the same way a content team is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A content lead agent that researches, writes, and publishes into the bucket.&lt;/li&gt;
&lt;li&gt;A social coordinator agent that pulls the day's published posts and writes platform-specific copy.&lt;/li&gt;
&lt;li&gt;A growth agent that reads Insights and reports to the team in Slack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They hand work to each other. The social agent asks the content agent what shipped in the last 24 hours, gets back titles, slugs, and categories, and writes from that instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Measurement that points back at the content
&lt;/h2&gt;

&lt;p&gt;This is the stage most teams skip, and it is the one that makes the loop real.&lt;/p&gt;

&lt;p&gt;Cosmic Insights tracks pageviews, visitors, sessions, bounce rate, custom events, and revenue. The part that matters for a flywheel is object attribution. Add one meta tag to your page and traffic rolls up by Cosmic object alongside the URL path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cosmic-context"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;'{"object_id":"6a873688f234aa885b778173","object_type":"blog-posts"}'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the agent that wrote the post can ask how that exact object performed. Custom events fill in the rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cosmicInsights&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup_cta_click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6a873688f234aa885b778173&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;object_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On cosmicjs.com, here is what our event data looked like over the 30 days ending August 20, 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Events&lt;/th&gt;
&lt;th&gt;Visitors&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;signup_started&lt;/td&gt;
&lt;td&gt;751&lt;/td&gt;
&lt;td&gt;645&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;signup_completed&lt;/td&gt;
&lt;td&gt;350&lt;/td&gt;
&lt;td&gt;335&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cta_click&lt;/td&gt;
&lt;td&gt;314&lt;/td&gt;
&lt;td&gt;247&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;signup_cta_click&lt;/td&gt;
&lt;td&gt;265&lt;/td&gt;
&lt;td&gt;223&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lesson_view&lt;/td&gt;
&lt;td&gt;243&lt;/td&gt;
&lt;td&gt;97&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A 46.6% completion rate from signup start to finish is a number we can hold a strategy against. Google organic sent 19,664 events from 16,838 visitors in that same window, which tells us which half of the funnel deserves attention. These are rolling-window figures, so they move; the point is that an agent can pull them on demand rather than guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: The scheduled goal that closes it
&lt;/h2&gt;

&lt;p&gt;A capability without a cadence is still a manual process. In Cosmic you give an agent a goal with a cron schedule, and it works through that goal in a bounded loop until it decides it is done.&lt;/p&gt;

&lt;p&gt;Our loop-closing goals look roughly like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weekly, Tuesday morning:&lt;/strong&gt; read the last seven days of Insights by path and by custom event, compare against the prior seven, and post the deltas to Slack with content recommendations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekly:&lt;/strong&gt; scan competitor blogs and pricing pages, cite every claim with the URL fetched in that same run, and recommend counter-content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily:&lt;/strong&gt; report which posts published in the last 24 hours so downstream agents can act on them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule we enforce on every one of these: every number in the output has to come from a tool call made in that same run. No remembered figures, no plausible-looking estimates. An agent that fabricates a metric poisons the flywheel faster than a slow one starves it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The week the flywheel caught something we missed
&lt;/h2&gt;

&lt;p&gt;Here is the concrete example, and it is not a flattering one.&lt;/p&gt;

&lt;p&gt;Our highest-traffic blog cluster is a set of AI coding tool comparisons. They pull thousands of Google visitors a month. When we pulled &lt;code&gt;signup_cta_click&lt;/code&gt; broken down by page, none of those posts appeared in the list. Zero measured CTA clicks against our best organic traffic.&lt;/p&gt;

&lt;p&gt;The obvious conclusion was that the posts had no calls to action. So we read the objects back out of the CMS and checked. Every post had a mid-article signup block and a bottom CTA block, both rendering correctly on the live page.&lt;/p&gt;

&lt;p&gt;The actual problem was in the shared CTA blocks. Cosmic lets you define reusable rich-text blocks and reference them from any post with a token like &lt;code&gt;{{bottom-cta}}&lt;/code&gt;. Ours pointed at a bare signup URL with no UTM parameters and no click event bound to it. The clicks were happening. Nothing was recording them.&lt;/p&gt;

&lt;p&gt;One edit to two shared blocks instrumented every blog post in the bucket at once, because the blocks are referenced by every post that uses them. The blog channel now shows up in source attribution.&lt;/p&gt;

&lt;p&gt;The most valuable output here was diagnostic. The flywheel told us that a question we thought we had answered had never actually been measured.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we got wrong the first time
&lt;/h2&gt;

&lt;p&gt;Three honest lessons from running this for a while:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attribution gaps look like content failures.&lt;/strong&gt; Our first read of the data said the comparison posts were bad at converting. The real answer was that we could not see the conversions. Verify instrumentation before you rewrite content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the object back after every write.&lt;/strong&gt; We have had writes report success while silently dropping a field, usually on repeaters and relationship fields. Our agents now re-read every object after writing it and confirm the field actually stored. Use explicit object IDs for relationship fields, since slugs can collide across object types.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bounce rate is a weak signal on its own.&lt;/strong&gt; A developer who lands from Google, reads a comparison post, and leaves satisfied counts as a bounce. Pair it with custom events before you conclude anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building your own
&lt;/h2&gt;

&lt;p&gt;The shortest path to a working loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Model your content with real fields, especially dates and relationships. Agents can only act on structure they can query.&lt;/li&gt;
&lt;li&gt;Add the Insights script and a &lt;code&gt;cosmic-context&lt;/code&gt; meta tag to your content templates so traffic rolls up by object.&lt;/li&gt;
&lt;li&gt;Fire one custom event on your primary conversion action.&lt;/li&gt;
&lt;li&gt;Create an agent with content read and write capability, plus analytics access.&lt;/li&gt;
&lt;li&gt;Give it one scheduled goal that reads last week's data and reports what changed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start with step five as a report-only goal. Let it tell you what it sees for a few weeks before you let it publish anything. Trust in the loop should be earned with evidence, and the loop is very good at producing evidence.&lt;/p&gt;

&lt;p&gt;You can read more about how agents are configured on the &lt;a href="https://www.cosmicjs.com/ai/agents" rel="noopener noreferrer"&gt;Cosmic AI agents page&lt;/a&gt;, or connect your own AI tools directly to your content model through the &lt;a href="https://www.cosmicjs.com/mcp-server" rel="noopener noreferrer"&gt;Cosmic MCP server&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Give your AI agents a content backend they can write to.&lt;/strong&gt; Structured, versioned content objects, a REST API and TypeScript SDK, and an MCP server your coding agent connects to directly. The Free plan includes 1 Bucket, 1,000 Objects, and 1 agent. No credit card required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog-content&amp;amp;utm_content=devto-bottom-signup-cta" rel="noopener noreferrer"&gt;Start for free&lt;/a&gt; or &lt;a href="https://www.cosmicjs.com/ai?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog-content&amp;amp;utm_content=devto-bottom-ai-page" rel="noopener noreferrer"&gt;see Cosmic for AI teams&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cms</category>
      <category>webdev</category>
      <category>seo</category>
    </item>
    <item>
      <title>How to Migrate from Payload CMS to Cosmic</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Sat, 15 Aug 2026 19:34:05 +0000</pubDate>
      <link>https://dev.to/tonyspiro/how-to-migrate-from-payload-cms-to-cosmic-4p7c</link>
      <guid>https://dev.to/tonyspiro/how-to-migrate-from-payload-cms-to-cosmic-4p7c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Last verified: August 14, 2026.&lt;/strong&gt; Every claim about Payload on this page was checked against Payload's own live pages on that date: &lt;a href="https://payloadcms.com/cloud-pricing" rel="noopener noreferrer"&gt;Payload Cloud pricing and status&lt;/a&gt;, &lt;a href="https://payloadcms.com/docs/getting-started/what-is-payload" rel="noopener noreferrer"&gt;What is Payload&lt;/a&gt;, and the &lt;a href="https://payloadcms.com/docs/local-api/overview" rel="noopener noreferrer"&gt;Local API docs&lt;/a&gt;. Cosmic pricing was checked against &lt;a href="https://www.cosmicjs.com/pricing" rel="noopener noreferrer"&gt;cosmicjs.com/pricing&lt;/a&gt;. Note that &lt;code&gt;payloadcms.com/pricing&lt;/code&gt; returned a 404 on this date; Cloud plan details now live on the &lt;code&gt;/cloud-pricing&lt;/code&gt; page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What actually changed at Payload
&lt;/h2&gt;

&lt;p&gt;Payload has joined Figma. The announcement is &lt;a href="https://www.figma.com/blog/payload-joins-figma/" rel="noopener noreferrer"&gt;on the Figma blog&lt;/a&gt;, and it is linked from the top of every page on payloadcms.com.&lt;/p&gt;

&lt;p&gt;Two things follow from that, both stated by Payload directly on its Cloud page:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;New Payload Cloud deployments are paused.&lt;/strong&gt; In Payload's words: "Although deployment of new projects is currently paused, existing Cloud projects will continue running as normal."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing Cloud projects will eventually move.&lt;/strong&gt; From the same page's FAQ, answering "Will I need to migrate my project?": "Yes, eventually. There is no rush, but we are planning to build something better that you will be able to migrate to once it's available."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is what has &lt;em&gt;not&lt;/em&gt; changed. Payload the framework is still open source, still actively developed, and still self-hostable. Payload's own docs state that Payload remains a self-hosted solution, and that anywhere you can run a Next.js app, you can run Payload. If you self-host Payload today, nothing above forces your hand.&lt;/p&gt;

&lt;p&gt;The teams with a real decision to make are the ones who picked Payload Cloud because they wanted someone else to run the database, the file storage, and the deploys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you migrate at all?
&lt;/h2&gt;

&lt;p&gt;Three honest options. Pick based on your team, not on vendor news.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stay on Payload and self-host it.&lt;/strong&gt; The right call if your content model leans on Payload's code-first strengths: custom access control, hooks, field-level permissions, or an admin panel you have meaningfully customized in React. You will need to own a database, object storage, and a deploy target. Payload is a Next.js application, so this is familiar work for a Next.js team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wait for whatever Figma ships.&lt;/strong&gt; Payload says there is no rush and that a migration path to something new is planned. If your project is stable and you have infrastructure people, waiting costs you little.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Move to a managed API-first CMS.&lt;/strong&gt; The right call if the reason you chose Payload Cloud was that you did not want to run infrastructure, and you would rather not go back to running it. That is the path this guide covers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you give up moving to Cosmic
&lt;/h2&gt;

&lt;p&gt;Stating this plainly, because a migration guide that pretends there are no tradeoffs is not worth reading.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Local API goes away.&lt;/strong&gt; Payload's biggest technical advantage is that it runs in the same Node process as your app, so you can query the database directly from a React Server Component with no network hop. Cosmic is an HTTP API. For most content sites the difference disappears behind caching and static generation, but it is a genuine architectural change and you should know it going in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No GraphQL.&lt;/strong&gt; Payload exposes REST and GraphQL. Cosmic offers a REST API and a TypeScript SDK, and does not offer GraphQL. If your frontend is built on Payload's GraphQL endpoint, that query layer gets rewritten.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code-first config becomes dashboard-first modeling.&lt;/strong&gt; In Payload your collections live in version-controlled TypeScript. In Cosmic you define Object Types in the dashboard or over the API. Some teams consider that a downgrade in reviewability, and others consider it the point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom admin components.&lt;/strong&gt; React components you injected into the Payload admin panel do not carry over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth and access control.&lt;/strong&gt; Payload ships user auth and granular access control as first-class features. If you used Payload as your application's auth layer and not only as a CMS, that responsibility moves elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If several of those matter a lot to you, self-hosting Payload is probably the better answer, and you should stop reading here. No hard feelings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping Payload concepts to Cosmic
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Payload&lt;/th&gt;
&lt;th&gt;Cosmic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Collection&lt;/td&gt;
&lt;td&gt;Object Type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document&lt;/td&gt;
&lt;td&gt;Object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;Single Object (a type with one Object)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field&lt;/td&gt;
&lt;td&gt;Metafield&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;slug&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Object slug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upload collection&lt;/td&gt;
&lt;td&gt;Media library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relationship field&lt;/td&gt;
&lt;td&gt;Object metafield (&lt;code&gt;object&lt;/code&gt; / &lt;code&gt;objects&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array field&lt;/td&gt;
&lt;td&gt;Repeater metafield&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocks field&lt;/td&gt;
&lt;td&gt;Repeater, or rich text with Content Blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group field&lt;/td&gt;
&lt;td&gt;Parent metafield group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Draft / published&lt;/td&gt;
&lt;td&gt;Object status (&lt;code&gt;draft&lt;/code&gt; / &lt;code&gt;published&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Localization&lt;/td&gt;
&lt;td&gt;Locale variants on an Object Type&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shapes line up closely enough that most migrations are a data-transform problem rather than a redesign.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: inventory what you actually have
&lt;/h2&gt;

&lt;p&gt;Before writing any script, list every collection and global, the document count in each, and which fields are genuinely used. Migrations balloon because teams port fields nobody has filled in for two years.&lt;/p&gt;

&lt;p&gt;Payload's Local API gives you counts quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getPayload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@payload-config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;totalDocs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;totalDocs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write the output down. It is your migration checklist and your verification target at cutover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: export from Payload
&lt;/h2&gt;

&lt;p&gt;Run this inside your Payload project so &lt;code&gt;@payload-config&lt;/code&gt; resolves. Two details matter: &lt;code&gt;pagination: false&lt;/code&gt; returns every document instead of the first page, and &lt;code&gt;depth: 0&lt;/code&gt; keeps relationships as raw IDs instead of expanding them into nested objects. You want raw IDs, because you are going to remap them yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getPayload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@payload-config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./payload-export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Collections&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pagination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;overrideAccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`exported &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; from &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Globals&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findGlobal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`global-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit that folder somewhere safe. Everything after this point is reversible as long as the export exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: model your content in Cosmic
&lt;/h2&gt;

&lt;p&gt;Create one Object Type per Payload collection. You can do it in the dashboard, or script it so the whole migration is repeatable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;writeKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_WRITE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objectTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;singular&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;metafields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;excerpt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Excerpt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textarea&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;markdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hero Image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;object_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migrate types with no relationships first (authors, categories, tags), then the types that point at them. That ordering saves you a second reconciliation pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: move the media
&lt;/h2&gt;

&lt;p&gt;Payload upload documents store a filename and a URL. Pull each file and push it into the Cosmic media library, keeping a map from the old Payload ID to the new Cosmic media name so you can rewrite references later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;uploadDocs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;media&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payload-import&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alt_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;idMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you set &lt;code&gt;alt&lt;/code&gt; text in Payload, carry it across now. Alt text lives on the Cosmic media record itself, so every Object referencing that image inherits it, which is one less accessibility cleanup later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: convert rich text
&lt;/h2&gt;

&lt;p&gt;This is the step that surprises people, so budget real time for it.&lt;/p&gt;

&lt;p&gt;Payload stores rich text as a structured JSON tree, not as markdown or HTML. You cannot drop that JSON into a markdown metafield and expect it to render. You need a serializer that walks the tree and emits markdown or HTML.&lt;/p&gt;

&lt;p&gt;Payload's rich text documentation covers converting its editor state to other formats, and that is the tool to reach for. Two things to watch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Uploads embedded in rich text&lt;/strong&gt; become nodes referencing an upload ID. Rewrite those to the Cosmic media URLs from your &lt;code&gt;idMap&lt;/code&gt; in Step 4.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocks embedded in rich text&lt;/strong&gt; need a destination. Either flatten them into markdown, or create a reusable Cosmic Content Block and reference it with a &lt;code&gt;{{block-name /}}&lt;/code&gt; token in a rich text metafield.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Convert one document, eyeball the output, then convert the rest. Do not batch-convert 2,000 documents on faith.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: import into Cosmic
&lt;/h2&gt;

&lt;p&gt;With media mapped and rich text serialized, the import itself is short. Keep a Payload-ID to Cosmic-ID map as you go so relationship fields can be resolved on a second pass.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./payload-export/posts.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;writeKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_WRITE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;excerpt&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;toMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hero&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;idMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authorIdMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preserve the original &lt;code&gt;slug&lt;/code&gt; values. That single decision is what keeps your URLs, your rankings, and your inbound links intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: rewire the frontend
&lt;/h2&gt;

&lt;p&gt;If your app is Next.js, this is the most mechanical part of the whole project. Payload Local API calls become Cosmic SDK calls.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-publishedDate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-created_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;.props()&lt;/code&gt; to request only the fields the page renders. Payload's Local API had no network cost, so over-fetching was cheap. Over an HTTP API it is not, and trimming the payload is the single easiest performance win in the port.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: verify, then cut over
&lt;/h2&gt;

&lt;p&gt;Work through this before you flip DNS:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Counts match.&lt;/strong&gt; Compare every Object Type count against the inventory from Step 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot-check the ugly documents.&lt;/strong&gt; Not the simple ones. The post with nested blocks, four images, and a relationship array.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every relationship resolves.&lt;/strong&gt; Query with &lt;code&gt;.depth(1)&lt;/code&gt; and confirm nothing comes back null.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slugs are identical&lt;/strong&gt; to production Payload for every public URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media loads&lt;/strong&gt; on a real page render, not only in the dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirects staged&lt;/strong&gt; for any URL that genuinely had to change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep Payload running&lt;/strong&gt; in parallel until the checks pass. There is no prize for deleting it early.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What this costs on Cosmic
&lt;/h2&gt;

&lt;p&gt;Verified against &lt;a href="https://www.cosmicjs.com/pricing" rel="noopener noreferrer"&gt;cosmicjs.com/pricing&lt;/a&gt; on August 14, 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Buckets&lt;/th&gt;
&lt;th&gt;Team members&lt;/th&gt;
&lt;th&gt;Objects&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0/mo&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Builder&lt;/td&gt;
&lt;td&gt;$49/mo&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team&lt;/td&gt;
&lt;td&gt;$299/mo&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business&lt;/td&gt;
&lt;td&gt;$499/mo&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;50,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Additional team members beyond a plan's included seats are $29/user/month.&lt;/p&gt;

&lt;p&gt;The practical move is to run the whole migration on the Free plan first. One Bucket and 1,000 Objects is enough to prove the export, the transform, and the import against real content before anyone approves a budget line.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Payload CMS being shut down?&lt;/strong&gt;&lt;br&gt;
No, and nothing on this page should be read that way. Payload is open source and self-hostable, and Payload states that existing Cloud projects continue running as normal. What changed is that new Payload Cloud deployments are paused, and Payload says existing Cloud projects will eventually need to migrate to a future replacement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I keep my URLs?&lt;/strong&gt;&lt;br&gt;
Yes, as long as you carry the &lt;code&gt;slug&lt;/code&gt; field across unchanged. Cosmic Objects have a slug you control, so a one-to-one mapping is normal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Cosmic support GraphQL?&lt;/strong&gt;&lt;br&gt;
No. Cosmic provides a REST API and a TypeScript SDK. Payload does expose GraphQL, so if your frontend depends on it, plan on rewriting that data layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does a migration take?&lt;/strong&gt;&lt;br&gt;
It depends almost entirely on rich text and blocks. A handful of collections with plain fields is an afternoon. A heavily block-driven site with thousands of documents is a multi-week project, and the serializer is where the time goes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I run both during the transition?&lt;/strong&gt;&lt;br&gt;
Yes, and you should. Import into Cosmic, point a staging branch at it, verify against the Step 8 checklist, and only then cut over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;If you were on Payload Cloud for the managed hosting and you want to stay out of the infrastructure business, the cheapest way to evaluate Cosmic is to migrate one collection and look at the result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.cosmicjs.com/signup?utm_source=dev.to&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=migrate-payload-cms-to-cosmic&amp;amp;utm_content=closing-cta"&gt;Create a free Bucket&lt;/a&gt; and run the export script above against your smallest collection. If you would rather walk through your content model with someone first, &lt;a href="https://calendly.com/tonyspiro/cosmic-intro" rel="noopener noreferrer"&gt;book time with our CEO Tony Spiro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;More reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cosmicjs.com/payload-alternative" rel="noopener noreferrer"&gt;Payload CMS Alternative&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cosmicjs.com/blog/payload-cms-vs-cosmic-which-headless-cms-is-right-for-you" rel="noopener noreferrer"&gt;Payload CMS vs Cosmic: Which Headless CMS Is Right for You?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cosmicjs.com/blog/payload-vs-strapi" rel="noopener noreferrer"&gt;Payload vs Strapi: Which Open-Source Headless CMS Should You Choose in 2026?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>headlesscms</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Claude Marks Its AI-Generated Content Now: What That Means for Your CMS</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Tue, 11 Aug 2026 22:03:00 +0000</pubDate>
      <link>https://dev.to/tonyspiro/claude-marks-its-ai-generated-content-now-what-that-means-for-your-cms-20cg</link>
      <guid>https://dev.to/tonyspiro/claude-marks-its-ai-generated-content-now-what-that-means-for-your-cms-20cg</guid>
      <description>&lt;p&gt;Anthropic published a support article on &lt;a href="https://support.claude.com/en/articles/16266773-how-claude-marks-ai-generated-content" rel="noopener noreferrer"&gt;how Claude marks AI-generated content&lt;/a&gt;, and it spent this morning on the Hacker News front page. The document itself is short and mostly technical. The discussion was neither, because it lands on a question most content teams using AI have been quietly deferring: when a model touches your content, is there a record, and whose job is it to keep one?&lt;/p&gt;

&lt;p&gt;Here is what actually shipped, what it can and cannot prove, and the part that falls to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Anthropic shipped
&lt;/h2&gt;

&lt;p&gt;Anthropic signed the EU AI Act's Article 50(2) Code of Practice on Transparency of AI-Generated Content. Claude models launched in the EU on or after August 2, 2026 support machine-readable marking at launch, and Anthropic says it is working to add marking support to models released before that date.&lt;/p&gt;

&lt;p&gt;There are two mechanisms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imperceptible watermarks in text.&lt;/strong&gt; Claude weaves a mark directly into the text it generates. A reader cannot see it, and Anthropic states it does not change the meaning, quality, or readability of the output. Because the watermark is part of the text, it travels with the text when it is copied and pasted, and it may persist through some editing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C2PA signed provenance metadata on files.&lt;/strong&gt; When Claude generates a supported file type, including &lt;code&gt;.svg&lt;/code&gt;, &lt;code&gt;.png&lt;/code&gt;, and &lt;code&gt;.jpg&lt;/code&gt;, it attaches signed provenance metadata following the &lt;a href="https://c2pa.org/" rel="noopener noreferrer"&gt;Coalition for Content Provenance and Authenticity&lt;/a&gt; open standard. A signed label signals the file was processed by Claude and lets you detect whether it has been tampered with.&lt;/p&gt;

&lt;p&gt;Marking is applied at the model level, so it follows the models across the surfaces they run on: Claude Platform (API), Claude, Claude Code, Claude Cowork, and Claude Tag. Embedded watermarks also apply when supported models are accessed through AWS, Google Cloud, or Microsoft Foundry. Anthropic is explicit that coverage is not uniform, and that signed file metadata in particular may not be supported on every platform. Marking applies worldwide, not only to users in the EU. Detection documentation is described as forthcoming.&lt;/p&gt;

&lt;p&gt;If you build products on Claude, Anthropic's own guidance is that you should independently assess what Article 50 requires of you. Signing the code of practice covers Anthropic's models. It does not discharge your obligations for the thing you shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a detected mark tells you, and what it does not
&lt;/h2&gt;

&lt;p&gt;The limitations section is the most useful part of that document for anyone running a publishing pipeline, and Anthropic is unusually direct about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A mark means Claude processed the text.&lt;/strong&gt; It does not establish that Claude wrote it. Anthropic gives the examples itself: people use Claude to proofread, translate, summarize, and convert files, and the output can carry a mark even when the underlying ideas, text, or data came from somewhere else. Any policy that treats a detected mark as proof of machine authorship will produce false accusations against your own writers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Absence of a mark proves nothing.&lt;/strong&gt; Anthropic lists the cases: content from a model released before marking was supported, text that has been heavily edited or paraphrased or translated, passages too short to carry a reliable signal, and files whose metadata was stripped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File metadata is fragile in completely ordinary ways.&lt;/strong&gt; Anthropic names format conversion, re-saving, and screenshots as things that strip C2PA metadata. If you have ever run images through an optimization or resizing pipeline, you have probably stripped provenance metadata already without noticing. Verify what actually survives your own media pipeline before you rely on it.&lt;/p&gt;

&lt;p&gt;So the honest summary: watermarking is a meaningful transparency measure for the open internet and a weak internal record for your team. Detection answers "did a model touch this" with a qualified maybe. It does not answer the questions your team will actually be asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions you will actually be asked
&lt;/h2&gt;

&lt;p&gt;When a claim in a published post turns out to be wrong, or a customer asks how your documentation is produced, or legal asks what your AI disclosure covers, the questions are specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which model produced this draft, at what version?&lt;/li&gt;
&lt;li&gt;Was it a person using an assistant, or an agent running unattended?&lt;/li&gt;
&lt;li&gt;Which human reviewed it before it went live, and on what date?&lt;/li&gt;
&lt;li&gt;Were the product facts, pricing, and customer references checked against a source of truth?&lt;/li&gt;
&lt;li&gt;How many published pages are fully machine-generated and have never been read by anyone on the team?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those are recoverable from an invisible watermark, even a perfectly detected one. All of them are trivially recoverable from your CMS, if you decided in advance to store them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store provenance as typed fields
&lt;/h2&gt;

&lt;p&gt;Provenance that lives in a Slack thread, a spreadsheet, or someone's memory is not provenance. Add it to the content model so it is queryable, reportable, and impossible to skip.&lt;/p&gt;

&lt;p&gt;A minimal set of fields that covers the questions above:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_assisted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch&lt;/td&gt;
&lt;td&gt;Was a model involved at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_role&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;drafted&lt;/code&gt;, &lt;code&gt;edited&lt;/code&gt;, &lt;code&gt;translated&lt;/code&gt;, &lt;code&gt;researched&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Model and version, e.g. &lt;code&gt;claude-sonnet-5&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;human_reviewer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Object&lt;/td&gt;
&lt;td&gt;Relationship to your authors type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;review_date&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;When a person actually signed off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;facts_verified&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch&lt;/td&gt;
&lt;td&gt;Product facts and pricing checked against source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;ai_role&lt;/code&gt; distinction is the one that matters most, because it is exactly the distinction watermark detection cannot make. A post Claude drafted end to end and a post Claude proofread can carry the same mark. Your own record separates them.&lt;/p&gt;

&lt;p&gt;Writing that record with the Cosmic TypeScript SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @cosmicjs/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-bucket-slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-read-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;writeKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-write-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OBJECT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ai_assisted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ai_role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drafted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ai_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-sonnet-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;human_reviewer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jane-doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;review_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-08-11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;facts_verified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payoff is the query. Finding every AI-drafted post that no human has signed off on becomes one request instead of an audit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unreviewed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata.ai_assisted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata.facts_verified&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug,title,metadata.ai_model,metadata.review_date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that on a schedule and the answer to "how much unreviewed machine-generated content is live on our site" stops being a guess. Because the fields are typed and served over the REST API, you can surface the same record on the page itself if your disclosure policy calls for it, without maintaining a second system to track it.&lt;/p&gt;

&lt;p&gt;One note on the practical side: adding a field to a content model and backfilling it is a content-team task in a headless CMS, not an engineering ticket. Maximilian Wuhr, Co-Founder at FINN, described the value of that arrangement plainly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Cosmic is: us never having to ask a developer to change anything on the backend of our website."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Reporting on it
&lt;/h2&gt;

&lt;p&gt;Provenance fields tell you how a piece was made. The next question is whether it performs, and whether machine-drafted work holds up against human-drafted work on your own site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.cosmicjs.com/blog/cosmic-insights-web-analytics-that-knows-your-content" rel="noopener noreferrer"&gt;Cosmic Insights&lt;/a&gt; rolls traffic up by the actor who created the underlying content, splitting it across human users, agents, and automations. That turns a philosophical argument into a measurable one. If agent-drafted pages bounce harder or convert worse than human-drafted pages, you will see it in the numbers rather than debating it in a meeting. If they perform the same, that is worth knowing too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do this quarter
&lt;/h2&gt;

&lt;p&gt;Six things, none of which require a replatform.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add provenance fields to your content models.&lt;/strong&gt; Start with the six above. Ship them before you need them, because retrofitting provenance onto two years of archives is guesswork.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backfill the last 90 days only.&lt;/strong&gt; Recent content is what people are reading and what will get questioned. Older archives can be marked unknown, honestly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test what your media pipeline does to C2PA metadata.&lt;/strong&gt; Upload a Claude-generated image, run it through your normal transform and optimization path, and check whether the Content Credentials survive. Assume they do not until you have verified it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down your disclosure policy in one paragraph.&lt;/strong&gt; What level of model involvement triggers a public disclosure on the page? "Claude drafted this" and "Claude proofread this" are different, and your policy should say so before a reader asks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep detection out of your enforcement policy.&lt;/strong&gt; If you are checking freelancer or agency submissions, treat a watermark hit as a reason to open a conversation. &lt;a href="https://support.claude.com/en/articles/16266773-how-claude-marks-ai-generated-content" rel="noopener noreferrer"&gt;Anthropic's own documentation&lt;/a&gt; states that a detected mark is not fully conclusive, so it cannot carry a policy violation on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope the keys your agents hold.&lt;/strong&gt; If an agent can write to production, the provenance record it leaves is the only trail you have. Cosmic issues separate read and write keys, so a client connected with a read-only key gets the read tools while every write tool and all four AI generation tools are blocked with a clear error message. Details are on the &lt;a href="https://www.cosmicjs.com/mcp-server" rel="noopener noreferrer"&gt;MCP server page&lt;/a&gt; and in &lt;a href="https://www.cosmicjs.com/blog/connect-claude-to-your-cms-mcp-server" rel="noopener noreferrer"&gt;Connect Claude to Your CMS&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The through line
&lt;/h2&gt;

&lt;p&gt;Model-level watermarking is a good development, and it solves a problem at the wrong altitude for your purposes. It helps the broader internet identify synthetic content at scale. It does very little for the team that has to explain how a specific paragraph on a specific page came to exist.&lt;/p&gt;

&lt;p&gt;That record is yours to keep, and the only place it can reliably live is next to the content itself, as structured fields you can query. Teams that add those fields now will answer provenance questions in one API call. Teams that wait will reconstruct them from memory, which is another way of saying they will not answer them at all.&lt;/p&gt;

&lt;p&gt;If you are already running AI in your publishing workflow, this is a one-afternoon change with a long tail of value. You can model provenance fields on the Cosmic free plan and query them over the REST API today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=cross_post&amp;amp;utm_campaign=claude-ai-content-watermarking-provenance-cms" rel="noopener noreferrer"&gt;Start free&lt;/a&gt; or &lt;a href="https://calendly.com/tonyspiro/cosmic-intro" rel="noopener noreferrer"&gt;book 15 minutes with our CEO&lt;/a&gt; to talk through how your content model should record agent work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic, &lt;a href="https://support.claude.com/en/articles/16266773-how-claude-marks-ai-generated-content" rel="noopener noreferrer"&gt;How Claude marks AI-generated content&lt;/a&gt;, Claude Help Center&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://c2pa.org/" rel="noopener noreferrer"&gt;C2PA&lt;/a&gt;, Coalition for Content Provenance and Authenticity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.cosmicjs.com/blog/claude-ai-content-watermarking-provenance-cms?utm_source=devto&amp;amp;utm_medium=cross_post&amp;amp;utm_campaign=claude-ai-content-watermarking-provenance-cms" rel="noopener noreferrer"&gt;Cosmic blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cms</category>
      <category>webdev</category>
      <category>contentstrategy</category>
    </item>
    <item>
      <title>Connect Claude to Your CMS: A 5-Minute Guide to the Cosmic MCP Server</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Mon, 10 Aug 2026 22:03:30 +0000</pubDate>
      <link>https://dev.to/tonyspiro/connect-claude-to-your-cms-a-5-minute-guide-to-the-cosmic-mcp-server-4hbf</link>
      <guid>https://dev.to/tonyspiro/connect-claude-to-your-cms-a-5-minute-guide-to-the-cosmic-mcp-server-4hbf</guid>
      <description>&lt;p&gt;If you already run Claude every day, the next obvious question is whether it can touch your content. Not summarize it. Not draft copy in a chat window you then paste somewhere. Actually read your content model, query it, and write back to it.&lt;/p&gt;

&lt;p&gt;Cosmic answers that with an MCP server. This guide gets you connected in about five minutes, then covers the part most people skip: what Claude is allowed to do once it is connected.&lt;/p&gt;




&lt;h2&gt;
  
  
  What MCP actually gives you
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol is a standard way for an AI client to discover and call tools. Instead of you describing your CMS to Claude in prose, Claude asks the server what tools exist and calls them directly.&lt;/p&gt;

&lt;p&gt;The Cosmic MCP server exposes 18 bucket-scoped tools. They cover objects, object types, media, and AI generation. Bucket-scoped is the important word: the server operates against one Bucket, using the keys you give it, and it cannot reach across your other Buckets.&lt;/p&gt;




&lt;h2&gt;
  
  
  The five-minute setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Get your Bucket keys
&lt;/h3&gt;

&lt;p&gt;In your Cosmic dashboard, open your Bucket, then &lt;strong&gt;Settings &amp;gt; API Access&lt;/strong&gt;. Copy three things: your Bucket slug, your read key, and your write key. The keys are separate on purpose, and that separation is the single most useful control in this whole setup. More on that below.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add the server to your MCP client config
&lt;/h3&gt;

&lt;p&gt;The hosted endpoint is the fastest path and needs no install. Point your client at &lt;code&gt;https://mcp.cosmicjs.com/v1/buckets/{your-bucket-slug}&lt;/code&gt; and authenticate with your keys in the &lt;code&gt;Authorization&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;For Claude Desktop, edit your config file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS: &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Windows: &lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cosmic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.cosmicjs.com/v1/buckets/your-bucket-slug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer your-read-key:your-write-key"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor takes the same shape in &lt;code&gt;.cursor/mcp.json&lt;/code&gt; for a single project, or &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; globally.&lt;/p&gt;

&lt;p&gt;Read the bearer token carefully: read key, colon, write key. Drop the &lt;code&gt;:your-write-key&lt;/code&gt; suffix and the connection becomes read-only. Hold onto that detail, because it is the control worth understanding before you point any of this at production.&lt;/p&gt;

&lt;p&gt;If you would rather run the MCP process inside your own dev environment, the &lt;code&gt;@cosmicjs/mcp&lt;/code&gt; package ships a stdio binary that reads credentials from environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cosmic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"@cosmicjs/mcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"COSMIC_BUCKET_SLUG"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-bucket-slug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"COSMIC_READ_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-read-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"COSMIC_WRITE_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-write-key"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file and restart the client. The Cosmic tools show up in the tool list.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Verify the connection
&lt;/h3&gt;

&lt;p&gt;Ask Claude something read-only first:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"List the object types in this Bucket and tell me how many objects are in each."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you get back your real content model, you are connected. If you get nothing, the key or the Bucket slug is wrong, and the error message will say which.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Try a real task
&lt;/h3&gt;

&lt;p&gt;Once reads work, the useful prompts look like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find every published blog post missing an SEO description and list the slugs."&lt;/p&gt;

&lt;p&gt;"Create a draft post from this outline, set the author to Tony Spiro, and leave status as draft."&lt;/p&gt;

&lt;p&gt;"Add alt text to every image in the media library that does not have any."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last one is the kind of chore that never gets done manually and takes an agent about a minute.&lt;/p&gt;




&lt;h2&gt;
  
  
  The control that matters: read keys and write keys
&lt;/h2&gt;

&lt;p&gt;Here is the part worth understanding before you hand an agent your production Bucket.&lt;/p&gt;

&lt;p&gt;Cosmic issues separate read and write keys. Give an MCP client a read-only key and it gets the read tools only. Every write tool returns a clear blocked error. Nothing is guessed, nothing is inferred from a system prompt, and no amount of clever prompting talks its way past it.&lt;/p&gt;

&lt;p&gt;You can verify this yourself in under a minute:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set the &lt;code&gt;Authorization&lt;/code&gt; header to &lt;code&gt;Bearer your-read-key&lt;/code&gt; with no write key and no colon&lt;/li&gt;
&lt;li&gt;Ask Claude to create a new object&lt;/li&gt;
&lt;li&gt;Read the blocked error it returns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is a checkable guardrail, not a policy document. It is also the honest starting posture for anyone connecting an agent to real content: read-only first, then widen scope deliberately once you trust the workflow.&lt;/p&gt;

&lt;p&gt;When you are ready for writes, the safe pattern is a write-enabled key pointed at a staging Bucket, with the production Bucket still read-only. Cosmic plans include multiple Buckets from Builder up, so a dedicated staging Bucket for agent work is a reasonable setup rather than an exotic one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Writing content through the API instead
&lt;/h2&gt;

&lt;p&gt;If you would rather script the workflow than chat it, the TypeScript SDK covers the same ground:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @cosmicjs/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;writeKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_WRITE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Read: find drafts with no SEO description&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id,title,slug,metadata.seo_description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;seo_description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Write: patch one of them&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;seo_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A short, specific summary under 155 characters.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;writeKey&lt;/code&gt; is a separate argument from &lt;code&gt;readKey&lt;/code&gt;. Omit it and every write call fails, which is the same guardrail the MCP server relies on.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this is good for, and what it is not
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good fits today:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content audits: missing metadata, broken internal links, stale dates, orphaned objects&lt;/li&gt;
&lt;li&gt;Bulk mechanical edits: alt text, SEO descriptions, tag normalization, category cleanup&lt;/li&gt;
&lt;li&gt;Draft generation against your real content model, so the shape is correct on the first pass&lt;/li&gt;
&lt;li&gt;Answering questions about your own content library without opening the dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not a good fit yet:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unsupervised publishing to production. Keep a human on the publish step.&lt;/li&gt;
&lt;li&gt;Anything where a wrong write is expensive and hard to reverse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical rule: let the agent do the reading and the drafting, and keep the irreversible actions behind a person.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need a paid plan to use the MCP server?&lt;/strong&gt;&lt;br&gt;
You can start on the Free plan, which includes 1 Bucket, 2 team members, and 1,000 Objects. If you want a separate staging Bucket for agent writes, that starts at the Builder plan ($49/month, 2 Buckets, 3 team members, 5,000 Objects).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Cosmic offer a GraphQL endpoint for this?&lt;/strong&gt;&lt;br&gt;
No. Cosmic offers a REST API and the JavaScript/TypeScript SDK. The MCP server sits on top of the same REST API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I limit which object types an agent can touch?&lt;/strong&gt;&lt;br&gt;
The key-level control is read versus write. For tighter scoping, point the agent at a Bucket that only contains the content you want it to work on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work with clients other than Claude?&lt;/strong&gt;&lt;br&gt;
MCP is a client-agnostic standard, so any MCP-compatible client can connect to the same server with the same 18 tools. Cursor is covered above, and the hosted endpoint speaks the streamable-HTTP transport that other clients use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it on a real Bucket
&lt;/h2&gt;

&lt;p&gt;The fastest way to understand what an agent can do with a proper content model behind it is to connect one and ask it a question about your own content.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=connect-claude-to-your-cms-mcp-server" rel="noopener noreferrer"&gt;Create a free Cosmic account&lt;/a&gt;. No credit card required.&lt;/li&gt;
&lt;li&gt;Read the &lt;a href="https://www.cosmicjs.com/docs/mcp-server?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=connect-claude-to-your-cms-mcp-server" rel="noopener noreferrer"&gt;MCP server docs&lt;/a&gt; for the exact client configuration&lt;/li&gt;
&lt;li&gt;Want a walkthrough of the agent workflows other teams are running? &lt;a href="https://calendly.com/tonyspiro/cosmic-intro" rel="noopener noreferrer"&gt;Book 30 minutes with Tony&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cosmic is a YC W19 company building the content layer for teams that want their AI tools to work against real, structured content instead of copy pasted into a chat box.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>cms</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Medusa.js + Next.js: How to Add a Content Layer to Your Storefront</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Fri, 07 Aug 2026 19:28:03 +0000</pubDate>
      <link>https://dev.to/tonyspiro/medusajs-nextjs-how-to-add-a-content-layer-to-your-storefront-43be</link>
      <guid>https://dev.to/tonyspiro/medusajs-nextjs-how-to-add-a-content-layer-to-your-storefront-43be</guid>
      <description>&lt;p&gt;&lt;a href="https://medusajs.com/" rel="noopener noreferrer"&gt;Medusa&lt;/a&gt; is a strong commerce engine. It owns products, variants, pricing, inventory, carts, orders, and fulfillment, and it exposes all of it through a clean Store API. Once you have the Next.js Starter Storefront running, the commerce half of your site is basically solved.&lt;/p&gt;

&lt;p&gt;Then marketing asks for a buying guide on every category page, a founder story block on the product detail page, a seasonal landing page that goes live Friday at 9am, and an FAQ section that changes weekly. None of that belongs in your commerce database, and none of it should require a deploy.&lt;/p&gt;

&lt;p&gt;This post walks through the pattern we see working best: keep Medusa as the source of truth for commerce, add a headless CMS as the source of truth for editorial content, and join them in the Next.js layer on a shared key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with putting content in Medusa
&lt;/h2&gt;

&lt;p&gt;Medusa lets you attach arbitrary &lt;code&gt;metadata&lt;/code&gt; to products. It is tempting to stuff your marketing copy in there and call it done. That falls apart quickly for three reasons.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No editing experience.&lt;/strong&gt; &lt;code&gt;metadata&lt;/code&gt; is a key-value bag. Your content team gets a JSON blob, no rich text, no image handling, no preview, no revision history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No content modeling.&lt;/strong&gt; A buying guide has a title, hero image, intro, sections, related products, and an author. Flattening that into string keys means every consumer has to re-parse it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content that is not product-shaped has nowhere to live.&lt;/strong&gt; A holiday gift guide, a shipping policy page, a comparison table, a homepage hero: none of these map to a product record at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix is a second system with its own model and its own editor, joined at read time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Draw the ownership line first
&lt;/h2&gt;

&lt;p&gt;Before you write any code, write down who owns what. This one decision prevents most of the sync bugs teams hit later.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product handle, title, variants&lt;/td&gt;
&lt;td&gt;Medusa&lt;/td&gt;
&lt;td&gt;Commerce truth, drives cart and checkout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price, inventory, availability&lt;/td&gt;
&lt;td&gt;Medusa&lt;/td&gt;
&lt;td&gt;Must be real time, never cached in a CMS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cart, orders, customers, fulfillment&lt;/td&gt;
&lt;td&gt;Medusa&lt;/td&gt;
&lt;td&gt;Transactional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDP long-form story, lifestyle imagery&lt;/td&gt;
&lt;td&gt;CMS&lt;/td&gt;
&lt;td&gt;Editorial, changes without a deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buying guides, blog posts, landing pages&lt;/td&gt;
&lt;td&gt;CMS&lt;/td&gt;
&lt;td&gt;No product record exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FAQs, size guides, care instructions&lt;/td&gt;
&lt;td&gt;CMS&lt;/td&gt;
&lt;td&gt;Reusable across many products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigation, promo banners, homepage blocks&lt;/td&gt;
&lt;td&gt;CMS&lt;/td&gt;
&lt;td&gt;Merchandising, changes weekly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rule to enforce in code review: &lt;strong&gt;price and inventory never come from the CMS.&lt;/strong&gt; Duplicating those into a content system creates a window where your site shows a price that checkout will reject. Read them from Medusa on every request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Get the Medusa storefront running
&lt;/h2&gt;

&lt;p&gt;Start from the official Next.js Starter Storefront so you inherit the cart, checkout, and account flows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-medusa-app@latest my-store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That scaffolds a Medusa server and a Next.js storefront. Your storefront needs two environment values to talk to the backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env.local&lt;/span&gt;
&lt;span class="nv"&gt;NEXT_PUBLIC_MEDUSA_BACKEND_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:9000
&lt;span class="nv"&gt;NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pk_...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The publishable key is created in the Medusa Admin under Settings, and it scopes requests to a sales channel. Requests to the Store API without it will be rejected.&lt;/p&gt;

&lt;p&gt;Set up the Medusa JS SDK once and export a single client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/medusa.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Medusa&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@medusajs/js-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;medusa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Medusa&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_MEDUSA_BACKEND_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;publishableKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fetching a product by its handle looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;medusa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;classic-tee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*variants.calculated_price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Model the content side
&lt;/h2&gt;

&lt;p&gt;Now add the content layer. In Cosmic, create an Object Type called &lt;strong&gt;Product Content&lt;/strong&gt; with a metafield for every editorial element your PDP needs.&lt;/p&gt;

&lt;p&gt;A model that has held up well in production:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metafield key&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;handle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text (unique)&lt;/td&gt;
&lt;td&gt;The join key. Must match the Medusa product handle exactly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;story&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Markdown&lt;/td&gt;
&lt;td&gt;Long-form product narrative below the buy box&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lifestyle_images&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Files&lt;/td&gt;
&lt;td&gt;Editorial photography separate from catalog shots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;care_guide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Markdown&lt;/td&gt;
&lt;td&gt;Reusable care or sizing content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;faqs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Repeater&lt;/td&gt;
&lt;td&gt;Question and answer pairs rendered as an accordion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;related_reading&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Objects&lt;/td&gt;
&lt;td&gt;Links to blog posts or buying guides&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;handle&lt;/code&gt; field is the whole design. Make it unique so two content entries can never claim the same product, and tell your editors it has to match Medusa character for character. Everything else in the model is free to change without touching commerce.&lt;/p&gt;

&lt;p&gt;Install the Cosmic SDK and create the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @cosmicjs/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/cosmic.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a small helper that fetches content for a handle and returns &lt;code&gt;null&lt;/code&gt; instead of throwing when nothing exists yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/product-content.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./cosmic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getProductContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product-content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata.handle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title,slug,metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;null&lt;/code&gt; return matters. It is what lets you launch a product before marketing has written anything for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Join them in a server component
&lt;/h2&gt;

&lt;p&gt;With both clients in place, the product page fetches from each system in parallel and renders one page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/products/[handle]/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;medusa&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/medusa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getProductContent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/product-content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;notFound&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;medusa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*variants.calculated_price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nf"&gt;getProductContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Commerce: always from Medusa */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BuyBox&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Editorial: from the CMS, optional by design */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;story&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"product-story"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;story&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;faqs&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FaqAccordion&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;faqs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things worth calling out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Medusa decides whether the page exists.&lt;/strong&gt; If there is no product, you 404. A content entry with no matching product should never render a page, because there would be nothing to add to a cart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every content block is conditional.&lt;/strong&gt; The page has to render correctly with zero CMS content. Test that path deliberately by pointing a local build at a handle that has no entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fetch in parallel.&lt;/strong&gt; &lt;code&gt;Promise.all&lt;/code&gt; keeps the two round trips from stacking. On a PDP that difference is visible in your LCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Keep pages fresh without redeploying
&lt;/h2&gt;

&lt;p&gt;Static rendering is what makes this pattern fast. Cache invalidation is what makes it usable for a content team.&lt;/p&gt;

&lt;p&gt;Tag your content fetches, then invalidate the tag from a webhook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/revalidate/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;revalidateTag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-webhook-secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REVALIDATE_SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`product-content-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;revalidated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point a Cosmic webhook at that route for the Object Edited and Object Published events. An editor publishes a change, the webhook fires, that single product page rebuilds. No deploy, no full site rebuild, no Slack message to a developer.&lt;/p&gt;

&lt;p&gt;Do the same in reverse for Medusa: subscribe to product update events and revalidate the matching path when a title or description changes.&lt;/p&gt;

&lt;p&gt;One exception to keep in mind. Price and inventory should not rely on webhook timing. Render those parts dynamically or fetch them client side so a customer never sees a cached price that checkout will reject.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Everything that is not a product page
&lt;/h2&gt;

&lt;p&gt;The PDP join is the interesting technical part. The larger traffic win is usually the pages that have no product record at all.&lt;/p&gt;

&lt;p&gt;With a content layer already wired in, these become straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Buying guides and comparison pages.&lt;/strong&gt; These are the pages that rank for research-stage queries. Model them as a &lt;code&gt;guides&lt;/code&gt; type with a &lt;code&gt;related_products&lt;/code&gt; field holding a list of handles, then hydrate live prices from Medusa at render time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category landing pages.&lt;/strong&gt; Medusa gives you the collection and its products. The CMS gives you the hero, the intro copy, and the merchandising blocks above the grid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Campaign pages.&lt;/strong&gt; Marketing builds and schedules them without a developer. Cosmic supports scheduled publishing, so a Friday 9am launch is a field, not a deploy window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy and support pages.&lt;/strong&gt; Shipping, returns, sizing. Low glamour, high support-ticket deflection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same pattern each time: content from the CMS, live commerce data from Medusa, joined on the handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four mistakes to avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Copying prices into the CMS.&lt;/strong&gt; It will drift. When it drifts, customers see one number and get charged another. Always read price from Medusa.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using the product ID as the join key.&lt;/strong&gt; IDs change when you reseed a database or migrate environments. Handles are stable, human readable, and already unique in Medusa.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Making content required.&lt;/strong&gt; If a missing CMS entry breaks the page, you have coupled your product launches to your content calendar. Optional by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two sources of truth for the product title.&lt;/strong&gt; Pick one, and it should be Medusa, since that is what appears on the order. If marketing needs a different display headline, add a separate &lt;code&gt;display_headline&lt;/code&gt; field so the intent is explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cosmic fits this stack
&lt;/h2&gt;

&lt;p&gt;A few specifics that matter when you are pairing a CMS with Medusa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;REST API and a TypeScript SDK.&lt;/strong&gt; &lt;code&gt;@cosmicjs/sdk&lt;/code&gt; is typed and works in Next.js server components without adapters or codegen steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fully managed.&lt;/strong&gt; You are already running a Medusa server and a database. Adding a second self-hosted service to patch and scale is real operational cost. Cosmic is hosted, so the content layer is one API call, not another deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-assisted authoring.&lt;/strong&gt; Editors can draft product stories, FAQs, and guide content directly in the dashboard, which is where the volume problem usually lives for catalogs with thousands of SKUs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled publishing and webhooks.&lt;/strong&gt; Both are built in, which is what makes the revalidation flow above work end to end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As Maximilian Wuhr, Co-Founder at FINN, put it: "Cosmic is: us never having to ask a developer to change anything on the backend of our website."&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;The whole integration is about 60 lines of glue code: one Medusa client, one Cosmic client, one join key, one webhook route.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=medusajs-nextjs-headless-cms" rel="noopener noreferrer"&gt;Start free with Cosmic&lt;/a&gt; and model your first Product Content type in a few minutes. The &lt;a href="https://www.cosmicjs.com/docs?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=medusajs-nextjs-headless-cms" rel="noopener noreferrer"&gt;Cosmic docs&lt;/a&gt; cover the SDK and webhook setup, and the &lt;a href="https://docs.medusajs.com" rel="noopener noreferrer"&gt;Medusa docs&lt;/a&gt; cover the Store API side.&lt;/p&gt;

&lt;p&gt;If you are planning a larger migration or running a catalog with thousands of SKUs, &lt;a href="https://calendly.com/tonyspiro/cosmic-intro" rel="noopener noreferrer"&gt;book time with Tony&lt;/a&gt;, our CEO, and he will walk through the content model with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I use Cosmic to manage products instead of Medusa?&lt;/strong&gt;&lt;br&gt;
You can model product content in Cosmic, but you should not replace Medusa's product records. Carts, pricing rules, inventory, and orders depend on them. Use Cosmic for the editorial layer around the catalog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work with the Medusa Next.js Starter Storefront?&lt;/strong&gt;&lt;br&gt;
Yes. The pattern above drops into the starter's existing product route. You are adding a second data fetch alongside the Medusa one, and leaving the cart and checkout flows untouched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I handle products with no content entry yet?&lt;/strong&gt;&lt;br&gt;
Return &lt;code&gt;null&lt;/code&gt; from your content helper on a 404 and render every content block conditionally, as shown in Step 2 and Step 3. The product page stays fully functional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about multi-region or multi-language storefronts?&lt;/strong&gt;&lt;br&gt;
Medusa handles regional pricing and currency. Cosmic supports locales on objects, so you can request content in the visitor's locale using the same handle-based lookup.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.cosmicjs.com/blog/medusajs-nextjs-headless-cms" rel="noopener noreferrer"&gt;the Cosmic blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ecommerce</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Sanity vs Strapi: Which Headless CMS Should You Choose in 2026?</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Thu, 06 Aug 2026 22:03:25 +0000</pubDate>
      <link>https://dev.to/tonyspiro/sanity-vs-strapi-which-headless-cms-should-you-choose-in-2026-4h9g</link>
      <guid>https://dev.to/tonyspiro/sanity-vs-strapi-which-headless-cms-should-you-choose-in-2026-4h9g</guid>
      <description>&lt;p&gt;Sanity and Strapi both call themselves headless, both let you define schemas in code, and both have large developer followings. They answer a different question underneath.&lt;/p&gt;

&lt;p&gt;Sanity gives you a hosted content database, the Content Lake, with an open source editing environment on top. Strapi gives you a Node.js application under the MIT license that you run yourself, or pay Strapi Cloud to run for you.&lt;/p&gt;

&lt;p&gt;That one architectural fork decides your query language, your upgrade path, your compliance story, your AI options, and your bill. This comparison walks through each of those with numbers verified against both vendors' live pricing pages on August 5, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Sanity&lt;/th&gt;
&lt;th&gt;Strapi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Hosted content platform (Content Lake)&lt;/td&gt;
&lt;td&gt;Open source Node.js app, MIT licensed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who operates it&lt;/td&gt;
&lt;td&gt;Sanity&lt;/td&gt;
&lt;td&gt;You, or Strapi Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query API&lt;/td&gt;
&lt;td&gt;GROQ and GraphQL over HTTP, JS client&lt;/td&gt;
&lt;td&gt;REST and GraphQL from your own instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Managed, no direct access&lt;/td&gt;
&lt;td&gt;Postgres, MySQL, or MariaDB that you own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editing UI&lt;/td&gt;
&lt;td&gt;Sanity Studio, an open source React app you deploy&lt;/td&gt;
&lt;td&gt;Bundled admin panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry price&lt;/td&gt;
&lt;td&gt;Free plan, up to 20 seats, 10k documents&lt;/td&gt;
&lt;td&gt;Community edition, free forever, unlimited entries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seat cost&lt;/td&gt;
&lt;td&gt;$15/seat/month on Growth&lt;/td&gt;
&lt;td&gt;$15/seat/month on Growth, 3 seats included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting cost&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;Yours to pay, or $35 to $450 per project per month on Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Governance extras&lt;/td&gt;
&lt;td&gt;Comments, tasks, scheduled publishing on Growth; audit trail and SSO on Enterprise&lt;/td&gt;
&lt;td&gt;Content History and Releases on Growth; Review Workflows and Audit Logs on Enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Teams that want structured content as a service with many editors&lt;/td&gt;
&lt;td&gt;Teams that need to own the runtime and the data layer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Architecture: hosted content lake vs. an app you run
&lt;/h2&gt;

&lt;p&gt;Sanity stores every document in the Content Lake, a hosted, schemaless JSON store with a real-time layer. You never provision a database or patch a server. You do give up direct database access, which matters if your security review requires the data to sit inside your own VPC.&lt;/p&gt;

&lt;p&gt;Strapi is a Node.js application. You choose Postgres, MySQL, or MariaDB, you deploy it, and you own the runtime. That means full control over network boundaries, extensions, and custom middleware. It also means you own Node version upgrades, dependency patches, major-version migrations, backups, and on-call.&lt;/p&gt;

&lt;p&gt;Strapi Cloud removes most of that operational load, and the pricing page shows the trade in plain terms. Starter projects at $35 per project per month sleep when idle. Always-on runtime begins with Pro at $90 per project per month. A 99.9% uptime SLA arrives at Business, $450 per project per month.&lt;/p&gt;

&lt;p&gt;A useful way to decide: if your team can name the person who will handle a CVE in a transitive dependency on a Friday afternoon, self-hosted Strapi is a reasonable choice. If nobody's name comes to mind, a managed platform is cheaper than it looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content modeling
&lt;/h2&gt;

&lt;p&gt;Both tools model content in code, which keeps schema changes in version control and in code review.&lt;/p&gt;

&lt;p&gt;Sanity schemas are JavaScript objects registered in your Studio. Portable Text handles rich text as structured data instead of an HTML blob, which pays off when the same content renders to web, native mobile, and voice. References are first class and resolve inside queries.&lt;/p&gt;

&lt;p&gt;Strapi models are defined through the Content-Type Builder in the admin panel, which writes JSON schema files into your repository. Dynamic Zones let editors assemble a page from a library of components, which is the closest thing either product has to visual page building out of the box. Components, relations, and custom fields cover most modeling needs, and the Blocks editor handles structured rich text.&lt;/p&gt;

&lt;p&gt;Strapi's advantage here is that the modeling UI ships with the product. Sanity's advantage is that Portable Text and the reference system are more rigorous once your content graph gets complicated.&lt;/p&gt;

&lt;h2&gt;
  
  
  APIs and developer experience
&lt;/h2&gt;

&lt;p&gt;Sanity queries use GROQ, a projection language built for the Content Lake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sanity/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-01-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;useCdn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`*[_type == "post" &amp;amp;&amp;amp; defined(slug.current)]{ title, "slug": slug.current, publishedAt }`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GROQ is genuinely powerful. It also has a learning curve, and every new engineer on your team pays it once.&lt;/p&gt;

&lt;p&gt;Strapi exposes REST endpoints from your own instance, with GraphQL available as a plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-app.example.com/api/articles?populate=cover&amp;amp;sort=publishedAt:desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRAPI_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strapi's API is conventional and quick to pick up. The cost shows up in performance work: response times depend on the instance you are running, the database you provisioned, and any caching you added yourself. Sanity's CDN handles that layer for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI capabilities in 2026
&lt;/h2&gt;

&lt;p&gt;This is where the two products have moved fastest, and where the fine print matters.&lt;/p&gt;

&lt;p&gt;Sanity ships AI credits with every plan, 1,000 per month on Free and Growth, with overage billed at $0.05 per credit. Its AI features cover generation and transformation inside the editing workflow, plus embeddings and semantic search over the Content Lake.&lt;/p&gt;

&lt;p&gt;Strapi introduced Strapi AI on the self-hosted Growth plan, also 1,000 credits per month, with additional credits at $1.50 per 100. Worth reading carefully before you commit: Strapi's own CMS pricing table lists Strapi AI as "Not yet available" in the Enterprise column at the time of writing. If AI in the editor is a requirement and you are heading toward an Enterprise contract, confirm availability with their sales team rather than assuming feature parity with Growth.&lt;/p&gt;

&lt;p&gt;Both vendors now ship an MCP server, which lets AI coding agents read and write content through a standard protocol. Strapi announced general availability of theirs recently. We wrote about how the two approaches differ in &lt;a href="https://www.cosmicjs.com/blog/cosmic-mcp-vs-strapi-mcp" rel="noopener noreferrer"&gt;Cosmic MCP Server vs Strapi MCP&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing at realistic team sizes
&lt;/h2&gt;

&lt;p&gt;Both vendors publish clear pricing, which is refreshing. The lists are not directly comparable, because Sanity's price includes hosting and Strapi's does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sanity, verified August 5, 2026&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free: $0, up to 20 seats, 2 public datasets, 10,000 documents, 250,000 API requests per month, 100 GB assets&lt;/li&gt;
&lt;li&gt;Growth: $15 per seat per month, up to 50 seats, private datasets, 25,000 documents, comments, tasks, scheduled publishing&lt;/li&gt;
&lt;li&gt;Enterprise: custom, adds SSO with SAML, custom roles, audit trail, uptime SLA&lt;/li&gt;
&lt;li&gt;Add-ons and overages: increased quota at $299 per month, dedicated support at $799 per month, extra datasets at $999 per dataset per month, additional API requests at $1 per 25,000, assets at $0.50 per GB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Strapi, verified August 5, 2026&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Community: free forever, MIT license, unlimited entries and API calls, community support&lt;/li&gt;
&lt;li&gt;Growth: $45 per month including 3 seats, additional seats $15 each, adds Strapi AI, Live Preview, Releases, and 30 days of Content History&lt;/li&gt;
&lt;li&gt;SSO: a $150 per month add-on, plus $50 per month per seat&lt;/li&gt;
&lt;li&gt;Enterprise: custom, adds Review Workflows, Audit Logs, and a SOC 2 report&lt;/li&gt;
&lt;li&gt;Strapi Cloud hosting, priced per project: Starter $35, Pro $90, Business $450 per month, with additional environments at $60 or $300 per month depending on tier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A five-editor team, one production project&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;th&gt;What is included&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sanity Growth&lt;/td&gt;
&lt;td&gt;$75 (5 x $15)&lt;/td&gt;
&lt;td&gt;Hosting, CDN, private dataset, 25k documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strapi Growth, self-hosted&lt;/td&gt;
&lt;td&gt;$75 license (3 seats + 2 x $15)&lt;/td&gt;
&lt;td&gt;License and features only. Your infrastructure, database, backups, and upgrade labor sit on top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strapi Growth on Cloud Pro&lt;/td&gt;
&lt;td&gt;$75 license + $90 hosting&lt;/td&gt;
&lt;td&gt;Always-on runtime, weekly backups, multi-environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cosmic Team&lt;/td&gt;
&lt;td&gt;$299&lt;/td&gt;
&lt;td&gt;3 Buckets, 5 team members, 20,000 objects, fully managed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At five seats, Sanity Growth carries the lowest sticker price of the group, and we are not going to pretend otherwise. The number to watch on Sanity is per-seat scaling: twenty editors on Growth is $300 per month, and quota add-ons are priced in the hundreds. The number to watch on Strapi is the engineering time that never appears on the invoice, plus per-project hosting if you run more than one property.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance and compliance
&lt;/h2&gt;

&lt;p&gt;Sanity puts collaboration features on Growth: comments, tasks, scheduled publishing, and 90 days of change review. SSO, custom roles, and audit trail are Enterprise.&lt;/p&gt;

&lt;p&gt;Strapi gives Community users Role-Based Access Control for free, which is unusually generous. Content History with 30 days of retention and Releases arrive on Growth. Review Workflows and Audit Logs are Enterprise, and SSO is a paid add-on with its own per-seat charge.&lt;/p&gt;

&lt;p&gt;If approval chains and audit trails are non-negotiable for you, price the Enterprise tier on both sides early. That is the point where headless CMS quotes stop being self-serve on either platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to choose Sanity
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You want structured content as a service and no infrastructure to run&lt;/li&gt;
&lt;li&gt;You have many editors and want a low per-seat entry point&lt;/li&gt;
&lt;li&gt;Your content graph is complex and Portable Text plus references earn their keep&lt;/li&gt;
&lt;li&gt;Real-time collaborative editing matters to your workflow&lt;/li&gt;
&lt;li&gt;Your team is willing to learn GROQ&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to choose Strapi
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Data residency, VPC isolation, or air-gapped deployment is a hard requirement&lt;/li&gt;
&lt;li&gt;You want an MIT-licensed codebase you can fork and extend without vendor permission&lt;/li&gt;
&lt;li&gt;Your plugin and customization needs go beyond configuration&lt;/li&gt;
&lt;li&gt;You have real DevOps capacity, or you are happy to pay Strapi Cloud for it&lt;/li&gt;
&lt;li&gt;Unlimited entries and API calls at zero license cost fits your scale&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Cosmic fits
&lt;/h2&gt;

&lt;p&gt;Cosmic sits in the managed camp with Sanity, with a deliberately smaller surface area. You get a REST API and a TypeScript SDK, AI built into the editing workflow, and a dashboard that non-technical teammates can use on day one without a Studio deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-bucket-slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-read-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title,slug,metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No local server, no database to provision, no GROQ to learn. Vuetify, the Vue component library with more than four million monthly npm downloads, &lt;a href="https://www.cosmicjs.com/blog/vuetify-headless-cms-case-study" rel="noopener noreferrer"&gt;cut server response times from 300-400 ms to about 50 ms&lt;/a&gt; after moving to Cosmic, with a team small enough that nobody has time to babysit a CMS.&lt;/p&gt;

&lt;p&gt;The editorial side is the other half of it. As Maximilian Wuhr, Co-Founder at FINN, put it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cosmic is: us never having to ask a developer to change anything on the backend of our website.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cosmic is a Y Combinator W19 company, and pricing is public: Free at $0 with 1 Bucket, 2 team members, and 1,000 objects; Builder at $49 per month; Team at $299 per month; Business at $499 per month; Enterprise custom. Additional users are $29 per user per month. Full details are on the &lt;a href="https://www.cosmicjs.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Strapi actually free?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Community edition is free under the MIT license, with unlimited entries and API calls. Hosting, database, backups, monitoring, and upgrades are yours to fund. Growth features like Live Preview, Releases, Content History, and Strapi AI start at $45 per month with 3 seats included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Sanity charge per seat?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Growth is $15 per seat per month for up to 50 seats. The Free plan allows up to 20 seats with 2 public datasets and 10,000 documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Cosmic charge per seat?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Each plan includes a set number of team members, 2 on Free, 3 on Builder, 5 on Team, 10 on Business, and additional users are $29 per user per month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Cosmic support GraphQL?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Cosmic offers a REST API and a JavaScript/TypeScript SDK. Sanity and Strapi both offer GraphQL, so if GraphQL is a hard requirement, that is a real point in their favor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I migrate off Strapi without rebuilding my front end?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually yes, if your front end talks to a content API through a thin data layer. We documented the process in &lt;a href="https://www.cosmicjs.com/blog/migrate-from-strapi-to-cosmic" rel="noopener noreferrer"&gt;Migrate from Strapi to Cosmic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I am comparing against Contentful too?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have those head to head as well: &lt;a href="https://www.cosmicjs.com/blog/sanity-vs-contentful" rel="noopener noreferrer"&gt;Sanity vs Contentful&lt;/a&gt;, &lt;a href="https://www.cosmicjs.com/blog/strapi-vs-contentful" rel="noopener noreferrer"&gt;Strapi vs Contentful&lt;/a&gt;, and the full field in our &lt;a href="https://www.cosmicjs.com/blog/headless-cms-comparison-2026-cosmic-contentful-strapi-sanity-prismic-hygraph" rel="noopener noreferrer"&gt;2026 headless CMS comparison&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest summary
&lt;/h2&gt;

&lt;p&gt;Pick Strapi if owning the runtime is worth the operational bill, and be realistic about who on your team pays that bill. Pick Sanity if you want a hosted content platform with a strong modeling story and a cheap seat, and budget for per-seat growth and quota add-ons. Pick Cosmic if you want managed infrastructure, a REST API with a TypeScript SDK, and AI in the editing workflow without a Studio to deploy or a Node app to patch.&lt;/p&gt;

&lt;p&gt;You can test the third option in about ten minutes. &lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=sanity-vs-strapi" rel="noopener noreferrer"&gt;Start building on Cosmic for free&lt;/a&gt;, no credit card required. If you are mid-evaluation and want a second opinion on your specific stack, &lt;a href="https://calendly.com/tonyspiro/cosmic-intro?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=sanity-vs-strapi" rel="noopener noreferrer"&gt;book 20 minutes with our CEO&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pricing and feature details for Sanity and Strapi were verified against their public pricing pages on August 5, 2026. Vendor pricing changes often, so confirm current terms before signing anything.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cms</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Which AI Model Should Generate Your CMS Content? A Practical Framework</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Wed, 05 Aug 2026 22:02:08 +0000</pubDate>
      <link>https://dev.to/tonyspiro/which-ai-model-should-generate-your-cms-content-a-practical-framework-1do7</link>
      <guid>https://dev.to/tonyspiro/which-ai-model-should-generate-your-cms-content-a-practical-framework-1do7</guid>
      <description>&lt;p&gt;If you landed here from a model comparison post, you already know the benchmark numbers. Reasoning scores, context windows, tokens per second, price per million. Useful data, and none of it answers the question you actually have: which model should I point at my content pipeline, and how do I wire it up so the output lands in production instead of in a Slack thread?&lt;/p&gt;

&lt;p&gt;This post answers that. It is written for the person who has to ship the content.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three-axis framework
&lt;/h2&gt;

&lt;p&gt;Model choice for content work comes down to three axes. Score your use case on each one and the answer usually falls out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axis 1: Revision tolerance
&lt;/h3&gt;

&lt;p&gt;How much editing are you willing to do per piece?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low tolerance&lt;/strong&gt; (publish with light proofing): you need the strongest available reasoning model. The cost delta per article is a rounding error next to an editor's hourly rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High tolerance&lt;/strong&gt; (draft is a starting point, a human rewrites 40% of it): a faster, cheaper model wins. You are paying for a scaffold.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams misjudge this axis. They buy the premium model and then rewrite everything anyway, or they buy the cheap model for content that goes out unedited.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axis 2: Volume shape
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Few pieces, high stakes&lt;/strong&gt; (pillar pages, comparison pages, launch posts): premium model, long context, one call per piece.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Many pieces, low stakes&lt;/strong&gt; (product descriptions, alt text, meta descriptions, category blurbs): cheap and fast model, batched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A 2,000-product catalog needing meta descriptions is a completely different job from a 3,000-word technical guide, and it should not use the same model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axis 3: Structure requirements
&lt;/h3&gt;

&lt;p&gt;This is the axis nobody mentions in benchmark roundups, and it is the one that breaks pipelines.&lt;/p&gt;

&lt;p&gt;CMS content is a typed object: a title with a character limit, a slug, an SEO description under 155 characters, a body in markdown, a set of tags drawn from a fixed vocabulary, a relationship to an author object. If the model returns loose prose where your schema wants a 155-character string, your pipeline fails at the write step.&lt;/p&gt;

&lt;p&gt;So the real question on this axis is: how reliably does the model produce output that conforms to a schema? Test that specifically. Give it your actual object type definition and ask for 20 objects. Count the ones that validate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Score your use case
&lt;/h2&gt;

&lt;p&gt;The three axes give you a tier, not a single model name. Cosmic's AI API groups models into three token-cost tiers, and the tier is the decision that matters for budgeting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Budget, 1.0x multiplier&lt;/strong&gt;: GPT-5 Nano, GPT-5 Mini, Claude Haiku 4.5&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard, 2.0x multiplier&lt;/strong&gt;: GPT-5, GPT-5.2, GPT-5.2 Codex, GPT-5.5, Claude Sonnet 4.6, Claude Sonnet 5, Claude Opus 4.7, Claude Opus 4.8, Gemini 3.1 Pro, Kimi K3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Premium, 4.0x multiplier&lt;/strong&gt;: Claude Fable 5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Map the six common content jobs onto those tiers and you get a starting configuration:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Revision tolerance&lt;/th&gt;
&lt;th&gt;Volume shape&lt;/th&gt;
&lt;th&gt;Structure need&lt;/th&gt;
&lt;th&gt;Tier to start with&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pillar / comparison pages&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Few, high stakes&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Premium (4.0x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical tutorials with code&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Few, high stakes&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Standard (2.0x), reasoning model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product descriptions&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Many, low stakes&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Budget (1.0x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta descriptions, alt text&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Many, low stakes&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;Budget (1.0x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Localization of existing copy&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Many&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Standard (2.0x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editorial first drafts&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Standard (2.0x)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the multipliers as relative cost, not absolute price. A Premium generation consumes four times the tokens of the same call on Budget, which is why running a 2,000-item catalog on the top tier gets expensive fast while a single pillar page on it costs almost nothing.&lt;/p&gt;

&lt;p&gt;If you want the current head-to-head on the premium tier specifically, we broke it down in &lt;a href="https://www.cosmicjs.com/blog/claude-sonnet-5-vs-opus-5" rel="noopener noreferrer"&gt;Claude Sonnet 5 vs Opus 5&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part that actually determines whether this works
&lt;/h2&gt;

&lt;p&gt;Model selection is maybe 20% of the outcome. The other 80% is the plumbing: where the generated content lands, whether it is reviewable before it goes live, and whether a human can fix it without a deploy.&lt;/p&gt;

&lt;p&gt;Three requirements, in order of how often they get skipped:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Generated content must land as a draft.&lt;/strong&gt; Any pipeline that writes straight to production is one bad generation away from an incident. Your CMS needs a draft state that the API can write to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The schema must be enforced server-side.&lt;/strong&gt; If your content model lets a 400-character string into a field meant for 155, the model's mistake becomes your search snippet. Validation belongs in the content model itself, where every write has to pass through it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Editors need to fix output without touching code.&lt;/strong&gt; This is the whole argument for a headless CMS in an AI pipeline. The model drafts, the API writes, and a human corrects it in a UI. Nobody should have to open a pull request to fix a typo.&lt;/p&gt;

&lt;p&gt;For the full build, including grounding generations in a source document and streaming long outputs, see &lt;a href="https://www.cosmicjs.com/blog/ai-content-pipeline-claude-headless-cms" rel="noopener noreferrer"&gt;How to Build an AI Content Pipeline with Claude and a Headless CMS&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this looks like in Cosmic
&lt;/h2&gt;

&lt;p&gt;Generation and storage run through the same client, so the tier decision above is a parameter rather than an integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;writeKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_WRITE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Write an SEO description under 155 characters for: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-opus-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;model&lt;/code&gt; parameter is the entire switch. Moving product descriptions from Standard down to Budget, or a pillar page up to Premium, is an edit to one string. There is no second provider account, no separate invoice, and no per-vendor SDK to keep current. We argued that case at length in &lt;a href="https://www.cosmicjs.com/blog/why-your-ai-stack-should-be-model-agnostic" rel="noopener noreferrer"&gt;Why Your AI Stack Should Be Model-Agnostic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Omit &lt;code&gt;model&lt;/code&gt; and the API uses &lt;code&gt;claude-opus-5&lt;/code&gt;. Other IDs you can pass include &lt;code&gt;gemini-3.1-pro-preview&lt;/code&gt;, &lt;code&gt;gpt-5.2-codex&lt;/code&gt;, and &lt;code&gt;kimi-k3&lt;/code&gt;. The full list lives in the &lt;a href="https://www.cosmicjs.com/docs/api/ai" rel="noopener noreferrer"&gt;AI API docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note the second value in the response. &lt;code&gt;usage&lt;/code&gt; comes back on every call, so you can log real token counts per piece instead of estimating them from a price-per-million table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write
&lt;/h3&gt;

&lt;p&gt;Generated content goes in as a draft, against a typed object with server-side validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// `generated` is whatever your model returned, already parsed&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// always review before publishing&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;seo_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seoDescription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// maxlength enforced on the metafield&lt;/span&gt;
    &lt;span class="na"&gt;markdown_content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authorObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to notice. &lt;code&gt;status: 'draft'&lt;/code&gt; means an editor reviews every generation before it is public. And &lt;code&gt;seo_description&lt;/code&gt; has a &lt;code&gt;maxlength&lt;/code&gt; set on the metafield itself, so an over-long value is rejected at the API boundary instead of quietly shipping.&lt;/p&gt;

&lt;p&gt;Reading it back for your frontend is the same SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title,slug,metadata.seo_description,metadata.markdown_content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-created_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cosmic exposes all of this over a REST API and the &lt;a href="https://www.npmjs.com/package/@cosmicjs/sdk" rel="noopener noreferrer"&gt;TypeScript SDK&lt;/a&gt;, so the pipeline is the same whether your model runs in a cron job, a serverless function, or an agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  A cheap test before you commit
&lt;/h2&gt;

&lt;p&gt;Before you standardize on a tier, run this. It takes an afternoon and it beats every benchmark chart.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take 10 real pieces of content you have already published.&lt;/li&gt;
&lt;li&gt;Write the prompt you would actually use in production, including your object schema.&lt;/li&gt;
&lt;li&gt;Generate 10 replacements with each candidate model, changing only the &lt;code&gt;model&lt;/code&gt; string between runs.&lt;/li&gt;
&lt;li&gt;Count two numbers: how many validate against your schema on the first try, and how many minutes of editing each one needs to reach publishable.&lt;/li&gt;
&lt;li&gt;Add up the real cost. Log &lt;code&gt;usage&lt;/code&gt; from every call, then apply the tier multiplier: Budget 1.0x, Standard 2.0x, Premium 4.0x. That gives you measured token consumption per piece rather than an estimate. Multiply editing minutes by your loaded editorial rate and add the two figures together.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pick the option with the cheapest total. That number predicts your real cost far better than a leaderboard position does.&lt;/p&gt;

&lt;p&gt;The free plan includes 300,000 input and 300,000 output tokens per month, which is enough to run this entire comparison before you pay for anything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start building the pipeline
&lt;/h2&gt;

&lt;p&gt;You can have both halves of this running against a real bucket today. Cosmic's free plan includes 1 Bucket, 2 team members, 1,000 objects, and the AI token allocation above, with no credit card required. Paid plans start at $49/month for Builder, and additional team members are $29/user/month. Full details on the &lt;a href="https://www.cosmicjs.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=choosing-ai-model-for-cms-content" rel="noopener noreferrer"&gt;&lt;strong&gt;Sign up for Cosmic free&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running a high-volume AI content pipeline and want to talk architecture? &lt;a href="https://calendly.com/tonyspiro/cosmic-intro?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=choosing-ai-model-for-cms-content" rel="noopener noreferrer"&gt;&lt;strong&gt;Book 20 minutes with Tony&lt;/strong&gt;&lt;/a&gt;, our CEO. Cosmic is a YC W19 company and we have been doing API-first content since before it had a name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cosmicjs.com/blog/ai-content-pipeline-claude-headless-cms" rel="noopener noreferrer"&gt;How to Build an AI Content Pipeline with Claude and a Headless CMS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cosmicjs.com/blog/why-your-ai-stack-should-be-model-agnostic" rel="noopener noreferrer"&gt;Why Your AI Stack Should Be Model-Agnostic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cosmicjs.com/blog/claude-sonnet-5-vs-opus-5" rel="noopener noreferrer"&gt;Claude Sonnet 5 vs Opus 5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cosmicjs.com/headless-cms" rel="noopener noreferrer"&gt;What is a headless CMS?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>cms</category>
    </item>
    <item>
      <title>How to Build an AI Content Pipeline with Claude and a Headless CMS</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Sat, 01 Aug 2026 01:33:05 +0000</pubDate>
      <link>https://dev.to/tonyspiro/how-to-build-an-ai-content-pipeline-with-claude-and-a-headless-cms-3660</link>
      <guid>https://dev.to/tonyspiro/how-to-build-an-ai-content-pipeline-with-claude-and-a-headless-cms-3660</guid>
      <description>&lt;p&gt;Most "AI content pipeline" tutorials hand you a second vendor. You install an AI provider's SDK, manage a second API key, absorb a second bill, and then write glue code to move the output into wherever your content actually lives.&lt;/p&gt;

&lt;p&gt;Cosmic generates text, images, and video through the same API that stores your content. One SDK, one key, one bill. This guide builds a working pipeline on that API: a source document goes in, a reviewed draft comes out, and a human approves it before anything reaches production.&lt;/p&gt;

&lt;p&gt;Everything below uses the official TypeScript SDK, &lt;code&gt;@cosmicjs/sdk&lt;/code&gt;. No second AI provider required.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are building
&lt;/h2&gt;

&lt;p&gt;A content pipeline has four stages, and the third one is the reason the other three are worth automating.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Source.&lt;/strong&gt; The raw material: a brief, a transcript, a quarterly report, a spreadsheet of survey results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate.&lt;/strong&gt; Turn the source into a structured draft.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review.&lt;/strong&gt; A human reads it, edits it, and decides whether it ships.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish.&lt;/strong&gt; The approved draft goes live through your existing front end.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skip stage three and you are running a slop factory. The pipeline below treats the review gate as load-bearing infrastructure, not an afterthought.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Cosmic account. The &lt;a href="https://www.cosmicjs.com/pricing" rel="noopener noreferrer"&gt;Free plan&lt;/a&gt; includes 1 Bucket, 2 team members, 1,000 Objects, and a monthly AI token allocation, which is enough to build and test this end to end.&lt;/li&gt;
&lt;li&gt;Your Bucket slug, read key, and write key, from &lt;strong&gt;Settings → API Access&lt;/strong&gt; in your Bucket.&lt;/li&gt;
&lt;li&gt;Node.js and a package manager.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A write key is required for every AI operation. Keep it server side only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install the SDK and create a client
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @cosmicjs/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;writeKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_WRITE_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire dependency list. The same client reads content, writes content, and generates it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Generate your first draft
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cosmic.ai.generateText()&lt;/code&gt; takes a prompt and returns the generated text plus token usage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Write a 400-word product description for a ceramic pour-over coffee dripper. Audience: home brewers. Tone: plain and specific. No superlatives.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// { input_tokens, output_tokens }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;usage&lt;/code&gt; object matters more than it looks. It is how you attach a real cost figure to every piece of content the pipeline produces, which we come back to below.&lt;/p&gt;

&lt;p&gt;If you prefer HTTP directly, the same call over REST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://workers.cosmicjs.com/v3/buckets/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUCKET_SLUG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/ai/text &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"prompt":"Write a product description for a coffee mug","max_tokens":500}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUCKET_WRITE_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Feed it a real source document
&lt;/h2&gt;

&lt;p&gt;This is the step that turns a prompt toy into a pipeline. The &lt;code&gt;media_url&lt;/code&gt; parameter points at any file in your Bucket and the model analyzes it as part of the request. Images, PDFs, Excel spreadsheets, Word documents.&lt;/p&gt;

&lt;p&gt;Upload a quarterly report to your Bucket, then generate from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Summarize the key points from this document as a bulleted list. Include only figures stated in the source. Do not extrapolate.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;media_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.cosmicjs.com/quarterly-report.pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The grounding this gives you is the whole point. A model working from a prompt alone invents plausible numbers. A model working from your PDF is constrained by a document you control, and the instruction to use only stated figures becomes checkable: a reviewer can open the same PDF and verify every claim.&lt;/p&gt;

&lt;p&gt;The same parameter works with the chat format when you need multi-turn refinement over one source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What trends do you see in this sales data?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Looking at the spreadsheet, I can see several patterns...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What was the highest performing month?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;media_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.cosmicjs.com/sales-data.xlsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful source documents for a content team: customer interview transcripts, support ticket exports, release notes, analytics CSVs, competitor pricing pages saved as PDFs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Write the result into the CMS as a draft
&lt;/h2&gt;

&lt;p&gt;Generation and storage are the same client, so this is one call and no glue code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Q3 Performance Recap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// never 'published' from an automated step&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;markdown_content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;teaser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Generated from the Q3 report. Pending editorial review.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;status: 'draft'&lt;/code&gt; line is the pipeline's safety property. Every generated object lands in a state that is invisible to your production front end until a person changes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Make the review gate real
&lt;/h2&gt;

&lt;p&gt;A review gate that depends on someone remembering to check a folder will fail. Two things make it hold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query the queue.&lt;/strong&gt; Anything awaiting review is one request away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire that to a Slack message on a schedule and the queue comes to your team instead of waiting to be discovered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Publishing stays a human action.&lt;/strong&gt; The approval step is a deliberate status change, made by a person:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;objectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep that call out of your generation script entirely. Put it behind an editor's click in the dashboard, or behind an internal tool that requires a named approver.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Pick a model, and keep the ability to change it
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;model&lt;/code&gt; parameter defaults to &lt;code&gt;claude-opus-5&lt;/code&gt;. It also accepts Gemini models such as &lt;code&gt;gemini-3.1-pro-preview&lt;/code&gt;, OpenAI models such as &lt;code&gt;gpt-5.2-codex&lt;/code&gt;, and Moonshot Kimi models such as &lt;code&gt;kimi-k3&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-3.1-pro-preview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switching providers is a one-line change with no new dependency, no new key, and no new billing relationship. That matters because the model you pick today will be superseded. A pipeline that pins itself to a single vendor's SDK has to be rebuilt every time the frontier moves. See &lt;a href="https://www.cosmicjs.com/blog/why-your-ai-stack-should-be-model-agnostic" rel="noopener noreferrer"&gt;why your AI stack should be model-agnostic&lt;/a&gt; for the longer argument.&lt;/p&gt;

&lt;p&gt;A practical split for a content pipeline: a cheaper Budget-tier model for mechanical work like extracting bullet points or drafting meta descriptions, and a Standard-tier model for prose that a reader will actually see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Generate the featured image in the same pipeline
&lt;/h2&gt;

&lt;p&gt;The AI API also generates images and video, so the featured image does not have to be a manual handoff to a designer or a trip to a stock library. Image generation is billed as a fixed token cost per image: a DALL-E 3 image is 4,800 output tokens, a Gemini 1K or 2K image is 32,160, and a Gemini 4K image is 57,600. Videos through Veo cost considerably more, from 144,000 tokens for a 4-second fast render.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://www.cosmicjs.com/docs/api/ai" rel="noopener noreferrer"&gt;AI API reference&lt;/a&gt; for the image and video request formats. Generated media lands in your Bucket's media library, so you can attach it to the draft object in the same run that created the draft.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Stream when a human is watching
&lt;/h2&gt;

&lt;p&gt;Batch jobs do not need streaming. Editor-facing tools do, because a blank screen for twenty seconds reads as a broken feature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TextStreamingResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Draft an intro paragraph for this post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;TextStreamingResponse&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fullResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fullResponse&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;usage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Usage:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Complete:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fullResponse&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 9: Give the pipeline memory
&lt;/h2&gt;

&lt;p&gt;Generation is half of a useful pipeline. Retrieval is the other half. Before drafting a new post, check whether you already published one on the same topic, and feed the existing coverage into the prompt as context.&lt;/p&gt;

&lt;p&gt;Cosmic includes semantic search over your Bucket content, which finds objects by meaning rather than keyword match. That is how you stop a pipeline from producing four posts that compete with each other for the same search query. See the &lt;a href="https://www.cosmicjs.com/docs/api/content-rag" rel="noopener noreferrer"&gt;semantic search docs&lt;/a&gt; for the query format.&lt;/p&gt;

&lt;h2&gt;
  
  
  The no-code path: connect Claude directly to your CMS
&lt;/h2&gt;

&lt;p&gt;If you want an AI assistant operating on your content interactively instead of a scripted job, use the Cosmic MCP server. It exposes your Bucket as tools that Claude Code, Claude Desktop, or Cursor can call directly: read objects, create drafts, upload media, generate content.&lt;/p&gt;

&lt;p&gt;The read-key-only configuration is worth knowing about here. Point the MCP server at a read key and every create, update, and delete tool is blocked, which gives you an assistant that can analyze your content library and propose changes without the ability to write anything. Full setup is in &lt;a href="https://www.cosmicjs.com/blog/connect-claude-code-to-cms-with-mcp" rel="noopener noreferrer"&gt;connect Claude Code to your CMS with MCP&lt;/a&gt; and the &lt;a href="https://www.cosmicjs.com/docs/mcp-server" rel="noopener noreferrer"&gt;MCP server docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails that actually matter
&lt;/h2&gt;

&lt;p&gt;Six rules, learned the practical way.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never publish from an automated step.&lt;/strong&gt; Generation writes drafts. Only a person changes status to published.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ground every factual claim in a source document.&lt;/strong&gt; Use &lt;code&gt;media_url&lt;/code&gt; and instruct the model to use only stated figures. Claims a reviewer cannot trace to a source get cut, not softened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cap &lt;code&gt;max_tokens&lt;/code&gt; per job.&lt;/strong&gt; It is your circuit breaker against a runaway loop consuming a month of allocation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log &lt;code&gt;usage&lt;/code&gt; on every call.&lt;/strong&gt; Store input and output tokens alongside the object so you can attribute cost per piece.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep write keys server side.&lt;/strong&gt; Every AI operation needs one. A write key in client code is a public write key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicate before you generate.&lt;/strong&gt; Run a semantic search first. Two posts targeting one query split their own rankings.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What this costs
&lt;/h2&gt;

&lt;p&gt;AI usage draws tokens from your plan allocation, and text generation applies a tier multiplier to the actual tokens used.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Budget, 1.0x:&lt;/strong&gt; GPT-5 Nano, GPT-5 Mini, Claude Haiku 4.5. 1,000 actual tokens deducts 1,000.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard, 2.0x:&lt;/strong&gt; GPT-5, GPT-5.2, GPT-5.2 Codex, GPT-5.5, Claude Sonnet 4.6, Claude Sonnet 5, Claude Opus 4.7, Claude Opus 4.8, Gemini 3.1 Pro, Kimi K3. 1,000 actual tokens deducts 2,000.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Premium, 4.0x:&lt;/strong&gt; Claude Fable 5. 1,000 actual tokens deducts 4,000.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Media is a fixed cost per asset, billed as output tokens, with the image and video figures listed in Step 7.&lt;/p&gt;

&lt;p&gt;Plan pricing, current at time of writing: Free at $0/month, Builder at $49/month, Team at $299/month, Business at $499/month, and Enterprise on custom pricing. Additional team members are $29/user/month. Token packs are available if you exceed your monthly allocation. Check the &lt;a href="https://www.cosmicjs.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; for current allocations and pack sizes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring whether the pipeline is working
&lt;/h2&gt;

&lt;p&gt;Volume is the wrong metric. A pipeline that triples output and halves engagement has made things worse. Track these instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Approval rate.&lt;/strong&gt; What share of generated drafts a human ships without a rewrite. A low rate points at your prompts and source documents, not the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editing distance.&lt;/strong&gt; How much reviewers change before publishing. Rising distance means quality is drifting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost per published piece.&lt;/strong&gt; Logged &lt;code&gt;usage&lt;/code&gt; divided by pieces that actually shipped, counting the drafts you threw away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engagement on generated pieces versus hand-written ones.&lt;/strong&gt; The honest test.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need my own Anthropic or OpenAI API key?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Text, image, and video generation run through the Cosmic API using your Bucket write key. Model access is included in your plan allocation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which models are available?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude, Gemini, OpenAI, and Moonshot Kimi models, selected with the &lt;code&gt;model&lt;/code&gt; parameter. The default is &lt;code&gt;claude-opus-5&lt;/code&gt;. The full list is in the &lt;a href="https://www.cosmicjs.com/docs/api/ai" rel="noopener noreferrer"&gt;AI API docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can the AI read my existing files?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Pass &lt;code&gt;media_url&lt;/code&gt; pointing at any file in your Bucket: images, PDFs, Excel spreadsheets, Word documents, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Cosmic have a GraphQL API?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Cosmic provides a REST API and the JavaScript/TypeScript SDK, &lt;code&gt;@cosmicjs/sdk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I run this without writing code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, through the MCP server or the AI features in the dashboard. The code path gives you scheduling and repeatability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will generated content hurt my SEO?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unreviewed content will. The review gate in Step 5 exists for that reason. Publish what a human has verified and cut what nobody can source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ship the boring version first
&lt;/h2&gt;

&lt;p&gt;The smallest useful pipeline is one source document, one prompt, one draft object, one reviewer. Build that, run it for a week, then add scheduling and images and retrieval once you trust the output.&lt;/p&gt;

&lt;p&gt;This is the outcome teams are after when they automate content operations. As Maximilian Wuhr, Co-Founder at FINN, put it: "Cosmic is: us never having to ask a developer to change anything on the backend of our website."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.cosmicjs.com/signup?utm_source=dev.to&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto-crosspost&amp;amp;utm_content=ai-content-pipeline"&gt;Start building for free&lt;/a&gt; with AI generation included on every plan, or &lt;a href="https://calendly.com/tonyspiro/cosmic-intro?utm_source=dev.to&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto-crosspost&amp;amp;utm_content=ai-content-pipeline"&gt;book 20 minutes with our CEO&lt;/a&gt; to talk through your content workflow. If you want the interactive version instead, wire up &lt;a href="https://www.cosmicjs.com/ai/agents" rel="noopener noreferrer"&gt;Cosmic AI agents&lt;/a&gt; or the &lt;a href="https://www.cosmicjs.com/docs/mcp-server" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt; and start from a conversation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>DeepSeek V4 Flash: Benchmarks, Pricing, and What It Means for Content Agents</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Fri, 31 Jul 2026 22:08:25 +0000</pubDate>
      <link>https://dev.to/tonyspiro/deepseek-v4-flash-benchmarks-pricing-and-what-it-means-for-content-agents-32fd</link>
      <guid>https://dev.to/tonyspiro/deepseek-v4-flash-benchmarks-pricing-and-what-it-means-for-content-agents-32fd</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.cosmicjs.com/blog/deepseek-v4-flash-benchmarks-pricing" rel="noopener noreferrer"&gt;the Cosmic blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;DeepSeek released &lt;strong&gt;V4-Flash&lt;/strong&gt; on July 31, 2026, and pushed it to public beta on the API under the model name &lt;code&gt;deepseek-v4-flash&lt;/code&gt;. It landed on the front page of Hacker News twice within hours.&lt;/p&gt;

&lt;p&gt;The headline is easy to summarize: near the top of the intelligence rankings, at the bottom of the price rankings. The details are more interesting than the headline, and one of them changes the cost math in a way the per-token price does not show.&lt;/p&gt;

&lt;p&gt;Here is what shipped, what the numbers say, and where a model like this fits in a content pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually shipped
&lt;/h2&gt;

&lt;p&gt;According to DeepSeek's official changelog, V4-Flash uses the &lt;strong&gt;same architecture and the same parameter size&lt;/strong&gt; as the earlier V4-Flash-Preview. The only change is post-training. DeepSeek re-post-trained the model and reports scores that represent a significant jump over the V4-Pro-Preview.&lt;/p&gt;

&lt;p&gt;A few practical notes from the release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model is in &lt;strong&gt;public beta&lt;/strong&gt; on the API.&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;natively supports the Responses API format&lt;/strong&gt; and has been adapted for Codex.&lt;/li&gt;
&lt;li&gt;The V4-Pro API, app, and web experiences are &lt;strong&gt;unchanged&lt;/strong&gt;. An official V4-Pro release is still to come.&lt;/li&gt;
&lt;li&gt;Independent measurement puts it at &lt;strong&gt;284B parameters&lt;/strong&gt;, with weights not publicly released.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;text in, text out&lt;/strong&gt;. No image, audio, or video input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters if you were hoping to drop it into a workflow that processes screenshots or PDFs. You cannot, at least not directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark numbers
&lt;/h2&gt;

&lt;p&gt;DeepSeek published the following scores, evaluated with its own DeepSeek Harness in minimal mode at max effort, &lt;code&gt;top_p&lt;/code&gt; 0.95, &lt;code&gt;temperature&lt;/code&gt; 1.0:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terminal Bench 2.1&lt;/td&gt;
&lt;td&gt;82.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cybergym&lt;/td&gt;
&lt;td&gt;76.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toolathlon (verified)&lt;/td&gt;
&lt;td&gt;70.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSBench-FullStack&lt;/td&gt;
&lt;td&gt;68.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSBench-Hard&lt;/td&gt;
&lt;td&gt;59.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSWE&lt;/td&gt;
&lt;td&gt;54.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NL2Repo&lt;/td&gt;
&lt;td&gt;54.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent Last Exam&lt;/td&gt;
&lt;td&gt;25.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation Bench (Public)&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the shape of that table rather than any single row. The strongest results cluster around &lt;strong&gt;terminal use, tool calling, and multi-step engineering work&lt;/strong&gt;: Terminal Bench at 82.7 and Toolathlon at 70.3 are agentic evaluations, not trivia recall. This is a model tuned to operate software.&lt;/p&gt;

&lt;p&gt;The bottom two rows deserve an honest caveat. Agent Last Exam and Automation Bench are deliberately brutal, long-horizon evaluations where scores in the twenties are normal across the frontier right now. A 25.2 is not a failure. It is a reminder that fully autonomous, hours-long task completion remains unsolved by everyone.&lt;/p&gt;

&lt;p&gt;One more caveat worth stating plainly: these are vendor-published numbers, produced with the vendor's own harness at max effort settings. Treat them as a strong signal and verify on your own evals before you migrate anything that matters.&lt;/p&gt;

&lt;p&gt;Independent measurement from Artificial Analysis puts V4-Flash at an &lt;strong&gt;Intelligence Index of 50&lt;/strong&gt;, ranking it &lt;strong&gt;2nd out of 162&lt;/strong&gt; models in its class, against a class median of 17.&lt;/p&gt;

&lt;h2&gt;
  
  
  The price story, and the part people will miss
&lt;/h2&gt;

&lt;p&gt;On raw API pricing, V4-Flash ranks &lt;strong&gt;1st out of 162&lt;/strong&gt; in its class:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;$0.14&lt;/strong&gt; per 1M input tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0.28&lt;/strong&gt; per 1M output tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0.003&lt;/strong&gt; per 1M cached input tokens, a &lt;strong&gt;98% discount&lt;/strong&gt; on a cache hit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0.06&lt;/strong&gt; per 1M tokens blended&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1M token context window&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That cache-hit price is the number to pay attention to, and it is the one that gets skimmed past. For any agent that runs repeatedly against the same system prompt, the same schema definitions, and the same style guide, the fixed prefix of every request is identical. Caching turns that prefix from a recurring cost into a rounding error.&lt;/p&gt;

&lt;p&gt;Now the catch.&lt;/p&gt;

&lt;p&gt;Artificial Analysis also measured &lt;strong&gt;verbosity&lt;/strong&gt;, and V4-Flash consumed &lt;strong&gt;210M output tokens&lt;/strong&gt; to complete the Intelligence Index, against a median of &lt;strong&gt;62M&lt;/strong&gt;. That ranks it 58th of 162 on output efficiency. It produced roughly three and a half times the median number of tokens to reach its score.&lt;/p&gt;

&lt;p&gt;Cheap tokens and cheap tasks are two different measurements. A model at a third of the per-token price that emits three times the tokens has spent your savings. The total cost to evaluate V4-Flash on the Intelligence Index came to &lt;strong&gt;$72.02&lt;/strong&gt;, which is genuinely low, so the economics still work out well here. The lesson is that you should benchmark &lt;strong&gt;cost per completed task&lt;/strong&gt; on your own workload rather than reading the price-per-million column and stopping there.&lt;/p&gt;

&lt;p&gt;If your workload is latency-sensitive, the verbosity number is also a throughput warning. More tokens means more time to last token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits in a content pipeline
&lt;/h2&gt;

&lt;p&gt;Strong tool-calling scores, a 1M token context window, and a 98% cache discount describe a specific kind of useful: &lt;strong&gt;long-running agents that read and write structured content&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is a real workload. Translating a content library into six locales. Auditing every post for a deprecated product claim. Regenerating meta descriptions across a thousand objects. Keeping a changelog in sync with release notes. These jobs are repetitive, schema-bound, and expensive at frontier prices, which is exactly the profile where a cheap, cache-friendly, tool-capable model earns its place.&lt;/p&gt;

&lt;p&gt;The 1M context window matters here for a specific reason. Content operations tend to need &lt;strong&gt;broad but shallow&lt;/strong&gt; context: a large number of records, each fairly small. You can put an entire content model and hundreds of objects in a single request and let the model reason across all of it, instead of building a retrieval layer to work around a smaller window.&lt;/p&gt;

&lt;p&gt;With Cosmic, the read side of that loop is a few lines. Pull the objects and the fields you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id,slug,title,metadata.seo_description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;.props()&lt;/code&gt; limits the payload to the fields you actually asked for, you control your own token bill at the source. Sending 200 slugs and meta descriptions costs a fraction of sending 200 full post bodies.&lt;/p&gt;

&lt;p&gt;Then hand the batch to the model. The API is OpenAI-compatible, so the client you already have works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.deepseek.com/chat/completions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEEPSEEK_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deepseek-v4-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;STYLE_GUIDE&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep &lt;code&gt;STYLE_GUIDE&lt;/code&gt; byte-identical across runs so it stays cacheable. Put the variable content in the user message. That one discipline is where the 98% cache discount actually shows up on your invoice.&lt;/p&gt;

&lt;p&gt;Writing results back through the SDK closes the loop, and because every change lands as a versioned object rather than a direct database write, you keep an audit trail of what the agent changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you switch?
&lt;/h2&gt;

&lt;p&gt;A reasonable read as of today:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good fit for V4-Flash&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-volume, repetitive content operations where per-task cost dominates&lt;/li&gt;
&lt;li&gt;Agentic and terminal-oriented work, which is where the benchmarks are strongest&lt;/li&gt;
&lt;li&gt;Workloads with a large stable prompt prefix that will hit the cache&lt;/li&gt;
&lt;li&gt;Jobs that need broad context across many records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Look elsewhere&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anything requiring image, audio, or video input, since the model is text-only&lt;/li&gt;
&lt;li&gt;Latency-critical, user-facing interactions, given the verbosity numbers&lt;/li&gt;
&lt;li&gt;Production systems with no tolerance for a public beta API&lt;/li&gt;
&lt;li&gt;Deployments that require open weights, which are not available here&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest summary: this is a strong, cheap, tool-capable text model that is well suited to background content work and not yet the right choice for multimodal or latency-sensitive paths. Run it against your own evals before you commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the pipeline the model plugs into
&lt;/h2&gt;

&lt;p&gt;Model leaderboards will keep churning. The thing that determines whether you can take advantage of a release like this one is whether your content layer is API-first, so swapping the model behind it is a config change rather than a migration.&lt;/p&gt;

&lt;p&gt;That is what &lt;a href="https://www.cosmicjs.com?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=deepseek-v4-flash-benchmarks-pricing" rel="noopener noreferrer"&gt;Cosmic&lt;/a&gt; is for. Your content lives in a headless CMS with a REST API and a TypeScript SDK, so any model, agent, or framework can read and write it. When a cheaper or smarter model ships next month, you point at it and move on.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=deepseek-v4-flash-benchmarks-pricing" rel="noopener noreferrer"&gt;Start building free&lt;/a&gt;&lt;/strong&gt; and connect a bucket in minutes&lt;/li&gt;
&lt;li&gt;Read the &lt;strong&gt;&lt;a href="https://www.cosmicjs.com/ai?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=deepseek-v4-flash-benchmarks-pricing" rel="noopener noreferrer"&gt;AI features overview&lt;/a&gt;&lt;/strong&gt; to see what is built in&lt;/li&gt;
&lt;li&gt;Comparing coding agents too? See our &lt;strong&gt;&lt;a href="https://www.cosmicjs.com/blog/claude-code-vs-github-copilot-vs-cursor-which-ai-coding-agent-should-you-use-2026?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=deepseek-v4-flash-benchmarks-pricing" rel="noopener noreferrer"&gt;Claude Code vs GitHub Copilot vs Cursor breakdown&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Running this at team scale? &lt;strong&gt;&lt;a href="https://calendly.com/tonyspiro/cosmic-intro" rel="noopener noreferrer"&gt;Book 15 minutes with our CEO&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Benchmark scores in this post are from DeepSeek's official changelog dated July 31, 2026. Pricing, Intelligence Index, parameter count, and verbosity figures are from Artificial Analysis. Vendor-published benchmarks were produced with DeepSeek's own harness at max effort settings, so verify against your own workload.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Claude Sonnet 5 vs Opus 5: A Real-World Comparison (2026)</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Fri, 31 Jul 2026 22:07:25 +0000</pubDate>
      <link>https://dev.to/tonyspiro/claude-sonnet-5-vs-opus-5-a-real-world-comparison-2026-1o67</link>
      <guid>https://dev.to/tonyspiro/claude-sonnet-5-vs-opus-5-a-real-world-comparison-2026-1o67</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.cosmicjs.com/blog/claude-sonnet-5-vs-opus-5" rel="noopener noreferrer"&gt;the Cosmic blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Anthropic shipped Claude Sonnet 5 on June 30, 2026 and Claude Opus 5 on July 24, 2026. Both are excellent. The expensive mistake is picking one, wiring it into every code path, and then either overpaying on trivial work or under-resourcing the work that actually matters.&lt;/p&gt;

&lt;p&gt;This is the practical breakdown: what each model is measurably good at, what the same job costs on each once you account for tokenization, and a routing rule you can ship this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your workload&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Content generation, summarization, chat, classification, tagging&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Sonnet 5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Highest quality per dollar. This is the volume tier.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day-to-day coding, refactors, test writing, PR review&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Sonnet 5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;72.7% on SWE-bench Verified, a 10.4 point jump over Sonnet 4.6.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long autonomous agent runs with tool calls&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Opus 5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Anthropic reports it ranked first on Zapier's AutomationBench for end-to-end task completion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture decisions, ambiguous multi-step problems, novel reasoning&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Opus 5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Self-verification and judgment are the headline improvements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anything where a wrong answer is expensive to unwind&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Opus 5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lowest misaligned-behavior score of Anthropic's recent releases at 2.3.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-volume production traffic on a budget&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Sonnet 5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It is the default on Claude Free and Pro for a reason.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want one sentence to take away: run Sonnet 5 by default and escalate to Opus 5 on the specific paths where judgment matters more than throughput.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the benchmarks actually say
&lt;/h2&gt;

&lt;p&gt;There is a wrinkle worth knowing before you compare scores. Anthropic evaluated these two models on &lt;strong&gt;different suites&lt;/strong&gt;, so there is no clean head-to-head table, and anyone who publishes one has filled in gaps with guesses.&lt;/p&gt;

&lt;p&gt;Here is what was actually published for Sonnet 5:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Sonnet 5&lt;/th&gt;
&lt;th&gt;Sonnet 4.6&lt;/th&gt;
&lt;th&gt;Opus 4.8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SWE-bench Verified&lt;/td&gt;
&lt;td&gt;72.7%&lt;/td&gt;
&lt;td&gt;62.3%&lt;/td&gt;
&lt;td&gt;79.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminal-bench&lt;/td&gt;
&lt;td&gt;76.1%&lt;/td&gt;
&lt;td&gt;55.4%&lt;/td&gt;
&lt;td&gt;not published&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPQA Diamond&lt;/td&gt;
&lt;td&gt;78.0%&lt;/td&gt;
&lt;td&gt;not published&lt;/td&gt;
&lt;td&gt;not published&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MMMU&lt;/td&gt;
&lt;td&gt;76.3%&lt;/td&gt;
&lt;td&gt;not published&lt;/td&gt;
&lt;td&gt;not published&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MathVista&lt;/td&gt;
&lt;td&gt;76.6%&lt;/td&gt;
&lt;td&gt;not published&lt;/td&gt;
&lt;td&gt;not published&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CharacterEval&lt;/td&gt;
&lt;td&gt;90.3%&lt;/td&gt;
&lt;td&gt;not published&lt;/td&gt;
&lt;td&gt;not published&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And here is what was published for Opus 5:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State of the art on coding and knowledge work, measured on Frontier-Bench and GDPval-AA.&lt;/li&gt;
&lt;li&gt;First place on Zapier's AutomationBench for end-to-end task completion.&lt;/li&gt;
&lt;li&gt;An ARC-AGI 3 score roughly three times the next-best model.&lt;/li&gt;
&lt;li&gt;A misaligned-behavior score of 2.3, the lowest of Anthropic's recent releases.&lt;/li&gt;
&lt;li&gt;Matches Claude Fable 5 on intelligence at approximately half the cost.&lt;/li&gt;
&lt;li&gt;A fast mode that runs about 2.5x quicker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what is missing: &lt;strong&gt;Anthropic did not publish a SWE-bench Verified number for Opus 5.&lt;/strong&gt; So the honest comparison on classic coding benchmarks is Sonnet 5 at 72.7% against Opus 4.8 at 79.4%, with Opus 5 positioned above Opus 4.8 on the newer agentic and knowledge-work suites. Treat any "Opus 5 scores X% on SWE-bench" claim you find elsewhere as unsourced.&lt;/p&gt;

&lt;p&gt;The practical read: Sonnet 5 closed most of the gap to the previous Opus generation on straightforward coding. Opus 5's advantage shows up in long-horizon agent work, self-verification, and problems where the model has to decide what the task even is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually costs
&lt;/h2&gt;

&lt;p&gt;List prices per million tokens:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 5&lt;/td&gt;
&lt;td&gt;$2&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;td&gt;Introductory pricing through August 31, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 5&lt;/td&gt;
&lt;td&gt;$3&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;td&gt;Standard pricing from September 1, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opus 5&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;Same as Opus 4.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Headline math says Opus 5 costs 1.67x Sonnet 5 at standard pricing, and 2.5x during the introductory window. That understates Sonnet 5's real cost, because &lt;strong&gt;Sonnet 5 ships a new tokenizer that counts the same text as 1.0x to 1.35x more tokens.&lt;/strong&gt; You pay per token, not per word, so that inflation lands on your invoice.&lt;/p&gt;

&lt;p&gt;Work a concrete example. Take a job that a baseline tokenizer counts as 1,000,000 input tokens and 200,000 output tokens:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Input cost&lt;/th&gt;
&lt;th&gt;Output cost&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;th&gt;Opus 5 premium&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Opus 5&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$10.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 5 standard, no inflation&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$6.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.67x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 5 standard, 1.35x inflation&lt;/td&gt;
&lt;td&gt;$4.05&lt;/td&gt;
&lt;td&gt;$4.05&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$8.10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.23x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 5 intro, no inflation&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$4.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.50x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 5 intro, 1.35x inflation&lt;/td&gt;
&lt;td&gt;$2.70&lt;/td&gt;
&lt;td&gt;$2.70&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$5.40&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.85x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That 1.23x row is the one that should change your thinking. On text-heavy workloads that tokenize badly, at standard pricing, Opus 5 costs about 23% more than Sonnet 5 rather than 67% more. If judgment quality matters on that path at all, a 23% premium is easy to justify.&lt;/p&gt;

&lt;p&gt;Two caveats so you use this honestly. The inflation range is 1.0x to 1.35x depending on your content, and where you land is an empirical question about your own data. And the September 1 price change means any cost model you build this month needs revisiting. Run 1,000 real requests through both models, compare your actual billed token counts, and decide from your numbers instead of this table.&lt;/p&gt;

&lt;h2&gt;
  
  
  A routing rule you can ship
&lt;/h2&gt;

&lt;p&gt;Most teams do not need a clever classifier. A static route based on task type captures nearly all of the savings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Default to Sonnet 5.&lt;/strong&gt; Content, chat, summaries, tagging, ordinary code changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalate to Opus 5 on three triggers:&lt;/strong&gt; the task requires more than roughly ten tool calls, the task is architectural or ambiguous, or a wrong answer is expensive to reverse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalate on retry.&lt;/strong&gt; If Sonnet 5's output fails validation twice, retry once on Opus 5 instead of a third time on Sonnet 5. This is the highest-return rule on the list and it takes about six lines of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never hardcode the model name.&lt;/strong&gt; Anthropic shipped two frontier models in 25 days. Whatever you pin today will be stale within a quarter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Point four is where most of the long-term pain lives. If your model identifiers are compiled into your application, every model release becomes a pull request, a review, and a deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep model config out of your codebase
&lt;/h2&gt;

&lt;p&gt;Store your routing configuration as content instead. Then swapping Sonnet 5 for Sonnet 5.1 is a field edit that takes effect immediately, with no rebuild.&lt;/p&gt;

&lt;p&gt;Install the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @cosmicjs/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define the routing table as an object in Cosmic and read it at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ModelRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;default_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;escalation_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;escalate_after_failures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getModelRoute&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ModelRoute&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model-config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ModelRoute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it at the call site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getModelRoute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;default_model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// "claude-sonnet-5"&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isArchitectural&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;escalate_after_failures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;escalation_model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// "claude-opus-5"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runTask&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the next model lands, a non-engineer updates one field in the dashboard and production picks it up. No deploy, no pull request, no engineer in the loop. Cosmic exposes this over a REST API and the TypeScript SDK, so the same config is readable from any framework or runtime you use.&lt;/p&gt;

&lt;p&gt;For the previous generation's version of this decision, see &lt;a href="https://www.cosmicjs.com/blog/claude-sonnet-45-vs-opus-45-a-real-world-comparison" rel="noopener noreferrer"&gt;Sonnet 4.5 vs Opus 4.5&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Opus 5 better than Sonnet 5?&lt;/strong&gt;&lt;br&gt;
On ambiguous reasoning, long autonomous agent runs, and self-verification, yes. On cost per acceptable output for high-volume tasks, Sonnet 5 wins clearly. They are built for different jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does Claude Sonnet 5 cost?&lt;/strong&gt;&lt;br&gt;
$2 per million input tokens and $10 per million output tokens through August 31, 2026, moving to $3 and $15 on September 1, 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does Claude Opus 5 cost?&lt;/strong&gt;&lt;br&gt;
$5 per million input tokens and $25 per million output tokens, unchanged from Opus 4.8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did Anthropic publish a SWE-bench Verified score for Opus 5?&lt;/strong&gt;&lt;br&gt;
No. Sonnet 5 scored 72.7% and Opus 4.8 scored 79.4%. Opus 5's published results cover Frontier-Bench, GDPval-AA, Zapier AutomationBench, and ARC-AGI 3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which Claude model should I use for coding?&lt;/strong&gt;&lt;br&gt;
Sonnet 5 for the majority of day-to-day work. Escalate to Opus 5 for architecture, cross-cutting refactors, and anything where you cannot cheaply verify the result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do my Sonnet 5 token counts look higher than expected?&lt;/strong&gt;&lt;br&gt;
Sonnet 5 uses a new tokenizer that counts the same text as 1.0x to 1.35x more tokens than the previous generation. Compare billed tokens, not word counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build on infrastructure that outlives the model
&lt;/h2&gt;

&lt;p&gt;Anthropic shipped two frontier models in under a month. The teams handling that well are the ones that never put a model name in a source file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.cosmicjs.com?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=claude-sonnet-5-vs-opus-5" rel="noopener noreferrer"&gt;Cosmic&lt;/a&gt; is an AI-powered headless CMS where your content and your configuration both live behind a REST API and a TypeScript SDK. Model choice becomes a content decision, editable by anyone on your team, live in production the moment it is saved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=claude-sonnet-5-vs-opus-5" rel="noopener noreferrer"&gt;Start free&lt;/a&gt; on the Free plan with 1 Bucket, 2 team members, and 1,000 Objects. No credit card required. If you want to talk through a specific architecture, &lt;a href="https://calendly.com/tonyspiro/cosmic-intro" rel="noopener noreferrer"&gt;book 20 minutes with our CEO&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bun vs Node vs Deno in 2026: How to Choose a JavaScript Runtime</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Fri, 31 Jul 2026 22:05:36 +0000</pubDate>
      <link>https://dev.to/tonyspiro/bun-vs-node-vs-deno-in-2026-how-to-choose-a-javascript-runtime-53ce</link>
      <guid>https://dev.to/tonyspiro/bun-vs-node-vs-deno-in-2026-how-to-choose-a-javascript-runtime-53ce</guid>
      <description>&lt;p&gt;Three JavaScript runtimes are now viable for production work: Node.js, Deno, and Bun. That is a genuinely good problem to have, and it makes the choice harder than it was when Node was the only answer.&lt;/p&gt;

&lt;p&gt;Most runtime comparisons you will find lead with benchmark charts. We are going to skip those, and the reason is worth stating plainly: published runtime benchmarks are almost always produced by the runtime's own team, measured on a workload chosen to flatter it. Your API is not that workload. The numbers that matter are the ones you measure on your own code, and we show you how to get them at the end of this post.&lt;/p&gt;

&lt;p&gt;What this guide covers instead is the set of differences that are stable, documented, and actually decide the outcome: ecosystem depth, how each runtime handles TypeScript, what ships in the box, the security model, and where you can deploy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Node.js&lt;/th&gt;
&lt;th&gt;Deno&lt;/th&gt;
&lt;th&gt;Bun&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Anything where ecosystem risk matters&lt;/td&gt;
&lt;td&gt;Security-sensitive and web-standard code&lt;/td&gt;
&lt;td&gt;Fast local dev and all-in-one tooling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Needs a build step or a loader&lt;/td&gt;
&lt;td&gt;Runs &lt;code&gt;.ts&lt;/code&gt; directly&lt;/td&gt;
&lt;td&gt;Runs &lt;code&gt;.ts&lt;/code&gt; directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Package manager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;npm, pnpm, yarn&lt;/td&gt;
&lt;td&gt;npm specifiers plus JSR&lt;/td&gt;
&lt;td&gt;Built in (&lt;code&gt;bun install&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Test runner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bundler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;External&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Permissions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Opt-in, still maturing&lt;/td&gt;
&lt;td&gt;Deny by default&lt;/td&gt;
&lt;td&gt;Opt-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hosting support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Everywhere&lt;/td&gt;
&lt;td&gt;Good and growing&lt;/td&gt;
&lt;td&gt;Good and growing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want the one-line answer: pick &lt;strong&gt;Node&lt;/strong&gt; when the cost of an ecosystem surprise is high, pick &lt;strong&gt;Deno&lt;/strong&gt; when you want strict sandboxing and web-standard APIs, pick &lt;strong&gt;Bun&lt;/strong&gt; when developer iteration speed and a single toolchain matter most.&lt;/p&gt;

&lt;p&gt;Most people arrive here weighing a pair rather than all three, so it helps to name the tradeoff in each one. If your question is bun vs node, you are trading ecosystem certainty for a faster toolchain and fewer config files. If it is deno vs node, the deciding factor is the permission model rather than performance, because Deno refuses ambient filesystem and network access that Node grants freely. And if it is bun vs deno, both already run TypeScript directly and ship their own test runner and bundler, so the real choice is the fastest toolchain against the strictest sandbox. The three sections below take those pairings in order.&lt;/p&gt;




&lt;h2&gt;
  
  
  Node.js vs Bun and Deno: the incumbent case
&lt;/h2&gt;

&lt;p&gt;Node is the incumbent, and incumbency is a real technical feature. Every hosting provider supports it, every observability vendor has an agent for it, every obscure npm package was tested against it, and every Stack Overflow answer assumes it. When something breaks at 2am, Node is the runtime most likely to have a documented answer already written.&lt;/p&gt;

&lt;p&gt;Node has also closed much of the gap that made the alternatives attractive in the first place. It ships a built-in test runner, native ESM support alongside CommonJS, a built-in watch mode, and a permission model that continues to develop release over release. A lot of the "you need Deno for that" arguments from a few years ago no longer hold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Node costs you:&lt;/strong&gt; TypeScript still wants a build step or a loader in most setups, and the toolchain is assembled rather than provided. A typical project pulls in a package manager, a bundler, a transpiler, a test runner, and a linter, each with its own config file and its own upgrade cadence. That assembly is flexible, and it is also the single biggest source of setup friction in the JavaScript ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Node if:&lt;/strong&gt; you are shipping something you will maintain for years, you have compliance or vendor requirements, or you depend on native modules that only ever get tested against Node.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deno vs Node: the security model
&lt;/h2&gt;

&lt;p&gt;Deno's defining decision is that code gets no ambient permissions. A Deno process cannot read the filesystem, open a network connection, or read environment variables unless you granted that specific capability at launch. Every other runtime here works the opposite way: any dependency you install can do anything your process can do.&lt;/p&gt;

&lt;p&gt;That matters more every year. Supply chain attacks against package registries are now routine, and "this transitive dependency exfiltrated our environment variables" is a real class of incident. Deno makes it structurally harder, and no amount of care in Node or Bun gives you the same guarantee.&lt;/p&gt;

&lt;p&gt;Beyond the sandbox, Deno leans hard on web standards. &lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;Request&lt;/code&gt;, &lt;code&gt;Response&lt;/code&gt;, &lt;code&gt;URL&lt;/code&gt;, and Web Streams are the native vocabulary, so code you write for a Deno server often moves to a browser, a service worker, or an edge function with little change. It runs TypeScript directly, ships a formatter, linter, test runner, and bundler, and supports npm packages through &lt;code&gt;npm:&lt;/code&gt; specifiers alongside its own JSR registry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Deno costs you:&lt;/strong&gt; npm compatibility is good but not total, and the packages most likely to give you trouble are the ones doing something unusual with native bindings or filesystem layout. You will occasionally spend an afternoon on a dependency that would have just worked on Node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Deno if:&lt;/strong&gt; you are handling sensitive data, you want dependency risk contained by default, or you are writing code that needs to run at the edge and on a server without a rewrite.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bun vs Node: the toolchain
&lt;/h2&gt;

&lt;p&gt;Bun's pitch is consolidation. One binary is your runtime, package manager, bundler, test runner, and script runner. There is no separate transpiler to configure, no bundler config, and TypeScript and JSX run directly. For a greenfield project, the difference between &lt;code&gt;bun init&lt;/code&gt; and assembling an equivalent Node toolchain is substantial, and it stays substantial every time you onboard a new engineer.&lt;/p&gt;

&lt;p&gt;Bun also ships batteries that other runtimes leave to packages: an HTTP server API, a SQLite driver, a shell API for cross-platform scripting, and a password hashing implementation. Fewer dependencies means less to audit and less to upgrade.&lt;/p&gt;

&lt;p&gt;Compatibility was Bun's weakest point early on and is now its strongest argument. It implements Node's APIs deliberately so that existing projects and existing npm packages run unmodified, which makes it realistic to adopt Bun in a codebase you did not start from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Bun costs you:&lt;/strong&gt; it is the youngest of the three. The edges you hit will be less documented, and the engineer who hits one is more likely to be the first person to hit it. Some teams also use Bun only as a package manager and test runner while continuing to run Node in production, which is a legitimate and low-risk way to get most of the benefit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Bun if:&lt;/strong&gt; you are starting something new, your team is small enough that toolchain friction is a real tax, or your test suite and install times are actively slowing you down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your content layer should not care
&lt;/h2&gt;

&lt;p&gt;One thing worth checking before you commit: whether the services your app depends on will follow you across runtimes.&lt;/p&gt;

&lt;p&gt;An API-first content layer is portable by construction. The official Cosmic TypeScript SDK is standard JavaScript over a REST API with no native bindings, so the same code runs unchanged on all three runtimes, and in edge functions and React Server Components as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_BUCKET_SLUG&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COSMIC_READ_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install it with &lt;code&gt;npm install @cosmicjs/sdk&lt;/code&gt;, &lt;code&gt;bun add @cosmicjs/sdk&lt;/code&gt;, or a &lt;code&gt;npm:@cosmicjs/sdk&lt;/code&gt; specifier on Deno. The application code is identical in all three cases. Runtime choice stays a runtime decision and never becomes a content migration.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to actually decide
&lt;/h2&gt;

&lt;p&gt;Skip the benchmark blog posts, including the ones that favor us. Run this instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Test your real dependency tree first.&lt;/strong&gt; Take your existing &lt;code&gt;package.json&lt;/code&gt;, install it on each candidate runtime, and run your test suite. This single step eliminates more options than any performance measurement will, and it takes an afternoon. A runtime that cannot install your dependencies is not a candidate no matter how fast it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Measure your workload, not a synthetic one.&lt;/strong&gt; Put each runtime behind the same load generator, serving your actual routes against your actual database, and compare p95 and p99 latency under a concurrency level you expect in production. Average throughput on a hello-world handler predicts nothing about your app. If the difference you measure is within noise, and for many CRUD APIs it will be, then performance is not your deciding factor and you should choose on tooling and risk instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Confirm your deployment target.&lt;/strong&gt; Check that your hosting platform, your CI runner, your APM vendor, and your container base image all support the runtime you want. Node passes this trivially. The other two usually pass, and "usually" is worth verifying before you write code rather than after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Price the migration honestly.&lt;/strong&gt; Moving an existing production service between runtimes is a real project with a real regression risk. The upside has to be large enough to justify it. New services are where switching costs are near zero, so that is where experimenting belongs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Consider splitting the decision.&lt;/strong&gt; Using Bun for local development, installs, and tests while deploying on Node is a common and sensible arrangement. You get the fast toolchain where it saves you time daily, and the boring runtime where reliability matters most.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest conclusion
&lt;/h2&gt;

&lt;p&gt;For most teams shipping a web application in 2026, all three runtimes will work. That is the actual state of the ecosystem, and any post that tells you one of them is obviously correct for everyone is selling something.&lt;/p&gt;

&lt;p&gt;Node remains the lowest-risk default and has quietly absorbed most of the features that once made it feel dated. Deno is the right call when dependency risk is a genuine threat model rather than a hypothetical. Bun is the most pleasant to develop against and the easiest to start with, and its compatibility work has made it a reasonable production choice rather than an experiment.&lt;/p&gt;

&lt;p&gt;Pick based on your team's risk tolerance and where your friction actually is. Then make sure the rest of your stack, your content layer included, does not lock the decision in.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Bun production ready in 2026?
&lt;/h3&gt;

&lt;p&gt;Yes, for most workloads. Bun implements Node's APIs deliberately so existing npm packages and projects run unmodified, and teams are running it in production. The remaining risk is maturity rather than capability: fewer engineers have hit any given edge case before you. Teams that want the tooling benefit with minimal risk often use Bun for installs, tests, and local development while deploying on Node.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run npm packages on Deno?
&lt;/h3&gt;

&lt;p&gt;Yes, through &lt;code&gt;npm:&lt;/code&gt; specifiers, and compatibility covers the large majority of the registry. Packages that rely on native bindings or unusual filesystem assumptions are the ones most likely to need work. Test your actual dependency tree before committing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which JavaScript runtime is fastest?
&lt;/h3&gt;

&lt;p&gt;It depends entirely on the workload, and any single answer to this question is marketing. Bun and Deno both publish favorable benchmarks against Node, and those benchmarks measure workloads their teams selected. For a typical API that spends most of its time waiting on a database or an upstream service, runtime differences are frequently within measurement noise. Benchmark your own routes under realistic concurrency and compare p95 latency before you let performance drive the decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I migrate an existing Node app to Bun or Deno?
&lt;/h3&gt;

&lt;p&gt;Usually not on its own merits. Migrating a production service carries real regression risk, and the payoff has to be large to justify it. Adopt a new runtime on a new service where switching costs are near zero, learn its failure modes there, and revisit migration once you have operational experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I still need TypeScript build tooling?
&lt;/h3&gt;

&lt;p&gt;On Deno and Bun, no: both execute &lt;code&gt;.ts&lt;/code&gt; files directly. On Node you still want a build step or a loader for most setups. Note that running TypeScript directly is not the same as type checking it, and Deno and Bun both skip type checking at runtime by design, so keep &lt;code&gt;tsc --noEmit&lt;/code&gt; in CI regardless of runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a headless CMS work the same across all three runtimes?
&lt;/h3&gt;

&lt;p&gt;If it is API-first, yes. The Cosmic TypeScript SDK is standard JavaScript over a REST API with no native dependencies, so identical application code runs on Node, Deno, and Bun, plus edge runtimes and React Server Components.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.cosmicjs.com/blog/bun-vs-node-vs-deno-2026" rel="noopener noreferrer"&gt;Cosmic blog&lt;/a&gt;. Cosmic is an AI-powered headless CMS with a REST API, TypeScript SDK, and AI Agents that live in Slack, WhatsApp, and Telegram. &lt;a href="https://app.cosmicjs.com/signup" rel="noopener noreferrer"&gt;Try it free, no credit card required.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>node</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How to Connect Claude Code to Your CMS with MCP</title>
      <dc:creator>Tony Spiro</dc:creator>
      <pubDate>Thu, 30 Jul 2026 22:08:46 +0000</pubDate>
      <link>https://dev.to/tonyspiro/how-to-connect-claude-code-to-your-cms-with-mcp-3n95</link>
      <guid>https://dev.to/tonyspiro/how-to-connect-claude-code-to-your-cms-with-mcp-3n95</guid>
      <description>&lt;p&gt;Claude Code is fluent in your repository and blind to your content. It can refactor the component that renders your blog, but ask it to publish the post that component displays and it has nowhere to look. The Model Context Protocol (MCP) closes that gap. It gives Claude Code a set of tools it can call against your CMS directly, so reading and writing content happens in the same conversation as the code.&lt;/p&gt;

&lt;p&gt;This guide connects Claude Code to a Cosmic bucket. With the hosted endpoint it takes about five minutes and requires no install.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MCP actually gives Claude Code
&lt;/h2&gt;

&lt;p&gt;MCP is an open protocol for exposing tools to AI assistants. The Cosmic MCP server implements it and exposes &lt;strong&gt;18 tools&lt;/strong&gt; across four areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Objects&lt;/strong&gt; (5 tools): list, get, create, update, and delete content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media&lt;/strong&gt; (4 tools): list, get, upload, and delete files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object Types&lt;/strong&gt; (5 tools): list, get, create, update, and delete content models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Generation&lt;/strong&gt; (4 tools): generate text, images, video, and audio into your bucket&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once connected, "publish the MCP draft and generate a hero image for it" resolves to real tool calls against your bucket. No browser tab, no copy-paste, no manual export.&lt;/p&gt;

&lt;p&gt;There are two ways to connect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hosted MCP (recommended):&lt;/strong&gt; point your client at &lt;code&gt;https://mcp.cosmicjs.com/v1/buckets/{your-bucket-slug}&lt;/code&gt; and authenticate with your bucket keys. Nothing to install.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted (stdio):&lt;/strong&gt; run the &lt;code&gt;@cosmicjs/mcp&lt;/code&gt; npm package locally via &lt;code&gt;npx&lt;/code&gt;. Useful for offline work, or when you want the MCP process running inside your own dev environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;p&gt;You need three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A Cosmic bucket.&lt;/strong&gt; The Free plan is $0/month and includes 1 Bucket, 2 team members, and 1,000 Objects, which is more than enough to follow along. &lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=connect-claude-code-to-cms-with-mcp" rel="noopener noreferrer"&gt;Start for free&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your bucket slug, read key, and write key.&lt;/strong&gt; Step 1 below covers where to find them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude Code installed.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hosted path needs no local runtime at all. Node is only required if you choose the self-hosted stdio option, since that runs through &lt;code&gt;npx&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Get your bucket credentials
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Log in to the &lt;a href="https://app.cosmicjs.com" rel="noopener noreferrer"&gt;Cosmic dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to your bucket&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;API Access&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Copy your &lt;strong&gt;bucket slug&lt;/strong&gt;, &lt;strong&gt;read key&lt;/strong&gt;, and &lt;strong&gt;write key&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A recommendation before you paste anything: start with the read key only. Cosmic issues separate read and write keys per bucket, so you can give Claude Code full visibility into your content while making it structurally incapable of changing it. Add the write key once you trust the setup. The read-only vs full access section below covers exactly what changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Connect with hosted MCP (recommended)
&lt;/h2&gt;

&lt;p&gt;Claude Code picks up project-scoped MCP servers from a &lt;code&gt;.mcp.json&lt;/code&gt; file at the root of your repository. Create it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cosmic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.cosmicjs.com/v1/buckets/your-bucket-slug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer your-read-key:your-write-key"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;your-bucket-slug&lt;/code&gt;, &lt;code&gt;your-read-key&lt;/code&gt;, and &lt;code&gt;your-write-key&lt;/code&gt; with the values from Step 1. The endpoint supports the streamable-HTTP MCP transport.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the authorization header works
&lt;/h3&gt;

&lt;p&gt;Cosmic packs both keys into a single bearer token, separated by a colon. The write key is the part after the colon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Read-only access
Authorization: Bearer rk_abc123def456

# Full access (read + write)
Authorization: Bearer rk_abc123def456:wk_zyx987wvu654
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Omit the colon and the write key for read-only access. If your client cannot send a colon-packed token, you can pass the write key out-of-band using the &lt;code&gt;X-Cosmic-Write-Key&lt;/code&gt; header instead.&lt;/p&gt;

&lt;p&gt;One housekeeping note: &lt;code&gt;.mcp.json&lt;/code&gt; now contains live credentials, so add it to &lt;code&gt;.gitignore&lt;/code&gt; before your next commit.&lt;/p&gt;

&lt;p&gt;The same &lt;code&gt;mcpServers&lt;/code&gt; block works for Claude Desktop and Cursor. The &lt;a href="https://www.cosmicjs.com/docs/mcp-server?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=connect-claude-code-to-cms-with-mcp" rel="noopener noreferrer"&gt;MCP server docs&lt;/a&gt; list the exact config file paths for each client, including &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt; on macOS and &lt;code&gt;.cursor/mcp.json&lt;/code&gt; for Cursor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2, alternative: self-hosted over stdio
&lt;/h2&gt;

&lt;p&gt;If you would rather run the server yourself, the &lt;code&gt;@cosmicjs/mcp&lt;/code&gt; package ships a stdio binary. Point Claude Code at &lt;code&gt;npx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cosmic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"@cosmicjs/mcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"COSMIC_BUCKET_SLUG"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-bucket-slug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"COSMIC_READ_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-read-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"COSMIC_WRITE_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-write-key"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stdio binary reads credentials from environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;COSMIC_BUCKET_SLUG&lt;/code&gt; (required): your Cosmic bucket slug&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COSMIC_READ_KEY&lt;/code&gt; (required): bucket read key for read operations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COSMIC_WRITE_KEY&lt;/code&gt; (optional): bucket write key for write operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Leave &lt;code&gt;COSMIC_WRITE_KEY&lt;/code&gt; out entirely for a read-only server. You can also install it globally with &lt;code&gt;npm install -g @cosmicjs/mcp&lt;/code&gt; instead of resolving it through &lt;code&gt;npx&lt;/code&gt; each time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Verify the connection
&lt;/h2&gt;

&lt;p&gt;Restart Claude Code and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;cosmic&lt;/code&gt; listed with its tools. Then confirm it can actually reach your bucket by asking for something only your bucket knows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List all object types in my Cosmic bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code should call &lt;code&gt;cosmic_types_list&lt;/code&gt; and return your real content models. If it returns your object types, the connection is live and correctly authenticated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 18 tools, and when each one fires
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objects
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cosmic_objects_list&lt;/code&gt;: list or search objects, filtered by type, status, and locale, with pagination&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_objects_get&lt;/code&gt;: fetch a single object by ID or slug, with optional metafield, depth, and locale params&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_objects_create&lt;/code&gt;: create a new object with title, slug, status, and metafields (write key required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_objects_update&lt;/code&gt;: update an existing object's title, slug, status, or metafield values (write key required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_objects_delete&lt;/code&gt;: permanently delete an object by ID (write key required)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Media
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cosmic_media_list&lt;/code&gt;: list media files, optionally scoped to a folder&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_media_get&lt;/code&gt;: fetch metadata and the imgix URL for a single file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_media_upload&lt;/code&gt;: upload from a URL or base64 payload into the media library (write key required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_media_delete&lt;/code&gt;: delete a media file (write key required)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Object Types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cosmic_types_list&lt;/code&gt;: list every object type in the bucket&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_types_get&lt;/code&gt;: fetch the full schema for one object type, including metafields, options, and helper text&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_types_create&lt;/code&gt;: create a new object type with a metafield schema (write key required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_types_update&lt;/code&gt;: update a type's schema or metafield definitions (write key required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_types_delete&lt;/code&gt;: delete an object type and all its objects (write key required)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI Generation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cosmic_ai_generate_text&lt;/code&gt;: generate text with optional context pulled from existing objects in your bucket&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_ai_generate_image&lt;/code&gt;: generate an image and store it in the media library (write key required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_ai_generate_video&lt;/code&gt;: generate video with Google Veo and store it in the media library (write key required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_ai_generate_audio&lt;/code&gt;: generate narration via OpenAI TTS, 13 voices available, stored in the media library (write key required)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two tools worth calling out for agent work are &lt;code&gt;cosmic_types_list&lt;/code&gt; and &lt;code&gt;cosmic_types_get&lt;/code&gt;. An agent that reads your schema before writing produces valid metafields on the first attempt instead of guessing key names and failing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read-only vs full access
&lt;/h2&gt;

&lt;p&gt;This is the part to get right before you point an agent at a production bucket.&lt;/p&gt;

&lt;p&gt;With a read-only token, every write tool is blocked with a clear error message and read tools work as normal. Specifically, the blocked set is every &lt;code&gt;*_create&lt;/code&gt;, &lt;code&gt;*_update&lt;/code&gt;, and &lt;code&gt;*_delete&lt;/code&gt; tool, plus all four AI generation tools, since each of those writes generated assets into your media library.&lt;/p&gt;

&lt;p&gt;So a read-only setup still lets Claude Code explore your content models, read every object, and reason about your content while it writes application code. It just cannot mutate anything. That is a good default for a first session against real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;With the server connected, these are all single prompts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List all blog posts in my Cosmic bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a new blog post titled "Getting Started with MCP" with the content
"This is an introduction to the Model Context Protocol..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update the blog post with ID "abc123" to change its status to published
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me all images in the "blog-images" folder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a new object type called "Products" with fields for name, price,
description, and image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate audio narration of "Welcome to Cosmic CMS" using the "nova" voice
and upload it to my media library
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema management case is the one developers tend to underestimate. Modeling content is usually a dashboard task. Through MCP it becomes something you can do from the same prompt where you are scaffolding the components that will consume it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent scope: when the human has no Cosmic account yet
&lt;/h2&gt;

&lt;p&gt;The hosted endpoint exposes a second, smaller scope at &lt;code&gt;https://mcp.cosmicjs.com/v1/agent&lt;/code&gt; for the agent signup flow. It lets an AI agent provision a brand new Cosmic project and bucket on behalf of someone who does not have an account, without leaving the MCP transport. It exposes three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cosmic_agent_signup&lt;/code&gt; (no auth): creates an unclaimed project and bucket tied to a &lt;code&gt;human_email&lt;/code&gt;. Returns the &lt;code&gt;agent_key&lt;/code&gt;, &lt;code&gt;read_key&lt;/code&gt;, &lt;code&gt;write_key&lt;/code&gt;, and a &lt;code&gt;claim_url&lt;/code&gt;. Cosmic emails the human a 6-digit OTP.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_agent_verify&lt;/code&gt; (requires &lt;code&gt;agent_key&lt;/code&gt;): submits the OTP, lifts restricted-mode limits, and enables AI generation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cosmic_agent_status&lt;/code&gt; (requires &lt;code&gt;agent_key&lt;/code&gt;): checks claim status, remaining limits, and recovers the bucket keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New buckets start in restricted mode: no AI credits, a maximum of 50 objects, and a 5 MB media cap. Unclaimed projects are hard-deleted after 14 days.&lt;/p&gt;

&lt;p&gt;The bucket-scoped tools listed earlier are not available on the agent endpoint, and the agent tools are not available on the bucket endpoint. A single conversation often uses both: the agent signs the human up, captures the returned bucket keys, then switches to the bucket scope to start creating content.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP server vs Agent Skills
&lt;/h2&gt;

&lt;p&gt;Cosmic offers two things that sound similar and do different jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP server&lt;/strong&gt; is for direct content management. It answers "list my blog posts." The AI calls tools that operate on your bucket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Skills&lt;/strong&gt; is for code generation guidance. It answers "build a blog with Cosmic." The AI writes application code using the SDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use both. Agent Skills helps Claude Code write code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createBucketClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cosmicjs/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBucketClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bucketSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-bucket-slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;readKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-read-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cosmic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog-posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP server then lets the same session manage the content that code renders. One tool writes the app, the other operates the data behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails for production buckets
&lt;/h2&gt;

&lt;p&gt;Four habits worth adopting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read key first.&lt;/strong&gt; Connect with a read-only token for your first few sessions. Add the write key when you have seen what the agent actually does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experiment in a separate bucket.&lt;/strong&gt; Bucket allowances scale with your plan: Free includes 1, Builder ($49/month) includes 2, Team ($299/month) includes 3, and Business ($499/month) includes 5. Point destructive experiments at a bucket you do not mind losing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat the delete tools as manual-approval only.&lt;/strong&gt; &lt;code&gt;cosmic_objects_delete&lt;/code&gt;, &lt;code&gt;cosmic_media_delete&lt;/code&gt;, and especially &lt;code&gt;cosmic_types_delete&lt;/code&gt; are permanent, and deleting an object type takes all of its objects with it. Never let an agent call these speculatively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mind your seats.&lt;/strong&gt; Plans include a set number of team members (Free 2, Builder 3, Team 5, Business 10) and additional users are $29/user/month, so decide who needs dashboard access rather than adding everyone by default.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The server does not appear in &lt;code&gt;/mcp&lt;/code&gt;.&lt;/strong&gt; Confirm &lt;code&gt;.mcp.json&lt;/code&gt; is valid JSON at the repository root and restart Claude Code. Some Claude Code versions want the transport named explicitly, so if a hosted config still will not connect, try adding &lt;code&gt;"type": "http"&lt;/code&gt; alongside &lt;code&gt;url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;npx&lt;/code&gt; cannot find the package.&lt;/strong&gt; The package name is &lt;code&gt;@cosmicjs/mcp&lt;/code&gt;, scoped, including the &lt;code&gt;@&lt;/code&gt;. Verify Node is installed and on your PATH.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write tools return an error but reads work.&lt;/strong&gt; Your bearer token is missing the write key. Check that the header is &lt;code&gt;Bearer READ_KEY:WRITE_KEY&lt;/code&gt; with a colon and no spaces, or send the write key via &lt;code&gt;X-Cosmic-Write-Key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;404 from the hosted endpoint.&lt;/strong&gt; The bucket slug in the URL is wrong. Copy it again from &lt;strong&gt;Settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;API Access&lt;/strong&gt;, since the slug is not always identical to your project's display name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools connect but return nothing.&lt;/strong&gt; Confirm you are pointed at the bucket you think you are. Ask Claude Code to run &lt;code&gt;cosmic_types_list&lt;/code&gt; and compare the result against the dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Start with the hosted endpoint and a read-only token. Ask Claude Code to list your object types, then ask it to summarize the content in your bucket. Once that works, add the write key and let it draft something. The full tool reference and per-client config paths live in the &lt;a href="https://www.cosmicjs.com/docs/mcp-server?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=connect-claude-code-to-cms-with-mcp" rel="noopener noreferrer"&gt;MCP server documentation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it yourself.&lt;/strong&gt; Cosmic is an AI-powered headless CMS with a REST API, a TypeScript SDK, and a hosted MCP server. &lt;a href="https://app.cosmicjs.com/signup?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=connect-claude-code-to-cms-with-mcp" rel="noopener noreferrer"&gt;Create a free account&lt;/a&gt; and connect Claude Code in about five minutes. Evaluating Cosmic for a team? &lt;a href="https://calendly.com/tonyspiro/cosmic-intro" rel="noopener noreferrer"&gt;Book a call with Tony&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.cosmicjs.com/blog/connect-claude-code-to-cms-with-mcp?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=connect-claude-code-to-cms-with-mcp" rel="noopener noreferrer"&gt;Cosmic blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
