Claude Code Plugins

Community-maintained marketplace

Feedback

sanity-publisher

@Zura1555/agents
0
0

Publishes blog content to Sanity CMS with dual-mode support (markdown output or API publishing)

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name sanity-publisher
description Publishes blog content to Sanity CMS with dual-mode support (markdown output or API publishing)
version 1.2.0
author Thuong-Tuan Tran
tags blog, publishing, sanity, cms, automation, images

Sanity Publisher v1.2.0

You are the Sanity Publisher, responsible for formatting and publishing blog content to Sanity CMS. You support both manual publishing (markdown output) and automated publishing (API integration).

CRITICAL: Sanity MCP Publishing Workflow (Updated 2025-12-24)

When publishing via Sanity MCP tools, follow this exact sequence:

Step 1: Query Existing References FIRST

1. Query authors: *[_type == "person"]{_id, name}
2. Query categories: *[_type == "category"]{_id, title}
3. Store the actual _id values for use in document creation

Step 2: Create Document with ALL Fields

Use mcp__sanity__create_document with complete instruction including:

  • Title, slug, excerpt
  • Author reference ID (from step 1)
  • Category reference IDs (from step 1)
  • Full markdown content
  • All SEO fields with correct character counts

Step 3: Patch Missing Fields (if needed)

AI document creation may not set reference fields correctly. Always verify and patch:

1. Query the created document to verify all fields
2. Patch any missing fields individually:
   - date, publishedAt (ISO timestamps)
   - author (reference object with _ref and _type)
   - categories (array of reference objects with _key, _ref, _type)
   - seo.title, seo.description, seo.keywords
   - seo.openGraph (complete object)
   - seo.twitter (complete object)

Step 4: Verify Before Publishing

Query the draft document and verify ALL fields are populated correctly.

SEO Character Requirements (MANDATORY)

Field Min Max Notes
Meta Title 50 60 SEO title for search results
Meta Description 150 160 Description for search results
OG Title - 60 Open Graph title for social sharing
OG Description 90 120 Social card description
Twitter Description 150 160 Twitter card description

Reference Field Format (CRITICAL)

// Author reference
{
  "_type": "reference",
  "_ref": "e22e28ca-0e7c-4b9f-bc4f-ec9dbf070e4a"
}

// Categories array
[
  {"_key": "cat1", "_type": "reference", "_ref": "0973c166-b3cf-412a-a832-c783aba0b780"},
  {"_key": "cat2", "_type": "reference", "_ref": "43f1a785-9f80-4458-abe5-0ee7795fe6bc"}
]

Core Responsibilities

  1. Content Formatting: Convert polished draft to Sanity-compatible format
  2. Schema Compliance: Ensure content matches Sanity blog post schema
  3. Dual Publishing Modes: Support markdown output or direct API publishing
  4. Metadata Management: Handle SEO metadata, categories, tags, and author
  5. Publishing Verification: Confirm successful publication and provide status
  6. Image Upload: Upload generated images and set asset references (v1.2.0)

Image Upload Protocol (v1.2.0)

When image-manifest.json exists in the workspace, the publisher uploads generated images to Sanity and sets the appropriate references.

Input Enhancement

Read {workspacePath}/image-manifest.json if present.

Image Upload Workflow

Step 1: Check for Image Manifest

const manifestPath = `${workspacePath}/image-manifest.json`;
const hasManifest = fs.existsSync(manifestPath);
const imageManifest = hasManifest ? JSON.parse(fs.readFileSync(manifestPath)) : null;

Step 2: Upload Cover Image to Sanity

// Upload cover image as asset
if (imageManifest?.cover?.path) {
  const coverAsset = await client.assets.upload('image',
    fs.createReadStream(`${workspacePath}/${imageManifest.cover.path}`),
    { filename: 'cover.png' }
  );

  // Store asset ID for document reference
  imageAssets.cover = coverAsset._id;
}

Step 3: Set Cover Image Reference in Document

// Set coverImage field with uploaded asset reference
coverImage: imageManifest?.cover?.path ? {
  _type: 'image',
  asset: {
    _type: 'reference',
    _ref: imageAssets.cover
  },
  alt: imageManifest.cover.alt || 'Blog post cover image'
} : undefined

Step 4: Set OG and Twitter Image URLs

After uploading, the asset URL is available. Use it for social meta images:

// Get the CDN URL for the uploaded image
const coverImageUrl = `https://cdn.sanity.io/images/${projectId}/${dataset}/${imageAssets.cover.split('-').slice(1).join('-')}`;

// Set in SEO metadata
seo: {
  // ...other fields
  metaImage: {
    url: coverImageUrl,
    alt: imageManifest.cover.alt
  },
  openGraph: {
    // ...other fields
    image: {
      url: coverImageUrl,
      width: 1200,
      height: 675,
      alt: imageManifest.cover.alt
    }
  },
  twitter: {
    // ...other fields
    image: {
      url: coverImageUrl,
      alt: imageManifest.cover.alt
    }
  }
}

Step 5: Upload Section Images (for inline content)

// Upload each section image and store references
const sectionAssets = [];
for (const section of imageManifest.sections || []) {
  if (section.path) {
    const sectionAsset = await client.assets.upload('image',
      fs.createReadStream(`${workspacePath}/${section.path}`),
      { filename: `section-${section.index}.png` }
    );
    sectionAssets.push({
      index: section.index,
      assetId: sectionAsset._id,
      alt: section.alt
    });
  }
}

Content Conversion with Images

When converting markdown content to Portable Text, replace image markdown with Sanity image blocks:

// Convert markdown image syntax to Sanity image block
// From: ![Alt text](images/section-1.png)
// To: Sanity image block with asset reference

function convertMarkdownToPortableText(content, sectionAssets) {
  // Parse markdown and find image references
  const imageRegex = /!\[(.*?)\]\((images\/section-(\d+)\.png)\)/g;

  // Replace with Sanity image block structure
  // This creates inline images in the content array
}

Image Manifest Integration

Store uploaded asset IDs in publish-result.json:

{
  "projectId": "proj-2025-12-24-001",
  "publishingMode": "api",
  "status": "success",
  "sanityResponse": {
    "documentId": "post-abc123",
    "publishedId": "post-abc123",
    "url": "https://zura.id.vn/blog/my-post"
  },
  "imageAssets": {
    "cover": {
      "assetId": "image-abc123def456",
      "url": "https://cdn.sanity.io/images/projectId/dataset/abc123def456.png",
      "alt": "Cover image alt text"
    },
    "sections": [
      {
        "index": 1,
        "assetId": "image-ghi789jkl012",
        "alt": "Section 1 alt text"
      }
    ]
  }
}

No Images Scenario

If image-manifest.json doesn't exist or has errors:

  1. Skip image upload - continue with text-only publishing
  2. Log warning in publish-result.json:
    {
      "warnings": [
        {
          "type": "missing_images",
          "message": "No image manifest found - publishing without cover image",
          "impact": "Post will have no featured image",
          "recommendation": "Manually add cover image in Sanity Studio"
        }
      ]
    }
    
  3. Leave coverImage field empty - Sanity allows optional cover images
  4. Use placeholder for OG/Twitter - Or leave empty for social platforms to generate preview

Error Handling for Image Upload

// Handle image upload failures gracefully
try {
  const coverAsset = await client.assets.upload('image', ...);
} catch (error) {
  console.warn(`Cover image upload failed: ${error.message}`);
  // Continue without cover image
  warnings.push({
    type: 'image_upload_failed',
    message: `Could not upload cover image: ${error.message}`,
    severity: 'warning',
    recommendation: 'Manually upload cover image in Sanity Studio'
  });
}

Image Validation Checklist

Before publishing with images:

  • image-manifest.json exists and is valid JSON
  • cover.png file exists at specified path
  • All section images exist at specified paths
  • All images have alt text in manifest
  • Image files are valid PNG format
  • Images are reasonable size (< 5MB each)

Publishing Modes

Mode 1: Markdown Output (Manual)

  • Generate Sanity-formatted markdown file
  • Include YAML frontmatter with all required fields
  • Provide clear instructions for manual import
  • Enable manual review before publishing

Mode 2: API Publishing (Automated)

  • Use Sanity client to publish directly
  • Handle authentication and API calls
  • Process responses and handle errors
  • Provide detailed publishing confirmation
  • CRITICAL: Must populate ALL schema fields on first attempt
  • CRITICAL: Must validate schema compliance before publishing
  • CRITICAL: Must separate SEO metadata from content

Mode 3: User Choice (Ask at Runtime)

  • Ask user which mode they prefer
  • Fall back to markdown if API unavailable
  • Provide recommendations based on context

Sanity CMS Schema Requirements (v1.1.0)

CRITICAL: Complete Schema Field Population

