Sanity CMS Setup Guide

Set up your Sanity CMS project with markdown support for seamless content publishing from Terradium. This guide will help you configure your Sanity Studio with the required schemas and markdown compatibility.

Important: Markdown Support Required

For the best experience with Terradium, you'll need to implement markdown support in your Sanity Studio. Terradium's AI generates content in markdown format, which provides richer formatting and better content structure.

Prerequisites

  • A Sanity account (create one here)
  • Node.js 16+ installed on your development machine
  • Basic knowledge of React/Next.js and TypeScript
  • Sanity CLI installed globally: npm install -g @sanity/cli

Project Setup

1. Create a New Sanity Project

sanity init

# Choose options:
# ✓ Create new project
# ✓ Your project name
# ✓ Use the default dataset configuration (Yes)
# ✓ Dataset name: production
# ✓ Output path: ./sanity-studio
# ✓ Select project template: Clean project with no predefined schemas

2. Install Required Dependencies

cd sanity-studio

# Core dependencies for markdown support
npm install sanity-plugin-markdown easymde@2

# Frontend dependencies (if using Next.js)
npm install react-markdown remark-gfm rehype-raw rehype-sanitize rehype-highlight highlight.js

# Utility dependencies for markdown processing
npm install remark strip-markdown unified

3. Get Your Project Credentials

You'll need these values for Terradium:

  • Project ID: Found in your Sanity dashboard or sanity.cli.ts
  • Dataset: Usually "production" (default)
  • API Token: Create one in your Sanity dashboard with read/write permissions

Creating an API Token

  1. Go to your Sanity dashboard
  2. Select your project
  3. Navigate to Settings → API
  4. Create a new token with "Editor" permissions
  5. Copy the token immediately (it won't be shown again)

Required Schemas

Terradium requires specific schemas to publish content. Copy these schema definitions to your Sanity Studio:

Post Schema

Create schemas/postType.ts:

import { DocumentTextIcon } from '@sanity/icons'
import { defineField, defineType } from 'sanity'

export const postType = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  icon: DocumentTextIcon,
  fields: [
    defineField({
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'author',
      title: 'Author',
      type: 'reference',
      to: { type: 'author' },
    }),
    defineField({
      name: 'mainImage',
      title: 'Main image',
      type: 'image',
      options: {
        hotspot: true,
      },
      fields: [
        {
          name: 'alt',
          type: 'string',
          title: 'Alternative Text',
        },
      ],
    }),
    defineField({
      name: 'categories',
      title: 'Categories',
      type: 'array',
      of: [{ type: 'reference', to: { type: 'category' } }],
    }),
    defineField({
      name: 'publishedAt',
      title: 'Published at',
      type: 'datetime',
    }),
    defineField({
      name: 'excerpt',
      title: 'Excerpt',
      type: 'text',
      description: 'Brief description of the post for previews and SEO',
      rows: 4,
    }),
    defineField({
      name: 'metaDescription',
      title: 'Meta Description',
      type: 'text',
      description: 'SEO meta description (150-160 characters recommended)',
      validation: (rule) => rule.max(160),
    }),
    defineField({
      name: 'keywords',
      title: 'Keywords',
      type: 'array',
      of: [{ type: 'string' }],
      description: 'SEO keywords for this post',
      options: {
        layout: 'tags',
      },
    }),
    defineField({
      name: 'estimatedReadingTime',
      title: 'Estimated Reading Time (minutes)',
      type: 'number',
      description: 'Estimated reading time in minutes',
    }),
    // Markdown content field (REQUIRED for Terradium)
    defineField({
      name: 'markdownContent',
      title: 'Markdown Content',
      type: 'markdown',
      description: 'Content in Markdown format - populated by Terradium',
      validation: (rule) => rule.required(),
    }),
  ],

  preview: {
    select: {
      title: 'title',
      author: 'author.name',
      media: 'mainImage',
    },
    prepare(selection) {
      const { author } = selection
      return { ...selection, subtitle: author && `by ${author}` }
    },
  },
})

Author Schema

Create schemas/authorType.ts:

import { UserIcon } from '@sanity/icons'
import { defineField, defineType } from 'sanity'

export const authorType = defineType({
  name: 'author',
  title: 'Author',
  type: 'document',
  icon: UserIcon,
  fields: [
    defineField({
      name: 'name',
      title: 'Name',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'name',
        maxLength: 96,
      },
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'image',
      title: 'Image',
      type: 'image',
      options: {
        hotspot: true,
      },
      fields: [
        {
          name: 'alt',
          type: 'string',
          title: 'Alternative Text',
        }
      ]
    }),
    defineField({
      name: 'bio',
      title: 'Bio',
      type: 'text',
    }),
  ],

  preview: {
    select: {
      title: 'name',
      media: 'image',
    },
  },
})

Category Schema

Create schemas/categoryType.ts:

