Skip to main content
The Validation API allows you to define rules that ensure data quality and consistency in your Sanity documents. Validation rules are defined in your schema and executed both in the Studio UI and server-side.

Rule Class

The Rule class provides a fluent API for defining validation rules. Each method returns a new Rule instance, allowing you to chain multiple constraints.
import {defineField} from 'sanity'

defineField({
  name: 'title',
  type: 'string',
  validation: (Rule) => Rule.required().min(5).max(100)
})

Type-Specific Constructors

Create rules for specific types:
Rule.string()    // String validation rules
Rule.number()    // Number validation rules
Rule.boolean()   // Boolean validation rules
Rule.array()     // Array validation rules
Rule.object()    // Object validation rules
Rule.dateTime()  // Date/DateTime validation rules
Example:
defineField({
  name: 'age',
  type: 'number',
  validation: (Rule) => Rule.number().integer().positive().max(120)
})

Common Validation Methods

These methods work across all field types:

required

Mark a field as required (cannot be null or undefined).
Rule.required()
Example:
defineField({
  name: 'email',
  type: 'string',
  validation: (Rule) => Rule.required()
})

optional

Explicitly mark a field as optional (allows null/undefined).
Rule.optional()

custom

Define custom validation logic with full access to document context.
Rule.custom<T>(
  fn: (value: T, context: ValidationContext) => true | string | Promise<true | string>,
  options?: {bypassConcurrencyLimit?: boolean}
)
fn
CustomValidator
required
Validation function that receives the field value and context. Return true for valid, or an error message string for invalid.
options.bypassConcurrencyLimit
boolean
Whether to bypass the default concurrency limit for async validators (default: false)
Example:
defineField({
  name: 'slug',
  type: 'string',
  validation: (Rule) => Rule.custom((slug) => {
    if (!slug) return true
    if (!/^[a-z0-9-]+$/.test(slug)) {
      return 'Slug can only contain lowercase letters, numbers, and hyphens'
    }
    return true
  })
})
Async validation:
defineField({
  name: 'username',
  type: 'string',
  validation: (Rule) => Rule.custom(async (username, context) => {
    if (!username) return true
    
    // Check if username is already taken
    const client = context.getClient({apiVersion: '2024-01-01'})
    const existing = await client.fetch(
      '*[_type == "user" && username == $username && _id != $id][0]',
      {username, id: context.document._id}
    )
    
    if (existing) {
      return 'Username is already taken'
    }
    return true
  })
})
Validation Context:
interface ValidationContext {
  document: SanityDocument      // The full document being edited
  parent: unknown                // Parent object/array containing this field
  path: Path                     // Path to this field
  type: SchemaType              // Schema type of this field
  getClient: (options: {apiVersion: string}) => SanityClient
  i18n: LocaleSource            // Internationalization utilities
}

String Validation

min / max

Set minimum and maximum length constraints.
Rule.min(length: number)
Rule.max(length: number)
Example:
defineField({
  name: 'bio',
  type: 'string',
  validation: (Rule) => Rule.min(10).max(500)
})

length

Require an exact length.
Rule.length(length: number)
Example:
defineField({
  name: 'countryCode',
  type: 'string',
  validation: (Rule) => Rule.length(2)
})

uppercase / lowercase

Enforce letter casing.
Rule.uppercase()
Rule.lowercase()
Example:
defineField({
  name: 'code',
  type: 'string',
  validation: (Rule) => Rule.required().uppercase()
})

regex

Validate against a regular expression.
Rule.regex(pattern: RegExp, name?: string, options?: {invert?: boolean})
pattern
RegExp
required
Regular expression to match
name
string
Name for the pattern (used in error messages)
options.invert
boolean
Invert the match (pattern must NOT match)
Example:
defineField({
  name: 'phoneNumber',
  type: 'string',
  validation: (Rule) => Rule.regex(
    /^\+?[1-9]\d{1,14}$/,
    'E.164 format',
    {name: 'E.164 phone number'}
  )
})