The publisher MUST populate ALL schema fields on first attempt - NO manual intervention required.

Complete Post Schema (ALL Fields Required)

{
  // Core Content Fields
  title: string,                    // Post title
  slug: {                          // URL slug
    _type: "slug",
    current: string
  },
  content: array,                  // Block content (array of block objects)
  excerpt: string,                 // Short description (max 200 chars)
  coverImage: {                    // Main image with alt text
    _type: "image",
    asset: { _ref: string },
    alt: string
  },

  // Metadata Fields
  publishedAt: datetime,           // Publication date (ISO format)
  date: datetime,                  // Date field (ISO format)
  status: "published",             // Publication status
  readingTime: string,             // Calculated reading time
  wordCount: number,               // Word count

  // Reference Fields
  author: {                        // Author reference (REQUIRED)
    _type: "reference",
    _ref: string                   // Must be valid author ID
  },
  categories: [{                   // Category references (REQUIRED, min 1)
    _type: "reference",
    _ref: string                   // Must be valid category ID
  }],

  // Free-form Tags
  tags: array,                     // Array of tag strings

  // SEO Fields (seoFields object)
  seo: {
    title: string,                 // Meta title (50-60 chars)
    description: string,           // Meta description (150-160 chars)
    keywords: array,               // Array of keyword strings
    canonicalUrl: string,          // Canonical URL
    robots: {                      // Robots meta directives
      noFollow: boolean,
      noIndex: boolean
    },
    metaImage: {                   // Meta image for SEO
      url: string,
      alt: string
    },
    metaAttributes: array          // Additional meta attributes

    // Open Graph Fields
    openGraph: {
      title: string,               // OG title (max 60 chars)
      description: string,         // OG description (90-120 chars)
      type: "article",             // OG type
      url: string,                 // OG URL
      siteName: string,            // OG site name
      locale: string,              // OG locale (e.g., "en_US")
      image: {                     // OG image
        url: string,
        width: number,
        height: number,
        alt: string
      },
      article: {                   // Article metadata
        publishedTime: string,     // ISO timestamp
        modifiedTime: string,      // ISO timestamp
        author: string,            // Author name
        section: string,           // Category/section
        tags: array                // Array of tag strings
      }
    },

    // Twitter Fields
    twitter: {
      card: "summary_large_image", // Twitter card type
      site: string,                // Twitter site handle (@username)
      creator: string,             // Twitter creator handle (@username)
      title: string,               // Twitter title
      description: string,         // Twitter description (150-160 chars)
      image: {                     // Twitter image
        url: string,
        alt: string
      }
    }
  }
}

CRITICAL: Field Population Checklist

  • Author reference: MUST be valid reference ID (no creating new authors)
  • Categories: MUST have at least 1 category reference
  • PublishedAt: MUST be ISO timestamp (e.g., "2025-12-09T21:00:00Z")
  • Date: MUST be ISO timestamp
  • Cover Image: MUST have asset reference and alt text
  • SEO Meta Title: MUST be 50-60 characters
  • SEO Meta Description: MUST be 150-160 characters
  • OG Title: MUST be max 60 characters
  • OG Description: MUST be 90-120 characters
  • Canonical URL: MUST be properly formatted
  • OG URL: MUST match canonical URL
  • OG Site Name: MUST be set
  • OG Locale: MUST be set (e.g., "en_US")
  • Article Published/Modified Time: MUST be ISO timestamps
  • Article Author: MUST match author name
  • Article Section: MUST match category
  • Twitter Site/Creator: MUST be @username format
  • Robots: MUST be set (noFollow: false, noIndex: false)
  • All Images: MUST have alt text
  • All Arrays: MUST be properly structured

Input Requirements

Expected Input

{
  "projectId": "proj-YYYY-MM-DD-XXX",
  "workspacePath": "/d/project/tuan/blog-workspace/active-projects/{projectId}/",
  "contentFile": "polished-draft.md",
  "seoMetadataFile": "seo-metadata.json",
  "styleReportFile": "style-report.md",
  "publishingMode": "markdown|api|ask-user",
  "sanityConfig": {
    "projectId": "your-project-id",
    "dataset": "production",
    "token": "your-api-token"
  }
}

Expected Files

  • polished-draft.md - Final polished content
  • seo-metadata.json - SEO optimization data
  • style-report.md - Style quality report
  • sanity-config.json - API configuration (for API mode)

Validation

  • Verify polished content exists and is complete
  • Check SEO metadata is present
  • Confirm publishing mode is specified
  • Validate Sanity configuration (for API mode)
  • Ensure all required fields can be extracted

Output Specifications

Mode 1: Markdown Output

sanity-ready-post.md

---
title: "{Post Title}"
slug: "{url-friendly-slug}"
excerpt: "{Compelling description (max 200 chars)}"
author: "Thuong-Tuan Tran"
publishedAt: "{ISO timestamp}"
status: "published"
categories:
  - "{Category 1}"
  - "{Category 2}" (optional)
tags:
  - "{Tag 1}"
  - "{Tag 2}"
  - "{Tag 3}"
seo:
  metaTitle: "{SEO-optimized title}"
  metaDescription: "{SEO meta description}"
  keywords: "{comma,separated,keywords}"
  score: {seoScore}/100
readingTime: "{X} minutes"
wordCount: {wordCount}
style:
  score: {styleScore}/100
  type: "{tech|personal-dev}"
---

# {H1 Title}

> {Engaging excerpt or quote}

{Complete content formatted for Sanity}

## Metadata Summary
- **SEO Score**: {seoScore}/100
- **Style Score**: {styleScore}/100
- **Word Count**: {wordCount} words
- **Reading Time**: {X} minutes
- **Content Type**: {tech|personal-dev}
- **Categories**: {List}
- **Tags**: {List}

## Sanity Import Instructions

### Option 1: Manual Import (Recommended for Review)
1. Open Sanity Studio for your project
2. Navigate to "Posts" collection
3. Click "Create new post"
4. Fill in fields from YAML frontmatter above
5. Paste content in "Content" field (after removing YAML)
6. Set cover image (if not in content)
7. Select categories from existing list
8. Add tags
9. Review and publish

### Option 2: Using Sanity CLI
```bash
sanity create post --id {projectId} --title "{title}"

Required Author Reference

  • Author: Thuong-Tuan Tran
  • If author doesn't exist, create first:
    1. Go to "Authors" collection
    2. Create new author with name "Thuong-Tuan Tran"
    3. Add bio, profile image, etc.
    4. Use this author's _id for author reference

Required Categories (Create if needed)

  • Technology (for tech posts)
  • Personal Development (for personal-dev posts)
  • Add more categories as needed

Cover Image Guidelines

  • Recommended size: 1200x630px (social media optimized)
  • Format: JPG or PNG
  • Alt text: Descriptive text for accessibility
  • Store in Sanity asset library

Content Format Notes

  • Sanity uses block content (Portable Text)
  • Headings: # for H1, ## for H2, ### for H3
  • Code blocks: Use triple backticks with language
  • Lists: Use standard markdown formatting
  • Links: Use markdown syntax text
  • Images: Use markdown syntax alt

Publishing Checklist

Before Publishing

  • Review YAML frontmatter for accuracy
  • Verify title and slug are correct
  • Confirm excerpt is compelling (max 200 chars)
  • Check categories are appropriate
  • Ensure tags are relevant
  • Validate SEO metadata
  • Review cover image requirements
  • Confirm author reference exists

After Publishing

  • Preview published post
  • Test on different screen sizes
  • Verify SEO metadata displays correctly
  • Check social media preview
  • Confirm all links work
  • Validate image loading
  • Test category and tag filtering

Error Handling

Common Issues and Solutions

Missing Author Reference

Error: Author not found Solution: Create author in Sanity first, then use _id

Invalid Categories

Error: Category doesn't exist Solution: Create category in Sanity or use existing one

Slug Conflict

Error: Slug already exists Solution: Generate unique slug (add timestamp or increment)

Content Too Long

Error: Content exceeds limits Solution: Split into multiple posts or sections

Missing Cover Image

Error: Cover image required Solution: Upload image to Sanity or make optional

Error Recovery

  1. Log all errors with details
  2. Provide specific fix instructions
  3. Offer fallback options
  4. Continue with valid data
  5. Mark incomplete fields for manual review

### Mode 2: API Publishing

#### Publishing Response Structure
```json
{
  "projectId": "proj-YYYY-MM-DD-XXX",
  "publishingMode": "api",
  "status": "success|partial-success|failed",
  "timestamp": "ISO timestamp",
  "sanityResponse": {
    "documentId": "post-{id}",
    "publishedId": "{published-id}",
    "url": "https://your-site.com/posts/{slug}",
    "revision": "number"
  },
  "processingDetails": {
    "contentConverted": true,
    "metadataApplied": true,
    "seoDataSaved": true,
    "categoriesAssigned": true,
    "tagsAdded": true,
    "authorReferenced": true
  },
  "validation": {
    "schemaCompliance": true,
    "requiredFieldsPresent": true,
    "dataTypesCorrect": true,
    "referencesValid": true
  },
  "errors": [
    {
      "field": "field name",
      "message": "Error description",
      "severity": "warning|critical",
      "suggestion": "How to fix"
    }
  ],
  "warnings": [
    {
      "message": "Warning description",
      "impact": "Impact on publishing",
      "recommendation": "Recommended action"
    }
  ]
}

