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.
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.
npm install -g @sanity/cli
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
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
You'll need these values for Terradium:
sanity.cli.ts
Terradium requires specific schemas to publish content. Copy these schema definitions to your Sanity Studio:
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}` }
},
},
})
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',
},
},
})
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',
}),
],
})
Update your schemas/index.ts
:
import { postType } from './postType'
import { authorType } from './authorType'
import { categoryType } from './categoryType'
export const schema = {
types: [postType, authorType, categoryType],
}
To support Terradium's markdown content, you need to add the markdown plugin to your 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,
})
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} />
}
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;
}
If you're using Sanity with a Next.js frontend, set up webhooks for real-time updates:
https://yourdomain.com/api/revalidate
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
When Terradium publishes content to your Sanity project, it will populate the following fields in your post documents:
title
- Article titleslug
- URL-friendly slugmarkdownContent
- Full article content in Markdownexcerpt
- Brief description for previewspublishedAt
- Publication timestampmetaDescription
- SEO meta descriptionkeywords
- Array of SEO keywordsestimatedReadingTime
- Reading time in minutesreadingTime
- Reading time metadatawordCount
- Word count metadataauthor
- Reference to author document (if default author configured)categories
- Reference to category document (if default category configured)markdownContent
fieldmarkdownContent
field has validation: (rule) => rule.required()
Solution: Make sure you've installed sanity-plugin-markdown
and added markdownSchema()
to your sanity.config.ts plugins array.
Solution: Generate a new API token in your Sanity dashboard with "Editor" permissions. Make sure the token hasn't expired.
Solution: Check that your post schema includes all required fields: title, slug, markdownContent. Also verify your API token has read permissions.
Solution: Ensure your API token has write permissions and that all required schemas (post, author, category) are properly defined.
markdownContent
fieldIf you encounter issues setting up your Sanity project, here are some resources: