Skip to main content

Overview

Prisma Schema Language (PSL) is the domain-specific language used to define your data model, database connection, and generator configuration in Prisma. This component is responsible for parsing, validating, and analyzing Prisma schema files.
PSL is the foundation of the entire Prisma stack. Every component—from the Schema Engine to the Query Compiler to Prisma Format—relies on PSL to understand your data model.

What is PSL?

PSL is a declarative language that lets you define:
  • Data models: Tables, collections, and their relationships
  • Fields: Columns with types, attributes, and constraints
  • Datasources: Database connection configuration
  • Generators: Client and other code generation settings
  • Enums: Enumerated types
  • Composite types: Embedded document types (MongoDB)

Example Schema

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
  
  @@map("users")
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  
  @@index([authorId])
}

Architecture

The PSL implementation follows a layered architecture with clear separation of concerns:

Diagnostics

Error and warning collection, pretty printing, span tracking

Schema AST

Abstract syntax tree representation of the parsed schema

Parser Database

Semantic analysis and validation of the schema

PSL Core

Public API, configuration, and high-level operations

Dependency Graph

diagnostics →
schema-ast →
parser-database →
psl-core →
builtin-connectors →
psl (public API)

Crate Organization

The PSL codebase is organized into focused crates:

diagnostics

Handles errors, warnings, and diagnostic messages:
pub struct Diagnostics {
    // Collection of errors and warnings
}

pub struct DatamodelError {
    // Error message and location
}

pub struct Span {
    // Source code location for error reporting
}
Features:
  • Error collection during parsing
  • Pretty-printed error messages with source context
  • Span tracking for precise error locations
  • Warning accumulation

schema-ast

Contains the Abstract Syntax Tree definitions:
pub struct SchemaAst {
    pub tops: Vec<Top>,
}

pub enum Top {
    Model(Model),
    Enum(Enum),
    Source(SourceConfig),
    Generator(GeneratorConfig),
    Type(CompositeType),
}
Responsibilities:
  • Raw schema parsing
  • AST node definitions
  • Syntax validation
  • Comment preservation

parser-database

Provides semantic analysis and validation:
pub struct ParserDatabase {
    // Validated schema information
}

impl ParserDatabase {
    pub fn new(files: &[(String, SourceFile)], 
               diagnostics: &mut Diagnostics) -> Self;
    
    // Query methods for models, fields, relations, etc.
}
Features:
  • Attribute validation
  • Relation inference
  • Type checking
  • Constraint validation
  • Index and unique constraint handling

psl-core

High-level API and configuration:
pub struct ValidatedSchema {
    pub configuration: Configuration,
    pub db: ParserDatabase,
    pub connector: &'static dyn Connector,
    pub diagnostics: Diagnostics,
}

pub fn validate(
    file: SourceFile,
    connectors: ConnectorRegistry<'_>,
    extension_types: &dyn ExtensionTypes,
) -> ValidatedSchema
Provides:
  • Schema validation entry point
  • Configuration parsing (datasources, generators)
  • Connector selection
  • Preview feature management

builtin-connectors

Database-specific validation and type systems:
  • PostgreSQL connector
  • MySQL connector
  • SQLite connector
  • SQL Server connector
  • MongoDB connector
  • CockroachDB connector
Each connector provides:
  • Native type mappings
  • Capability definitions
  • Database-specific validation rules
  • Default values and constraints

psl (Public API)

The main entry point used by other Prisma components:
pub use psl::{
    Configuration,
    Datasource,
    Generator,
    ValidatedSchema,
    validate,
    parse_configuration,
    reformat,
};

Core Functionality

Schema Validation

The main validation flow:
use psl::*;

let schema = r#"
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id    Int    @id
  email String @unique
}
"#;

let source_file = SourceFile::from(schema);
let validated = validate(
    source_file,
    BUILTIN_CONNECTORS,
    &EmptyExtensionTypes,
);

if validated.diagnostics.has_errors() {
    eprintln!("{}", validated.render_own_diagnostics());
}

Multi-File Schemas