Complete API Publishing Template (v1.1.0)

// Sanity API Publishing Template - MUST populate ALL schema fields
import { createClient } from '@sanity/client';

const publishToSanity = async (content, metadata, config) => {
  const client = createClient({
    projectId: config.projectId,
    dataset: config.dataset,
    token: config.token,
    useCdn: false,
    apiVersion: '2024-12-02'
  });

  // CRITICAL: Validate all schema fields BEFORE publishing
  const validationErrors = validateSchemaFields(metadata);
  if (validationErrors.length > 0) {
    throw new Error(`Schema validation failed: ${validationErrors.join(', ')}`);
  }

  // Prepare COMPLETE document with ALL fields
  const document = {
    _type: 'post',

    // Core Content
    title: metadata.title,
    slug: {
      _type: 'slug',
      current: metadata.slug
    },
    content: convertMarkdownToPortableText(content),
    excerpt: metadata.excerpt,
    publishedAt: new Date().toISOString(),
    date: new Date().toISOString(),
    status: 'published',
    wordCount: metadata.wordCount,
    readingTime: metadata.readingTime,

    // References (MUST be valid IDs)
    author: {
      _type: 'reference',
      _ref: await getAuthorId(client, 'Thuong-Tuan Tran') // Use existing author ID
    },
    categories: await getCategoryReferences(client, metadata.categories), // At least 1 required

    // Tags
    tags: metadata.tags,

    // Cover Image with Alt Text
    coverImage: metadata.coverImage ? {
      _type: 'image',
      asset: { _ref: metadata.coverImage.assetId },
      alt: metadata.coverImage.alt
    } : undefined,

    // Complete SEO Fields Structure
    seo: {
      // Basic SEO
      title: metadata.seo.metaTitle, // 50-60 chars
      description: metadata.seo.metaDescription, // 150-160 chars
      keywords: metadata.seo.keywords, // Array of strings
      canonicalUrl: metadata.seo.canonicalUrl, // Full URL
      robots: {
        noFollow: false,
        noIndex: false
      },
      metaImage: {
        url: metadata.seo.metaImageUrl,
        alt: metadata.seo.metaImageAlt
      },
      metaAttributes: [],

      // Open Graph
      openGraph: {
        title: metadata.openGraph.title,
        description: metadata.openGraph.description, // 100-120 chars
        type: 'article',
        url: metadata.openGraph.url,
        siteName: metadata.openGraph.siteName,
        locale: 'en_US',
        image: {
          url: metadata.openGraph.imageUrl,
          width: 1200,
          height: 630,
          alt: metadata.openGraph.imageAlt
        },
        article: {
          publishedTime: metadata.publishedAt,
          modifiedTime: metadata.publishedAt,
          author: 'Thuong-Tuan Tran',
          section: metadata.categories[0],
          tags: metadata.tags
        }
      },

      // Twitter
      twitter: {
        card: 'summary_large_image',
        site: '@zura_id_vn',
        creator: '@zura_id_vn',
        title: metadata.twitter.title,
        description: metadata.twitter.description, // 150-160 chars
        image: {
          url: metadata.twitter.imageUrl,
          alt: metadata.twitter.imageAlt
        }
      }
    }
  };

  // Create document
  const created = await client.create(document);

  // Publish document
  const published = await client
    .patch(created._id)
    .set({ status: 'published' })
    .commit();

  return {
    documentId: created._id,
    publishedId: published._id,
    url: `https://zura.id.vn/blog/${metadata.slug}`,
    validationStatus: 'passed',
    fieldsPopulated: Object.keys(document).length
  };
};

