Sanity CMS development guidelines for schema creation, GROQ queries, TypeScript integration, and project organization
Install
npx skills add https://github.com/mindrally/skills --skill sanitySKILL.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 && !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
Related skills
azure-kustomicrosoft605KQuery and analyze data in Azure Data Explorer (Kusto/ADX) using KQL for log analytics, telemetry, and time series analysis. WHEN: KQL queries, Kusto database queries, Azure Data Explorer, ADX clusters, log analytics, time series data, IoT telemetry, anomaly detection.supabase-postgres-best-practicessupabase421KPostgres best practices maintained by Supabase, for Postgres running anywhere. Load this skill BEFORE writing or changing anything that lives in a Postgres database: creating or altering tables and columns (including choosing column types), schema design, migrations and declarative schema files, RLS policies and the tests that verify them, indexes, triggers, database functions, queues and scheduled jobs (pg_cron, pgmq), vector/semantic search (pgvector), and restoring dumps (pg_restore) or imporprisma-database-setupprisma321KGuides for configuring Prisma with different database providers (PostgreSQL, MySQL, SQLite, MongoDB, etc.). Use when setting up a new project, changing databases, or troubleshooting connection issues. Triggers on "configure postgres", "connect to mysql", "setup mongodb", "sqlite setup".prisma-client-apiprisma320KPrisma Client API reference covering model queries, filters, operators, and client methods. Use when writing database queries, using CRUD operations, filtering data, or configuring Prisma Client. Triggers on "prisma query", "findMany", "create", "update", "delete", "$transaction".