Prisma supports splitting schemas across multiple files:
let files = vec![
    ("schema.prisma".to_string(), SourceFile::from(main_schema)),
    ("models/user.prisma".to_string(), SourceFile::from(user_schema)),
    ("models/post.prisma".to_string(), SourceFile::from(post_schema)),
];

let validated = validate_multi_file(
    &files,
    BUILTIN_CONNECTORS,
    &EmptyExtensionTypes,
);

Configuration Parsing

Extract datasources and generators without full validation:
let (files, config, diagnostics) = 
    error_tolerant_parse_configuration(&files, BUILTIN_CONNECTORS);

for datasource in config.datasources {
    println!("Provider: {}", datasource.active_provider);
    println!("URL: {}", datasource.url);
}

for generator in config.generators {
    println!("Generator: {}", generator.name);
    println!("Provider: {}", generator.provider);
}

Schema Reformatting

Format Prisma schemas consistently:
let formatted = psl::reformat(schema_string, 2); // 2-space indent

Validation Features

Type Checking

Validates field types, ensures type compatibility, checks native type mappings

Relation Validation

Infers implicit relations, validates explicit relations, checks referential actions

Attribute Validation

Validates @id, @unique, @default, @relation, and all other attributes

Constraint Checking

Ensures unique constraints, validates indexes, checks composite keys

Attribute System

PSL supports field-level and block-level attributes: Field attributes (single @):
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique @db.VarChar(255)
  createdAt DateTime @default(now()) @map("created_at")
}
Block attributes (double @@):
model User {
  firstName String
  lastName  String
  
  @@unique([firstName, lastName])
  @@index([lastName])
  @@map("users")
}

Relation Inference

PSL can automatically infer simple relations:
// Explicit relation
model Post {
  author   User @relation(fields: [authorId], references: [id])
  authorId Int
}

model User {
  posts Post[]
}
// Implicit many-to-many
model Post {
  categories Category[]
}

model Category {
  posts Post[]
}
// PSL infers a join table: _CategoryToPost

Diagnostics and Error Reporting

PSL provides excellent error messages with source context:
Error validating field `authorId` in model `Post`: 
The relation field `author` on model `Post` is missing an 
opposite relation field on the model `User`.
  --> schema.prisma:12
   |
11 |   author   User @relation(fields: [authorId], references: [id])
12 |   authorId Int
   |

Pretty Printing

The diagnostics crate provides formatted output:
if validated.diagnostics.has_errors() {
    // Renders colored, formatted errors with source spans
    eprintln!("{}", validated.render_own_diagnostics());
}

Connector System

Connectors provide database-specific behavior:
pub trait Connector {
    fn name(&self) -> &str;
    fn capabilities(&self) -> ConnectorCapabilities;
    fn default_native_type_for_scalar_type(&self, scalar_type: ScalarType) 
        -> Option<NativeTypeInstance>;
    // ... more methods
}

Capabilities

Each connector declares its capabilities:
let caps = connector.capabilities();

if caps.contains(ConnectorCapability::FullTextSearch) {
    // Full-text search is supported
}

if caps.contains(ConnectorCapability::AutoIncrement) {
    // Auto-increment IDs are supported
}
Common capabilities:
  • AutoIncrement
  • Enums
  • Json
  • FullTextSearch
  • MultiSchema
  • NamedForeignKeys
  • ScalarLists
  • And many more…

Preview Features

PSL supports preview features for experimental functionality:
generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["fullTextSearch", "multiSchema"]
}
Managing preview features:
pub struct PreviewFeatures {
    // Active preview features
}

impl PreviewFeatures {
    pub fn is_active(&self, feature: PreviewFeature) -> bool;
}

Testing

PSL has three main test entry points:

1. Validation Tests

Declarative tests in psl/tests/validation/:
// File: invalid_relation.prisma
model Post {
  author User @relation(fields: [authorId], references: [invalid])
  authorId Int
}

model User {
  id Int @id
}

// Expected error (in comment at end of file)
// Error: The relation field `author` references `invalid` which does not exist.
# Run validation tests
cargo test -p psl -F all

# Update expectations
UPDATE_EXPECT=1 cargo test -p psl -F all

2. Reformat Tests

