sanity

Sanity CMS development guidelines for schema creation, GROQ queries, TypeScript integration, and project organization

INSTALLATION
npx skills add https://github.com/mindrally/skills --skill sanity
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Sanity CMS Development

You are an expert in Sanity CMS, GROQ queries, TypeScript integration, and headless CMS architecture.

Core Principles

  • Design schemas with content modeling best practices
  • Write efficient GROQ queries
  • Use TypeScript for type safety
  • Organize projects for scalability
  • Implement proper validation and preview

Project Structure

sanity/

├── schemas/

│   ├── documents/

│   │   ├── post.ts

│   │   └── author.ts

│   ├── objects/

│   │   ├── blockContent.ts

│   │   └── image.ts

│   └── index.ts

├── lib/

│   ├── client.ts

│   └── queries.ts

├── components/

│   └── previews/

└── sanity.config.ts

Schema Definition

Document Types

// schemas/documents/post.ts

import { defineType, defineField } from 'sanity';

export default defineType({

  name: 'post',

  title: 'Post',

  type: 'document',

  fields: [

    defineField({

      name: 'title',

      title: 'Title',

      type: 'string',

      validation: (Rule) => Rule.required().min(10).max(80),

    }),

    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: 'publishedAt',

      title: 'Published at',

      type: 'datetime',

    }),

    defineField({

      name: 'body',

      title: 'Body',

      type: 'blockContent',

    }),

  ],

  preview: {

    select: {

      title: 'title',

      author: 'author.name',

      media: 'mainImage',

    },

    prepare({ title, author, media }) {

      return {

        title,

        subtitle: author ? `by ${author}` : '',

        media,

      };

    },

  },

});

Object Types

// schemas/objects/blockContent.ts

import { defineType, defineArrayMember } from 'sanity';

export default defineType({

  name: 'blockContent',

  title: 'Block Content',

  type: 'array',

  of: [

    defineArrayMember({

      type: 'block',

      styles: [

        { title: 'Normal', value: 'normal' },

        { title: 'H2', value: 'h2' },

        { title: 'H3', value: 'h3' },

        { title: 'Quote', value: 'blockquote' },

      ],

      marks: {

        decorators: [

          { title: 'Strong', value: 'strong' },

          { title: 'Emphasis', value: 'em' },

          { title: 'Code', value: 'code' },

        ],

        annotations: [

          {

            name: 'link',

            type: 'object',

            title: 'URL',

            fields: [

              {

                name: 'href',

                type: 'url',

                title: 'URL',

              },

            ],

          },

        ],

      },

    }),

    defineArrayMember({

      type: 'image',

      options: { hotspot: true },

    }),

  ],

});

GROQ Queries

Basic Queries

// lib/queries.ts

// Get all posts

export const allPostsQuery = groq`

  *[_type == "post"] | order(publishedAt desc) {

    _id,

    title,

    slug,

    publishedAt,

    "author": author->name,

    "imageUrl": mainImage.asset->url

  }

`;

// Get single post by slug

export const postBySlugQuery = groq`

  *[_type == "post" && slug.current == $slug][0] {

    _id,

    title,

    body,

    publishedAt,

    "author": author->{name, image},

    "categories": categories[]->title

  }

`;

// Pagination

export const paginatedPostsQuery = groq`

  *[_type == "post"] | order(publishedAt desc) [$start...$end] {

    _id,

    title,

    slug,

    excerpt

  }

`;

Advanced GROQ

// Conditional projections

export const conditionalQuery = groq`

  *[_type == "post"] {

    title,

    "content": select(

      defined(body) => body,

      "No content available"

    )

  }

`;

// Coalesce for fallbacks

export const fallbackQuery = groq`

  *[_type == "post"] {

    "displayTitle": coalesce(seoTitle, title)

  }

`;

// References

export const withReferencesQuery = groq`

  *[_type == "post" && references($authorId)] {

    title,

    publishedAt

  }

`;

Client Setup

// lib/client.ts

import { createClient } from '@sanity/client';

import imageUrlBuilder from '@sanity/image-url';

export const client = createClient({

  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,

  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,

  apiVersion: '2024-01-01',

  useCdn: process.env.NODE_ENV === 'production',

});

const builder = imageUrlBuilder(client);

export function urlFor(source: any) {

  return builder.image(source);

}

TypeScript Integration

// Generate types from schema

import { Post, Author } from '@/sanity/types';

export async function getPosts(): Promise<Post[]> {

  return client.fetch(allPostsQuery);

}

export async function getPost(slug: string): Promise<Post | null> {

  return client.fetch(postBySlugQuery, { slug });

}

Validation

defineField({

  name: 'email',

  type: 'string',

  validation: (Rule) =>

    Rule.required()

      .email()

      .custom((email) => {

        if (email &#x26;&#x26; !email.endsWith('@company.com')) {

          return 'Must be a company email';

        }

        return true;

      }),

});

Custom Components

// Custom input component

import { StringInputProps } from 'sanity';

export function CustomStringInput(props: StringInputProps) {

  return (

    <div>

      <label>{props.schemaType.title}</label>

      {props.renderDefault(props)}

      <span>{props.value?.length ?? 0} characters</span>

    </div>

  );

}

Best Practices

  • Use references for relationships between documents
  • Implement proper validation rules
  • Create meaningful preview configurations
  • Use portable text for rich content
  • Optimize images with Sanity's image pipeline
  • Set up proper CORS and API permissions
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card