Permission Mongo provides comprehensive schema validation to ensure data integrity and consistency across your collections.
Overview
The schema validation system:
Type checking - Enforce field types (string, number, date, objectId, etc.)
Constraints - Define min/max lengths, patterns, enums, and ranges
Required fields - Ensure critical fields are always present
Immutable fields - Prevent modification of fields after creation
Nested validation - Validate objects and arrays recursively
Custom formats - Email, URL, and other format validation
Schema definition
Define schemas in schema.yml:
collections :
users :
fields :
_id :
type : objectId
auto : true
email :
type : string
required : true
format : email
name :
type : string
required : true
min_length : 1
max_length : 255
role :
type : string
required : true
enum : [ "admin" , "manager" , "user" , "viewer" ]
tenant_id :
type : objectId
required : true
created_at :
type : date
auto : true
immutable : true
updated_at :
type : date
auto_update : true
Field types
Permission Mongo supports these field types:
Text values with optional length and pattern constraints
Floating-point numbers with optional min/max constraints
Whole numbers only, with optional min/max constraints
ISO8601 date/time values or native time.Time objects
MongoDB ObjectID (24 hex characters)
UUID string in standard format
Array of items with optional item schema
Nested object with property definitions
Any type - no validation applied
const (
TypeString FieldType = "string"
TypeNumber FieldType = "number"
TypeInteger FieldType = "integer"
TypeBoolean FieldType = "boolean"
TypeDate FieldType = "date"
TypeObjectId FieldType = "objectId"
TypeUUID FieldType = "uuid"
TypeArray FieldType = "array"
TypeObject FieldType = "object"
TypeAny FieldType = "any"
)
String constraints
Validate string fields with these constraints:
fields :
username :
type : string
required : true
min_length : 3
max_length : 50
pattern : "^[a-zA-Z0-9_]+$"
email :
type : string
required : true
format : email
website :
type : string
format : url
status :
type : string
enum : [ "draft" , "published" , "archived" ]
default : "draft"
Minimum string length (inclusive)
Maximum string length (inclusive)
Regular expression pattern the value must match
Predefined format: email or url
Default value if field is not provided
Number constraints
Validate numeric fields:
fields :
age :
type : integer
min : 0
max : 150
price :
type : number
min : 0.01
max : 999999.99
rating :
type : number
min : 0
max : 5
Minimum value (inclusive)
Maximum value (inclusive)
Array validation
Validate arrays and their items:
fields :
tags :
type : array
items :
type : string
min_length : 1
max_length : 50
collaborators :
type : array
items :
type : objectId
metadata :
type : array
items :
type : object
properties :
key :
type : string
required : true
value :
type : string
Schema for array items. All items must match this schema.
Object validation
Validate nested objects:
fields :
address :
type : object
properties :
street :
type : string
required : true
city :
type : string
required : true
state :
type : string
pattern : "^[A-Z]{2}$"
postal_code :
type : string
pattern : "^[0-9]{5}$"
country :
type : string
default : "US"
Map of property names to field schemas. Each property is validated according to its schema.
Field modifiers
Control field behavior with modifiers:
fields :
_id :
type : objectId
auto : true # Auto-generated on create
email :
type : string
required : true # Must be present
immutable : true # Cannot be changed after creation
created_at :
type : date
auto : true # Auto-set on create
immutable : true # Cannot be changed
updated_at :
type : date
auto_update : true # Auto-updated on every change
computed_field :
type : string
computed : true # Computed by application logic
Field must be present in documents
Field cannot be modified after creation
Field is automatically generated on document creation
Field is automatically updated on every modification
Field is computed by hooks or application logic - skipped during validation
Validation process
The validator checks documents in this order:
Required field check - Ensure all required fields are present
Type validation - Verify each field matches its declared type
Constraint validation - Check min/max, patterns, enums, etc.
Nested validation - Recursively validate objects and arrays
// ValidateDocument validates a document for a collection
func ( v * Validator ) ValidateDocument ( collection string , doc map [ string ] interface {}) [] ValidationError {
var errors [] ValidationError
collConfig , ok := v . schema . GetCollection ( collection )
if ! ok {
errors = append ( errors , ValidationError {
Field : "" ,
Code : ErrUnknownCollection ,
Message : fmt . Sprintf ( "unknown collection: %s " , collection ),
})
return errors
}
// Validate required fields
for name , field := range collConfig . Fields {
if field . Required {
val , exists := doc [ name ]
if ! exists || val == nil {
errors = append ( errors , ValidationError {
Field : name ,
Code : ErrRequired ,
Message : fmt . Sprintf ( "field ' %s ' is required" , name ),
})
}
}
}
// Validate each field in the document
for name , value := range doc {
if name == "_id" {
continue
}
field , exists := collConfig . Fields [ name ]
if ! exists {
continue
}
if field . Computed != nil {
continue
}
if value == nil && ! field . Required {
continue
}
fieldErrors := v . ValidateField ( & field , name , value )
errors = append ( errors , fieldErrors ... )
}
return errors
}
Validation errors
Validation errors include specific error codes:
Field type doesn’t match schema
Required field is missing
String doesn’t match pattern
String doesn’t match format (email/url)
Value not in allowed enum list
Attempt to modify immutable field
Update validation
When updating documents, the validator checks:
Document validation - Validate the new document
Immutable fields - Ensure immutable fields haven’t changed
// ValidateUpdate validates an update against the old document
func ( v * Validator ) ValidateUpdate ( collection string , oldDoc , newDoc map [ string ] interface {}) [] ValidationError {
var errors [] ValidationError
collConfig , ok := v . schema . GetCollection ( collection )
if ! ok {
return errors
}
// First validate the new document
docErrors := v . ValidateDocument ( collection , newDoc )
errors = append ( errors , docErrors ... )
// Check immutable fields
for name , field := range collConfig . Fields {
if ! field . Immutable {
continue
}
oldVal , oldExists := oldDoc [ name ]
newVal , newExists := newDoc [ name ]
if oldExists && ( ! newExists || ! reflect . DeepEqual ( oldVal , newVal )) {
errors = append ( errors , ValidationError {
Field : name ,
Code : ErrImmutable ,
Message : fmt . Sprintf ( "field ' %s ' is immutable" , name ),
Value : newVal ,
})
}
}
return errors
}
Partial validation
For partial updates (PATCH operations), use partial validation:
// ValidatePartialDocument validates only the fields present
func ( v * Validator ) ValidatePartialDocument ( collection string , doc map [ string ] interface {}) [] ValidationError {
var errors [] ValidationError
collConfig , ok := v . schema . GetCollection ( collection )
if ! ok {
return errors
}
// Validate each field in the document (but don't check required)
for name , value := range doc {
if name == "_id" {
continue
}
field , exists := collConfig . Fields [ name ]
if ! exists || field . Computed != nil || value == nil {
continue
}
fieldErrors := v . ValidateField ( & field , name , value )
errors = append ( errors , fieldErrors ... )
}
return errors
}
This validates only the fields being updated without requiring all fields to be present.
Example schema
Complete example for a documents collection:
collections :
documents :
fields :
_id :
type : objectId
required : true
title :
type : string
required : true
max_length : 500
content :
type : string
status :
type : string
enum : [ "draft" , "published" , "archived" ]
default : "draft"
tenant_id :
type : objectId
required : true
owner_id :
type : objectId
required : true
tags :
type : array
items :
type : string
metadata :
type : object
properties :
category :
type : string
priority :
type : integer
min : 1
max : 5
created_at :
type : date
auto : true
immutable : true
updated_at :
type : date
auto_update : true
indexes :
- fields : [ "tenant_id" , "status" ]
- fields : [ "owner_id" ]
- fields : [ "tags" ]
Best practices
Always require critical fields
Mark essential fields like tenant_id, owner_id, and identifiers as required: true.
Make audit fields immutable
Set created_at, created_by, and other audit fields as immutable: true to preserve history.
Use enums for known values
Define enum constraints for status fields, types, and other fields with fixed options.
Validate formats for external data
Use format: email and format: url for user-provided data to ensure validity.
Set reasonable length limits
Apply max_length to prevent abuse and ensure database performance.
RBAC - Control access with role-based policies
Hooks - Execute validation logic in pre-operation hooks
Versioning - Track document changes over time