Tests in psl/tests/reformat/:
# Each .prisma file is reformatted
# Result compared to .reformatted.prisma file
UPDATE_EXPECT=1 cargo test -p psl reformat

3. Unit Tests

Regular Rust tests in psl/tests/datamodel_tests.rs:
#[test]
fn test_unique_constraint() {
    let schema = r#"
        model User {
            id    Int    @id
            email String @unique
        }
    "#;
    
    let validated = validate_schema(schema);
    assert!(!validated.diagnostics.has_errors());
}
Validation tests are preferred for new tests—they’re declarative, fast to compile, and easy to maintain.

Configuration

Datasource Configuration

datasource db {
  provider          = "postgresql"
  url               = env("DATABASE_URL")
  relationMode      = "prisma"  // or "foreignKeys"
  extensions        = [uuidOssp(map: "uuid-ossp")]
}
Parsed into:
pub struct Datasource {
    pub name: String,
    pub provider: String,
    pub active_provider: &'static str,
    pub url: StringFromEnvVar,
    pub relation_mode: Option<RelationMode>,
    // ... more fields
}

Generator Configuration

generator client {
  provider        = "prisma-client-js"
  output          = "./generated/client"
  previewFeatures = ["fullTextSearch"]
  binaryTargets   = ["native", "debian-openssl-3.0.x"]
}
Parsed into:
pub struct Generator {
    pub name: String,
    pub provider: String,
    pub output: Option<String>,
    pub preview_features: PreviewFeatures,
    pub binary_targets: Vec<String>,
    // ... more fields
}

Native Type Mappings

Each connector maps Prisma types to database-native types:
// PostgreSQL
model User {
  id      Int     @id @db.Integer
  name    String  @db.VarChar(255)
  balance Decimal @db.Decimal(10, 2)
  data    Json    @db.JsonB
}

// MySQL
model User {
  id      Int     @id @db.Int
  name    String  @db.VarChar(255)
  balance Decimal @db.Decimal(10, 2)
  data    Json    @db.Json
}

Common Use Cases

Schema Validation in CI

use psl::*;

fn validate_schema_file(path: &str) -> Result<(), String> {
    let schema = std::fs::read_to_string(path)?;
    let source = SourceFile::from(schema);
    let validated = validate(source, BUILTIN_CONNECTORS, &EmptyExtensionTypes);
    
    if validated.diagnostics.has_errors() {
        Err(validated.render_own_diagnostics())
    } else {
        Ok(())
    }
}

Programmatic Schema Generation

let schema = format!(r#"
datasource db {{
  provider = "{}"
  url      = env("DATABASE_URL")
}}

model {} {{
  id    Int    @id @default(autoincrement())
  name  String
}}
"#, provider, model_name);

let formatted = psl::reformat(&schema, 2)?;
std::fs::write("schema.prisma", formatted)?;

Extracting Model Information

let validated = validate_schema(schema_string);

for (model_id, model) in validated.db.walk_models() {
    println!("Model: {}", model.name());
    
    for field in model.scalar_fields() {
        println!("  Field: {} ({})", field.name(), field.field_type());
    }
    
    for relation in model.relations() {
        println!("  Relation: {} -> {}", 
            relation.name(),
            relation.referenced_model().name()
        );
    }
}

Prisma 7 Changes

In Prisma 7, url, directUrl, and shadowDatabaseUrl are no longer valid in the PSL datasource block. Connection strings are now provided externally by Prisma Client.
PSL now rejects these properties with helpful error messages:
// ❌ No longer valid
datasource db {
  provider         = "postgresql"
  url              = env("DATABASE_URL")
  directUrl        = env("DIRECT_URL")  // Error!
  shadowDatabaseUrl = env("SHADOW_URL")  // Error!
}

// ✅ Valid in Prisma 7
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")  // Only for schema engine
}

Source Code

Explore the PSL source code:
  • Main crate: psl/psl/
  • Core logic: psl/psl-core/src/
  • Parser database: psl/parser-database/
  • Diagnostics: psl/diagnostics/
  • Contributing guide: psl/CONTRIBUTING.md
  • Repository: prisma/prisma-engines

Build docs developers (and LLMs) love