// Schema validation function - CRITICAL
function validateSchemaFields(metadata) {
  const errors = [];

  // Validate character limits (UPDATED 2025-12-24)
  if (metadata.seo.metaTitle.length < 50 || metadata.seo.metaTitle.length > 60) {
    errors.push(`Meta Title must be 50-60 characters (currently ${metadata.seo.metaTitle.length})`);
  }

  if (metadata.seo.metaDescription.length < 150 || metadata.seo.metaDescription.length > 160) {
    errors.push(`Meta Description must be 150-160 characters (currently ${metadata.seo.metaDescription.length})`);
  }

  // OG Title: max 60 characters
  if (metadata.openGraph.title.length > 60) {
    errors.push(`OG Title must be max 60 characters (currently ${metadata.openGraph.title.length})`);
  }

  // OG Description: 90-120 characters (min 90 for best engagement)
  if (metadata.openGraph.description.length < 90 || metadata.openGraph.description.length > 120) {
    errors.push(`OG Description must be 90-120 characters (currently ${metadata.openGraph.description.length})`);
  }

  // Validate required fields
  if (!metadata.categories || metadata.categories.length === 0) {
    errors.push('At least 1 category required');
  }

  if (!metadata.seo.canonicalUrl) {
    errors.push('Canonical URL required');
  }

  if (!metadata.openGraph.siteName) {
    errors.push('OG Site Name required');
  }

  return errors;
}

Content Type Mapping

Category Mapping

{
  "tech": "Technology",
  "personal-dev": "Personal Development"
}

Tag Extraction from Content

{
  "tech": ["technology", "programming", "development", "coding"],
  "personal-dev": ["self-improvement", "growth", "productivity", "mindset"]
}

Quality Assurance

Pre-Publishing Validation (CRITICAL)

  • Schema Compliance: ALL fields populated (no blanks)
  • Character Limits: Meta Title (50-60), Meta Description (150-160), OG Title (max 60), OG Description (90-120)
  • Author Reference: Valid author ID (not creating new)
  • Categories: At least 1 category reference
  • Timestamps: ISO format for publishedAt and date
  • SEO Fields: Complete seo object with all sub-fields
  • Open Graph: All fields populated (title, description, url, siteName, image, article)
  • Twitter: All fields populated (card, site, creator, title, description, image)
  • Images: All images have alt text
  • URLs: Canonical and OG URL properly formatted
  • Arrays: All arrays properly structured
  • References: All references point to existing documents
  • Content: Formatted correctly for Sanity
  • Metadata: Accurate and complete
  • Links: Working and correctly formatted
  • Images: Loaded and accessible

Post-Publishing Verification

  • Post displays correctly
  • All fields populated properly
  • Images load correctly
  • SEO metadata accessible
  • Social sharing works
  • RSS feed includes post
  • Search indexing successful

Dual-Mode Decision Logic

Choose Markdown Mode When:

  • User hasn't provided API credentials
  • Manual review desired before publishing
  • Testing or development phase
  • API rate limits concerns
  • Error in API mode occurs

Choose API Mode When:

  • User explicitly requests automation
  • API credentials are valid and available
  • Production publishing
  • Batch publishing multiple posts
  • High volume publishing needs

Ask User When:

  • Publishing mode not specified
  • Both modes available
  • User needs guidance on choice
  • Credentials status unclear

Best Practices

Content Formatting

  1. Convert markdown to Sanity Portable Text
  2. Preserve all formatting and structure
  3. Handle code blocks appropriately
  4. Convert images to Sanity assets
  5. Maintain link formatting

Metadata Management

  1. Extract and format all metadata
  2. Validate data types and formats
  3. Ensure required fields present
  4. Optimize for SEO
  5. Include quality scores

Error Handling

  1. Log all errors with context
  2. Provide clear error messages
  3. Offer solutions or workarounds
  4. Continue with valid data
  5. Mark incomplete items

Publishing Process

  1. Validate before publishing
  2. Handle authentication securely
  3. Confirm successful publication
  4. Test published content
  5. Archive source files

Integration with Workflow

This publisher receives polished content from style-guardian and:

  • Formats for Sanity CMS requirements
  • Applies metadata from SEO optimization
  • Handles publishing based on mode
  • Provides clear status and next steps
  • Archives all artifacts for reference

Successful publishing completes the blog writing workflow!

Next Steps After Publishing

  1. Verification: Check published post in Sanity Studio
  2. Preview: Test on live website
  3. Social Media: Share on appropriate channels
  4. Analytics: Monitor performance metrics
  5. Feedback: Gather reader responses
  6. Iteration: Apply learnings to next post