DEV Community

Cover image for Stop Fighting Sanity's Portable Text - Here's How to Use Markdown Instead
Setasena Randata
Setasena Randata

Posted on

Stop Fighting Sanity's Portable Text - Here's How to Use Markdown Instead

Let me tell you about the worst hour of my Tuesday.

I wrote a 2,000-word blog post in Notion. Beautiful formatting, perfect headings, code blocks, the works. Then I tried to publish it to my Sanity CMS.

One hour later, I was manually converting this:

## How to Use the API

Simply call the `fetchData()` function:

\`\`\`javascript
const data = await fetchData()
\`\`\`
Enter fullscreen mode Exit fullscreen mode

Into this monstrosity:

{
  _type: 'block',
  style: 'h2',
  children: [{ _type: 'span', text: 'How to Use the API' }]
},
{
  _type: 'block',
  children: [
    { _type: 'span', text: 'Simply call the ' },
    { _type: 'span', text: 'fetchData()', marks: ['code'] },
    { _type: 'span', text: ' function:' }
  ]
},
{
  _type: 'code',
  language: 'javascript',
  code: 'const data = await fetchData()'
}
Enter fullscreen mode Exit fullscreen mode

I literally said out loud: "There has to be a better way."

Turns out, there is.

Why Portable Text Makes You Want to Scream

Don't get me wrong - Sanity is fantastic. The headless CMS architecture is brilliant, the API is clean, and the real-time collaboration is πŸ”₯.

But Portable Text? It's like they said:

"You know what developers love? Converting their perfectly good Markdown into deeply nested JSON objects by hand."

Nobody. Nobody said that.

The problem is that Portable Text is structured content. It's designed to be platform-agnostic, queryable, and transformable. Which is great! Until you actually have to write content in it.

The Solution: Markdown Plugin

Here's the good news: Sanity has a markdown plugin. Here's the bad news: nobody talks about it.

Let me show you exactly how to set it up in your Sanity project.

Step 1: Install the Plugin

cd your-sanity-studio

# Install the markdown plugin
npm install sanity-plugin-markdown easymde@2
Enter fullscreen mode Exit fullscreen mode

That's it for installation. Now for configuration.

Step 2: Update Your Sanity Config

Open your sanity.config.ts and add the markdown schema to your plugins:

import { defineConfig } from 'sanity'
import { structureTool } from 'sanity/structure'
import { markdownSchema } from 'sanity-plugin-markdown'

export default defineConfig({
  name: 'default',
  title: 'Your Project',

  projectId: 'your-project-id',
  dataset: 'production',

  plugins: [
    structureTool(),
    markdownSchema(), // πŸ‘ˆ Add this line
  ],

  schema: {
    types: [...yourSchemas],
  },
})
Enter fullscreen mode Exit fullscreen mode

Step 3: Add Markdown Field to Your Schema

Now update your post schema to include a markdown content field:

import { defineField, defineType } from 'sanity'

export const postType = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      type: 'slug',
      options: { source: 'title' },
    }),
    // 🎯 The magic field
    defineField({
      name: 'content',
      title: 'Content',
      type: 'markdown',
      description: 'Write your content in beautiful Markdown',
    }),
  ],
})
Enter fullscreen mode Exit fullscreen mode

Step 4: Style the Editor (Optional but Recommended)

The default markdown editor is... functional. Let's make it nice:

# Install CSS dependencies
npm install easymde@2
Enter fullscreen mode Exit fullscreen mode

Create sanity.css in your studio root:

@import 'easymde/dist/easymde.min.css';

/* Make it look less like 2010 */
.EasyMDEContainer .CodeMirror {
  border-radius: 8px;
  border-color: #e2e8f0;
  font-family: 'JetBrains Mono', 'Fira Code', monospace;
  font-size: 14px;
  line-height: 1.6;
}

.EasyMDEContainer .editor-toolbar {
  border-color: #e2e8f0;
  border-radius: 8px 8px 0 0;
  background: #f8fafc;
}

.EasyMDEContainer .editor-toolbar button {
  color: #475569;
}

.EasyMDEContainer .editor-toolbar button:hover {
  background: #e2e8f0;
  border-color: #cbd5e1;
}
Enter fullscreen mode Exit fullscreen mode

Import it in your studio's main file.

What You Get

After this setup, your Sanity Studio has a proper markdown editor with:

βœ… Live preview side-by-side
βœ… Toolbar for formatting (no more memorizing markdown syntax)
βœ… Code block support with syntax highlighting
βœ… Image insertion
βœ… Table support
βœ… Full keyboard shortcuts

And here's the best part: it stores actual markdown text, not Portable Text JSON.

Rendering Markdown on Your Frontend

Now when you query your Sanity content, you get clean markdown strings:

// What you get from Sanity
const post = {
  title: "My Post",
  content: "## Introduction\n\nThis is **markdown**!"
}
Enter fullscreen mode Exit fullscreen mode

To render it in your Next.js/React app:

npm install react-markdown remark-gfm rehype-raw rehype-sanitize
Enter fullscreen mode Exit fullscreen mode
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'

export default function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <ReactMarkdown
        remarkPlugins={[remarkGfm]}
        className="prose prose-lg"
      >
        {post.content}
      </ReactMarkdown>
    </article>
  )
}
Enter fullscreen mode Exit fullscreen mode

Boom. Clean, simple, no Portable Text serializers required.

Common Gotchas

Gotcha #1: "Unknown type: markdown"

If you see this error, you forgot to add markdownSchema() to your plugins array in sanity.config.ts.

Gotcha #2: Editor looks broken

Make sure you're importing the CSS. The easymde styles are required:

import 'easymde/dist/easymde.min.css'
Enter fullscreen mode Exit fullscreen mode

Gotcha #3: Images not working

Markdown image syntax expects URLs:

![Alt text](https://example.com/image.jpg)
Enter fullscreen mode Exit fullscreen mode

If you want to use Sanity's image CDN, you'll need to either:

  1. Upload images separately and use the CDN URL
  2. Stick with Portable Text for the image field
  3. Use a mixed approach (markdown for content, separate image fields)

Why This Matters

Look, I get it. Portable Text is "the right way" to do structured content. But sometimes you just want to write a damn blog post without feeling like you're programming JSON.

Markdown gives you:

  • Speed - Write content at the speed of thought
  • Portability - Works everywhere, from GitHub to Notion
  • Simplicity - Everyone knows markdown
  • Less friction - Copy-paste actually works

Is it perfect? No. But it's a hell of a lot better than manually building nested block objects.

The Full Working Example

I've set up a demo repo with a complete working Sanity Studio using markdown:

# Clone the example
git clone https://github.com/yourusername/sanity-markdown-example
cd sanity-markdown-example

# Install and run
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

Schema included, styles configured, ready to customize.

But Wait - There's an Even Better Way

Here's where I drop the pitch.

If you're:

  • Writing lots of blog content
  • Managing multiple Sanity projects
  • Doing content work for clients
  • Tired of the content β†’ CMS workflow

You might want to check out Terradium.

It's an AI-powered content platform I built that:

  1. Generates SEO-optimized content using multi-agent AI
  2. Automatically publishes to Sanity in markdown format
  3. Handles the entire workflow from research to publishing

Here's the workflow:

You: "Write 5 blog posts about Next.js 15 features"
     ↓
Terradium AI:
  β†’ Researches keywords
  β†’ Generates outlines
  β†’ Writes full articles
  β†’ Optimizes for SEO
  β†’ Publishes to your Sanity project
     ↓
You: Review and click publish (or let it auto-publish)
Enter fullscreen mode Exit fullscreen mode

Time saved: ~5 hours per article
Cost: Transparent "electron" credits (~$0.50 per full article)
Result: Professional content in your Sanity CMS, ready to review

The free tier gives you 30 electrons (~5 full articles) to try it out. No credit card required.

Why I Built This

After manually creating hundreds of blog posts for clients, I realized:

  1. The research phase is 80% the same every time
  2. SEO optimization follows predictable patterns
  3. Markdown β†’ Sanity conversion should be automated
  4. The "publish to CMS" step is pure busywork

So I automated all of it with AI agents.

Now I use Terradium to:

  • Generate client blog content in bulk
  • Create documentation faster
  • Populate demo projects with real content
  • Test Sanity schemas with actual articles

It integrates directly with your Sanity project (using the markdown setup we just configured!) and handles everything from keyword research to final publishing.

Try It Out

If you just spent an hour following this tutorial to set up markdown support, you're exactly the person who would benefit from Terradium.

πŸ‘‰ Try Terradium Free - 30 electrons included, no credit card

Use your newly configured Sanity + Markdown setup, connect your project credentials, and generate your first AI-powered blog post in ~10 minutes.

Wrapping Up

Whether you use the markdown plugin manually or automate it with Terradium, the key takeaway is:

You don't have to fight Portable Text.

Markdown is a perfectly valid option for Sanity CMS. It's faster to write, easier to migrate, and frankly, way more pleasant to work with.

Set it up once, and never manually convert content again.

Questions?

Drop them in the comments! I'm happy to help with:

  • Sanity + markdown setup issues
  • Frontend rendering questions
  • Migration from Portable Text to markdown
  • Terradium integration

And if you found this helpful, consider giving Terradium a try. The free tier is genuinely useful (I'm not just saying that - it's 10 full articles).


TL;DR: Sanity's Portable Text is pain. Install sanity-plugin-markdown, add markdownSchema() to your config, use type: 'markdown' in your schema. Write content like a normal person. Or automate the whole thing with Terradium and never write manually again.

Happy coding! πŸš€

Top comments (0)