Format code blocks with Prettier

Official(made by Sanity team)

By Simeon Griggs

Automatically format code blocks using Prettier to enforce consistency across all documents.

schemaTypes/post.ts

import {defineType, defineArrayMember} from 'sanity'

export const post = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    {
      name: 'content',
      title: 'Content',
      type: 'array',
      of: [
        defineArrayMember({type: 'block'}),
        defineArrayMember({type: 'code'}),
        // Add other block types as needed
      ],
    },
    // Add other fields as needed
  ],
})

functions/prettier-format-code/index.ts

import {documentEventHandler} from '@sanity/functions'
import {createClient} from '@sanity/client'
import {format, type Options} from 'prettier'

const PRETTIER_CONFIG: Options = {
  semi: false,
  singleQuote: true,
  trailingComma: 'es5',
  printWidth: 60,
  tabWidth: 2,
  useTabs: false,
}

const BLOCK_CONTENT_FIELD_NAME = 'content'

export const handler = documentEventHandler(async ({context, event}) => {
  console.log('Begin formatting code function...')

  const client = createClient({
    ...context.clientOptions,
    useCdn: false,
    apiVersion: '2025-08-21',
  })

  if (!event.data[BLOCK_CONTENT_FIELD_NAME]) {
    console.log('No content')
    return
  }

  const codeBlocks = event.data[BLOCK_CONTENT_FIELD_NAME].filter((block) => block._type === 'code')

  if (!codeBlocks.length) {
    console.log('No code blocks')
    return
  }

  try {
    const formattedCodeBlocks = await Promise.all(
      codeBlocks.map(async (block) => {
        if (block._type !== 'code' || !block.code) {
          return block
        }

        try {
          // Determine parser based on language
          const parser = getParserForLanguage(block.language)

          if (!parser) {
            console.warn(`No parser found for language: ${block.language}`)
            return block
          }

          // Format the code using Prettier
          const formattedCode = await format(block.code, {
            parser,
            ...PRETTIER_CONFIG,
          })

          return {
            ...block,
            code: formattedCode,
          }
        } catch (formatError) {
          console.warn(`Failed to format code block ${block._key}:`, formatError)
          // Return original block if formatting fails
          return block
        }
      }),
    )

    // Find changed code blocks by comparing formatted code with original code
    const changedCodeBlocks = formattedCodeBlocks.filter((formattedBlock) => {
      const originalBlock = event.data[BLOCK_CONTENT_FIELD_NAME].find(
        (block) => block._key === formattedBlock._key,
      )
      return (
        originalBlock &&
        originalBlock._type === 'code' &&
        formattedBlock.code !== originalBlock.code
      )
    })

    if (!changedCodeBlocks.length) {
      console.log('No changed code blocks')
      return
    }

    const transaction = client.transaction()
    for (const block of changedCodeBlocks) {
      const patch = client.patch(event.data._id).set({
        [`${BLOCK_CONTENT_FIELD_NAME}[_key=="${block._key}"].code`]: block.code,
      })
      transaction.patch(patch)
    }
    await transaction.commit()
    console.log(
      `Formatted ${changedCodeBlocks.length === 1 ? '1 code block' : `${changedCodeBlocks.length} code blocks`}`,
    )
  } catch (error) {
    console.error('Error formatting code blocks:', error)
    throw error
  }
})

function getParserForLanguage(language?: string): string {
  if (!language) return 'babel'

  const languageMap: Record<string, string> = {
    tsx: 'typescript',
    ts: 'typescript',
    jsx: 'babel',
    js: 'babel',
    json: 'json',
    css: 'css',
    scss: 'scss',
    less: 'less',
    html: 'html',
    vue: 'vue',
    yaml: 'yaml',
    markdown: 'markdown',
    md: 'markdown',
  }

  return languageMap[language] || 'babel'
}

sanity.blueprint.ts

import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'

export default defineBlueprint({
  // ...all other settings
  resources: [
    //...all other functions
    defineDocumentFunction({
      name: 'prettier-format-code',
      event: {
        on: ['create', 'update'],
        filter:
          '_type == "post" && (delta::changedAny(content[_type == "code"]) || (delta::operation() == "create" && defined(content[_type == "code"])))',
        projection: '{_id, content}',
      },
    }),
  ],
})

Problem

Without enforced code formatting, code blocks in content can be inconsistently rendered.

Solution

This Sanity Function formats code blocks with Prettier when a document is published and code blocks have been modified. The formatter uses the language value of the code block to determine the parser.

This function is designed to format code generated by the Code Input plugin.

Quick Start

View the complete example and source code.

Initialize blueprints if you haven't already: npx sanity blueprints init

Then: npx sanity blueprints add function --example prettier-format-code

Then deploy: npx sanity blueprints deploy

How It Works

When a content editor publishes a new article where code blocks have been modified, the function automatically:

  1. Triggers on the publish event
  2. Formats all code blocks
  3. Checks if the code blocks have changed after formatting
  4. Updates code blocks with new formatting

Key Benefits

  • Improves content quality by ensuring consistent code formatting across all documents
  • Removes overhead of manually formatting code blocks

Perfect For

  • Editorial teams or individuals managing documentation or blog posts with code examples.

Contributor

Official Recipes by Sanity

First Published Timestamp Function

Featured contribution
Official(made by Sanity team)

Automatically track when content was first published with a timestamp that sets once and never overwrites, providing reliable publication history for analytics and editorial workflows.

Knut Melvær
Go to First Published Timestamp Function

Automatically tag blog posts

Featured contribution
Official(made by Sanity team)

AI-powered automatic tagging for Sanity blog posts that analyzes content to generate 3 relevant tags, maintaining consistency by reusing existing tags from your content library.

Go to Automatically tag blog posts