email

Validate email address format.
Rule.email()
Example:
defineField({
  name: 'email',
  type: 'string',
  validation: (Rule) => Rule.required().email()
})

uri

Validate URL format with scheme restrictions.
Rule.uri(options?: {
  scheme?: string | RegExp | Array<string | RegExp>
  allowRelative?: boolean
  relativeOnly?: boolean
  allowCredentials?: boolean
})
Example:
defineField({
  name: 'website',
  type: 'url',
  validation: (Rule) => Rule.uri({
    scheme: ['https'], // Only allow HTTPS
    allowRelative: false
  })
})

Number Validation

min / max

Set minimum and maximum value constraints.
Rule.min(value: number)
Rule.max(value: number)
Example:
defineField({
  name: 'rating',
  type: 'number',
  validation: (Rule) => Rule.min(1).max(5)
})

greaterThan / lessThan

Exclusive comparison (value must be strictly greater/less).
Rule.greaterThan(value: number)
Rule.lessThan(value: number)
Example:
defineField({
  name: 'discount',
  type: 'number',
  validation: (Rule) => Rule.greaterThan(0).lessThan(100)
})

integer

Require an integer (no decimals).
Rule.integer()
Example:
defineField({
  name: 'quantity',
  type: 'number',
  validation: (Rule) => Rule.required().integer().positive()
})

precision

Limit decimal places.
Rule.precision(limit: number)
Example:
defineField({
  name: 'price',
  type: 'number',
  validation: (Rule) => Rule.precision(2) // Max 2 decimal places
})

positive / negative

Require positive or negative values.
Rule.positive()  // value >= 0
Rule.negative()  // value < 0
Example:
defineField({
  name: 'amount',
  type: 'number',
  validation: (Rule) => Rule.required().positive()
})

Array Validation

min / max

Set minimum and maximum array length.
Rule.min(length: number)
Rule.max(length: number)
Example:
defineField({
  name: 'tags',
  type: 'array',
  of: [{type: 'string'}],
  validation: (Rule) => Rule.min(1).max(5)
})

unique

Require all array items to be unique.
Rule.unique()
Example:
defineField({
  name: 'keywords',
  type: 'array',
  of: [{type: 'string'}],
  validation: (Rule) => Rule.required().min(3).unique()
})

Object Validation

fields

Define validation rules for object fields.
Rule.fields(rules: FieldRules)
Example:
defineField({
  name: 'author',
  type: 'object',
  fields: [
    {name: 'name', type: 'string'},
    {name: 'email', type: 'string'}
  ],
  validation: (Rule) => Rule.fields({
    name: (nameRule) => nameRule.required(),
    email: (emailRule) => emailRule.required().email()
  })
})

Date Validation

min / max

Set minimum and maximum date constraints (ISO 8601 format).
Rule.min(date: string)
Rule.max(date: string)
Example:
defineField({
  name: 'publishDate',
  type: 'date',
  validation: (Rule) => Rule.min('2023-01-01').max('2025-12-31')
})

defineField({
  name: 'eventTime',
  type: 'datetime',
  validation: (Rule) => Rule.min('2024-01-01T00:00:00Z')
})

Asset Validation

assetRequired

Require that an asset (image/file) has been uploaded.
Rule.assetRequired()
Example:
defineField({
  name: 'coverImage',
  type: 'image',
  validation: (Rule) => Rule.required().assetRequired()
})

Reference Validation

reference

Validate reference fields.
Rule.reference()
Example:
defineField({
  name: 'category',
  type: 'reference',
  to: [{type: 'category'}],
  validation: (Rule) => Rule.required().reference()
})

Validation Levels

Set the severity level of validation messages:

error

Block document publishing (default).
Rule.error(message?: string)
Example:
defineField({
  name: 'title',
  type: 'string',
  validation: (Rule) => Rule.required().error('Title is mandatory')
})