import { TagIcon } from '@sanity/icons'
import { defineField, defineType } from 'sanity'

export const categoryType = defineType({
  name: 'category',
  title: 'Category',
  type: 'document',
  icon: TagIcon,
  fields: [
    defineField({
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'description',
      title: 'Description',
      type: 'text',
    }),
  ],
})

Schema Index

Update your schemas/index.ts:

import { postType } from './postType'
import { authorType } from './authorType'
import { categoryType } from './categoryType'
export const schema = {
  types: [postType, authorType, categoryType],
}

Adding Markdown Support

To support Terradium's markdown content, you need to add the markdown plugin to your Sanity configuration.

1. Update Sanity Configuration

Update your sanity.config.ts:

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

import { schema } from './schemas'

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

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

  plugins: [
    structureTool(),
    visionTool(),
    markdownSchema(), // Add this line
  ],

  schema,
})

2. Create Custom Markdown Input (Optional)

For enhanced editor experience, create components/CustomMarkdownInput.tsx:

'use client'

import { useMemo } from 'react'
import { MarkdownInput } from 'sanity-plugin-markdown'

export function CustomMarkdownInput(props: any) {
  const reactMdeProps = useMemo(() => {
    return {
      options: {
        toolbar: [
          'bold', 'italic', 'heading', 'strikethrough', '|',
          'quote', 'unordered-list', 'ordered-list', '|',
          'link', 'image', 'table', 'code', '|',
          'preview', 'side-by-side', 'fullscreen', '|',
          'guide'
        ],
        spellChecker: true,
        status: ['lines', 'words', 'cursor'],
        placeholder: 'Content will be populated by Terradium...',
      },
    }
  }, [])

  return <MarkdownInput {...props} reactMdeProps={reactMdeProps} />
}

3. Add CSS for Markdown Editor

Import the required CSS in your studio. Create sanity.css:

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

/* Customize the markdown editor */
.EasyMDEContainer .CodeMirror {
  border-radius: 4px;
  border-color: #e2e8f0;
}

.EasyMDEContainer .editor-toolbar {
  border-color: #e2e8f0;
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
}

Setting Up Webhooks (Optional)

If you're using Sanity with a Next.js frontend, set up webhooks for real-time updates:

1. Create Webhook in Sanity Dashboard

  1. 1. Go to your Sanity project dashboard
  2. 2. Navigate to Settings → API → Webhooks
  3. 3. Click "Create webhook"
  4. 4. Set URL to: https://yourdomain.com/api/revalidate
  5. 5. Set trigger to: Document changes
  6. 6. Select dataset: production
  7. 7. Add a secret (save this for your environment variables)

2. Environment Variables

Add these to your .env.local file:

NEXT_PUBLIC_SANITY_PROJECT_ID=your-project-id
NEXT_PUBLIC_SANITY_DATASET=production
NEXT_PUBLIC_SANITY_API_VERSION=2024-08-26
SANITY_WEBHOOK_SECRET=your-webhook-secret

What Terradium Sends to Your Sanity Project

When Terradium publishes content to your Sanity project, it will populate the following fields in your post documents:

Required Fields

  • title - Article title
  • slug - URL-friendly slug
  • markdownContent - Full article content in Markdown
  • excerpt - Brief description for previews
  • publishedAt - Publication timestamp

SEO & Metadata Fields

  • metaDescription - SEO meta description
  • keywords - Array of SEO keywords
  • estimatedReadingTime - Reading time in minutes
  • readingTime - Reading time metadata
  • wordCount - Word count metadata

Optional Reference Fields (if configured)

  • author - Reference to author document (if default author configured)
  • categories - Reference to category document (if default category configured)

Important Notes

  • • Terradium uses Markdown format only - no Portable Text is sent
  • • All content is generated as raw Markdown text in the markdownContent field
  • • Images referenced in Markdown should be hosted externally or uploaded to Sanity separately
  • • Make sure your markdownContent field has validation: (rule) => rule.required()

Troubleshooting

Common Issues

Error: "Unknown type: markdown"

Solution: Make sure you've installed sanity-plugin-markdown and added markdownSchema() to your sanity.config.ts plugins array.

Error: "Session not found" or "Invalid token"

Solution: Generate a new API token in your Sanity dashboard with "Editor" permissions. Make sure the token hasn't expired.

Content not appearing in Terradium

Solution: Check that your post schema includes all required fields: title, slug, markdownContent. Also verify your API token has read permissions.

Publishing fails from Terradium

Solution: Ensure your API token has write permissions and that all required schemas (post, author, category) are properly defined.

Verification Checklist

  • Sanity project created with correct project ID
  • All required schemas (post, author, category) defined
  • Markdown plugin installed and configured
  • API token created with "Editor" permissions
  • Post schema includes markdownContent field
  • At least one author and category created
  • Sanity Studio deployed and accessible

Need Help?

If you encounter issues setting up your Sanity project, here are some resources: