Scalars Reference
Scalar type reference for all built-in and custom scalars.
FraiseQL’s type system bridges Python types, GraphQL schema, and PostgreSQL. This guide explains how types flow through the system.
Each layer has its own type representation:
| Python | GraphQL | PostgreSQL | JSON |
|---|---|---|---|
str | String! | TEXT | "string" |
int | Int! | INTEGER | 123 |
float | Float! | DOUBLE PRECISION | 1.23 |
bool | Boolean! | BOOLEAN | true |
str | None | String | TEXT | "string" or null |
list[str] | [String!]! | TEXT[] | ["a", "b"] |
| TypeScript | GraphQL | PostgreSQL | MySQL | SQLite |
|---|---|---|---|---|
string | String | TEXT | VARCHAR(255) | TEXT |
number (int) | Int | INTEGER | INT | INTEGER |
number (float) | Float | FLOAT8 | DOUBLE | REAL |
boolean | Boolean | BOOLEAN | TINYINT(1) | INTEGER |
Date | DateTime | TIMESTAMPTZ | DATETIME | TEXT |
string[] | [String!]! | TEXT[] | JSON | TEXT (JSON) |
When using the gRPC transport, FraiseQL maps Python types to Protobuf types for the generated .proto schema:
| Python | GraphQL | Protobuf | Wire Type |
|---|---|---|---|
str | String! | string | Length-delimited |
int | Int! | int32 | Varint |
float | Float! | double | 64-bit |
bool | Boolean! | bool | Varint |
str | None | String | optional string | Length-delimited |
list[str] | [String!]! | repeated string | Length-delimited |
ID | ID! | string | Length-delimited (UUID as string) |
DateTime | DateTime! | google.protobuf.Timestamp | Message |
Decimal | Decimal! | string | Length-delimited (decimal as string) |
For a complete reference of scalar-to-Protobuf mappings, see Scalars.
import fraiseqlfrom fraiseql.scalars import ID, DateTime, Decimal
@fraiseql.typeclass User: """A user in the system.""" id: ID # UUID → ID! → UUID → "uuid-string" email: str # str → String! → TEXT → "string" name: str # str → String! → TEXT → "string" age: int # int → Int! → INTEGER → 123 balance: Decimal # Decimal → Decimal! → NUMERIC → "123.45" is_active: bool # bool → Boolean! → BOOLEAN → true created_at: DateTime # DateTime → DateTime! → TIMESTAMPTZ → "2024-01-15T..."Use union with None for nullable fields:
@fraiseql.typeclass User: bio: str | None # String (nullable) avatar_url: str | None # String (nullable) deleted_at: DateTime | None # DateTime (nullable)@fraiseql.typeclass User: roles: list[str] # [String!]! - non-null list, non-null items tags: list[str] | None # [String!] - nullable list, non-null items scores: list[int] # [Int!]!@fraiseql.typeclass Post: id: ID title: str author: User # Nested User objectGraphQL:
type Post { id: ID! title: String! author: User!}Use string literals for forward references:
@fraiseql.typeclass User: id: ID name: str posts: list['Post'] # Forward reference to Post
@fraiseql.typeclass Post: id: ID title: str author: 'User' # Forward reference to User@fraiseql.typeclass Comment: id: ID content: str parent: 'Comment | None' # Self-reference replies: list['Comment'] # Self-reference list@fraiseql.typeclass Post: id: ID featured_image: 'Image | None' # Optional relationshipfrom fraiseql.scalars import ( ID, # UUID DateTime, # Timestamp with timezone Date, # Date only Time, # Time only Decimal, # Arbitrary precision Json, # JSONB Vector, # pgvector)Custom scalars must subclass CustomScalar and implement serialize, parse_value, and parse_literal as instance methods:
from fraiseql.scalars import CustomScalarimport fraiseqlimport re
@fraiseql.scalarclass Email(CustomScalar): """Email address with validation.""" name = "Email"
def serialize(self, value: str) -> str: return str(value).lower()
def parse_value(self, value: str) -> str: if not re.match(r'^[^@]+@[^@]+\.[^@]+$', str(value)): raise ValueError("Invalid email") return str(value).lower()
def parse_literal(self, ast) -> str: if hasattr(ast, 'value'): return self.parse_value(ast.value) raise ValueError("Invalid email literal")Usage:
@fraiseql.typeclass User: email: Email # Custom scalarfrom enum import Enum
@fraiseql.enumclass OrderStatus(Enum): """Order status values.""" PENDING = "pending" CONFIRMED = "confirmed" SHIPPED = "shipped" DELIVERED = "delivered" CANCELLED = "cancelled"
@fraiseql.typeclass Order: id: ID status: OrderStatus # Enum typeGraphQL:
enum OrderStatus { PENDING CONFIRMED SHIPPED DELIVERED CANCELLED}
type Order { id: ID! status: OrderStatus!}Define shared fields across types:
@fraiseql.interfaceclass Node: """An object with a globally unique ID.""" id: ID
@fraiseql.interfaceclass Timestamped: """An object with timestamps.""" created_at: DateTime updated_at: DateTime
@fraiseql.type(implements=["Node", "Timestamped"])class User: id: ID name: str created_at: DateTime updated_at: DateTimeGraphQL:
interface Node { id: ID!}
interface Timestamped { createdAt: DateTime! updatedAt: DateTime!}
type User implements Node & Timestamped { id: ID! name: String! createdAt: DateTime! updatedAt: DateTime!}Represent multiple possible types:
@fraiseql.typeclass User: id: ID name: str
@fraiseql.typeclass Organization: id: ID name: str
@fraiseql.union(members=[User, Organization])class Actor: """An entity that can perform actions.""" passGraphQL:
union Actor = User | OrganizationQuery with type resolution:
query { actor(id: "...") { ... on User { name email } ... on Organization { name memberCount } }}Input types for mutations:
@fraiseql.inputclass CreateUserInput: """Input for creating a user.""" email: str name: str bio: str | None = None
@fraiseql.inputclass UpdateUserInput: """Input for updating a user.""" name: str | None = None bio: str | None = NoneGraphQL:
input CreateUserInput { email: String! name: String! bio: String}
input UpdateUserInput { name: String bio: String}FraiseQL infers types from Python annotations:
CREATE VIEW v_user ASSELECT u.id, -- UUID jsonb_build_object( 'id', u.id::text, -- String in JSONB 'name', u.name, -- String 'age', u.age, -- Integer 'balance', u.balance::text -- Decimal as string ) AS dataFROM tb_user u;CREATE FUNCTION fn_create_user( user_email TEXT, user_name TEXT) RETURNS mutation_response AS $$FraiseQL infers:
email: str, name: strentity JSONB field in mutation_response| Python | GraphQL | Notes |
|---|---|---|
str | String! | Non-null |
str | None | String | Nullable |
list[str] | [String!]! | Non-null list and items |
list[str] | None | [String!] | Nullable list, non-null items |
list[str | None] | [String]! | Non-null list, nullable items |
| PostgreSQL | Python |
|---|---|
NOT NULL | Required field |
| Nullable | ... | None |
DEFAULT | Default value |
Input validation is handled in your PostgreSQL fn_* functions using RAISE EXCEPTION or by using custom scalar types. There is no fraiseql.validate() function in the Python package.
For field-level validation via custom scalars, define a CustomScalar subclass (see the Custom Scalars section above) and use it as the field’s type annotation:
@fraiseql.inputclass CreateUserInput: email: Email # Custom scalar validates at parse time name: strType coercion at GraphQL boundaries (string "123" → integer 123) is handled by the Rust runtime, not Python code. The Python package is compile-time only — it produces schema.json and has no runtime behavior.
Computed fields are calculated in the SQL view. Declare them as regular annotated fields in the Python type — the computation happens entirely in PostgreSQL:
@fraiseql.typeclass User: first_name: str last_name: str full_name: str # Computed in the SQL viewSQL implementation — compute in the view, include u.id as required:
CREATE VIEW v_user ASSELECT u.id, jsonb_build_object( 'first_name', u.first_name, 'last_name', u.last_name, 'full_name', u.first_name || ' ' || u.last_name -- Computed ) AS dataFROM tb_user u;There are no @fraiseql.computed decorator or fraiseql.field(computed=True) parameter. Computed fields are a SQL view concern, not a Python annotation concern.
Control field exposure:
from typing import Annotatedfrom fraiseql.scalars import ID, Decimalimport fraiseql
@fraiseql.typeclass User: id: ID email: str # To exclude password_hash: simply don't declare it here. # Ensure v_user's jsonb_build_object() does not include password_hash. salary: Annotated[Decimal, fraiseql.field( requires_scope="hr:read" # Require scope )]There is no fraiseql.field(exclude=True) parameter. To exclude a field from GraphQL, omit it from the Python type class and ensure the SQL view’s jsonb_build_object() does not include it.
# Good: Specific typesid: IDemail: strprice: Decimalcreated_at: DateTime
# Avoid: Generic typesid: str # Could be anythingprice: float # Precision issuescreated_at: str # Format unclear# PostgreSQL: DECIMAL(12,2)# Python: Decimal (not float)price: Decimal
# PostgreSQL: TIMESTAMPTZ# Python: DateTime (not str)created_at: DateTime@fraiseql.typeclass Order: """ Represents a customer order.
Contains order details, line items, and fulfillment status. """ id: ID """Unique order identifier."""
total: Decimal """Order total in USD."""Scalars Reference
Scalar type reference for all built-in and custom scalars.
Decorators
All decorators available in the FraiseQL schema DSL.
Schema Design
Design patterns for structuring your FraiseQL schema.
Elo Validation
Custom scalar validation with the Elo expression language.