warning

Show warning but allow publishing.
Rule.warning(message?: string)
Example:
defineField({
  name: 'excerpt',
  type: 'text',
  validation: (Rule) => Rule.max(200).warning('Excerpt should be under 200 characters for better SEO')
})

info

Show informational message.
Rule.info(message?: string)
Example:
defineField({
  name: 'slug',
  type: 'slug',
  validation: (Rule) => Rule.required().info('Slug will be used in the URL')
})

Localized Messages

Provide validation messages in multiple languages:
defineField({
  name: 'title',
  type: 'string',
  validation: (Rule) => Rule.required().error({
    'en-US': 'Title is required',
    'nb-NO': 'Tittel er påkrevd',
    'es-ES': 'El título es obligatorio'
  })
})

Field References

Compare field values using Rule.valueOfField():
defineField({
  name: 'maxPrice',
  type: 'number',
  validation: (Rule) => Rule.min(Rule.valueOfField('minPrice'))
})
Example with context:
defineField({
  name: 'endDate',
  type: 'date',
  validation: (Rule) => Rule.custom((endDate, context) => {
    const startDate = context.parent?.startDate
    if (endDate && startDate && endDate < startDate) {
      return 'End date must be after start date'
    }
    return true
  })
})

Complex Validation Examples

Conditional Required Fields

defineField({
  name: 'externalUrl',
  type: 'url',
  validation: (Rule) => Rule.custom((url, context) => {
    const type = context.parent?.linkType
    if (type === 'external' && !url) {
      return 'External URL is required when link type is external'
    }
    return true
  })
})

Cross-Field Validation

defineType({
  name: 'event',
  type: 'document',
  fields: [
    defineField({
      name: 'startDate',
      type: 'datetime',
      validation: (Rule) => Rule.required()
    }),
    defineField({
      name: 'endDate',
      type: 'datetime',
      validation: (Rule) => Rule.custom((endDate, context) => {
        const startDate = context.document?.startDate
        if (!endDate || !startDate) return true
        if (endDate <= startDate) {
          return 'End date must be after start date'
        }
        return true
      })
    })
  ]
})

Array Item Validation

defineField({
  name: 'contributors',
  type: 'array',
  of: [
    {
      type: 'object',
      fields: [
        {name: 'name', type: 'string'},
        {name: 'role', type: 'string'}
      ]
    }
  ],
  validation: (Rule) => Rule.custom((contributors) => {
    if (!contributors || contributors.length === 0) {
      return 'At least one contributor is required'
    }
    const hasAuthor = contributors.some(c => c.role === 'author')
    if (!hasAuthor) {
      return 'At least one contributor must have the role "author"'
    }
    return true
  })
})

Async Validation with API

defineField({
  name: 'isbn',
  type: 'string',
  validation: (Rule) => Rule.custom(async (isbn, context) => {
    if (!isbn) return true
    
    const client = context.getClient({apiVersion: '2024-01-01'})
    const query = '*[_type == "book" && isbn == $isbn && _id != $id][0]'
    const existing = await client.fetch(query, {
      isbn,
      id: context.document._id
    })
    
    if (existing) {
      return `ISBN ${isbn} is already used by "${existing.title}"`
    }
    
    return true
  })
})

Best Practices

  1. Combine multiple constraints: Chain methods for comprehensive validation
    Rule.required().min(10).max(200).regex(/^[A-Z]/)
    
  2. Use appropriate severity levels: Reserve error() for blocking issues, use warning() for recommendations
  3. Provide helpful messages: Explain what’s wrong and how to fix it
    Rule.required().error('Product name is required for the catalog')
    
  4. Consider performance: Expensive async validations should use bypassConcurrencyLimit: true sparingly
  5. Test edge cases: Validate with null, undefined, empty strings, etc.
  6. Use custom validators for complex logic: Don’t try to express everything with built-in methods

Build docs developers (and LLMs) love