Skip to content

Models

Learn how to define and use Pydantic models with Airtable integration.


Basic Model Definition

The @airtable_model decorator transforms any Pydantic BaseModel into an Airtable-connected model:

from pydantic_airtable import airtable_model, configure_from_env
from pydantic import BaseModel
from typing import Optional

configure_from_env()

@airtable_model(table_name="Users")
class User(BaseModel):
    name: str
    email: str
    age: Optional[int] = None
    is_active: bool = True

Model Decorator Parameters

The @airtable_model decorator accepts these parameters:

Parameter Type Description
table_name str Airtable table name (required)
config AirtableConfig Configuration object
access_token str Direct token specification
base_id str Direct base ID specification

Examples

# Minimal - uses global config
@airtable_model(table_name="Users")
class User(BaseModel):
    name: str

# With explicit credentials
@airtable_model(
    table_name="Projects",
    access_token="pat_xxx",
    base_id="appXXX"
)
class Project(BaseModel):
    name: str

# With config object
config = AirtableConfig(access_token="pat_xxx", base_id="appXXX")

@airtable_model(config=config, table_name="Tasks")
class Task(BaseModel):
    title: str

Automatic Fields

Every model automatically includes these fields:

Field Type Description
id Optional[str] Airtable record ID (e.g., recXXXXXXXXXXXXXX)
created_time Optional[datetime] Record creation timestamp
@airtable_model(table_name="Users")
class User(BaseModel):
    name: str
    email: str

# After creation
user = User.create(name="Alice", email="alice@example.com")
print(user.id)           # recXXXXXXXXXXXXXX
print(user.created_time) # 2024-01-15T10:30:00.000Z

Field Types

Supported Python Types

Python Type Airtable Type Notes
str SINGLE_LINE_TEXT Default for strings
int NUMBER Integer values
float NUMBER Decimal values
bool CHECKBOX True/False
datetime DATETIME Date and time
date DATE Date only
Enum SELECT Single selection
List[str] MULTI_SELECT Multiple selections

Field Type Detection

Field names trigger automatic type detection:

@airtable_model(table_name="Contacts")
class Contact(BaseModel):
    name: str           # → SINGLE_LINE_TEXT
    email: str          # → EMAIL (detected!)
    phone: str          # → PHONE (detected!)
    website: str        # → URL (detected!)
    bio: str            # → LONG_TEXT (detected!)
    salary: float       # → CURRENCY (detected!)
    completion: float   # → PERCENT (detected!)

See Field Types for complete detection rules.


Optional Fields

Use Optional for nullable fields:

from typing import Optional

@airtable_model(table_name="Users")
class User(BaseModel):
    name: str                          # Required
    email: str                         # Required
    phone: Optional[str] = None        # Optional
    age: Optional[int] = None          # Optional
    bio: Optional[str] = None          # Optional

Default Values

Set default values as you would with Pydantic:

from datetime import datetime

@airtable_model(table_name="Tasks")
class Task(BaseModel):
    title: str
    status: str = "pending"
    priority: int = 1
    is_urgent: bool = False
    created_at: datetime = datetime.now()

Enum Fields

Use Python enums for SELECT fields:

from enum import Enum

class Priority(str, Enum):
    LOW = "Low"
    MEDIUM = "Medium"
    HIGH = "High"
    URGENT = "Urgent"

class Status(str, Enum):
    PENDING = "Pending"
    IN_PROGRESS = "In Progress"
    COMPLETED = "Completed"
    CANCELLED = "Cancelled"

@airtable_model(table_name="Tasks")
class Task(BaseModel):
    title: str
    priority: Priority = Priority.MEDIUM
    status: Status = Status.PENDING

When you create the table, enum values become SELECT options automatically.


Complex Models

Nested Information

For related data, use string fields with JSON or structured text:

import json
from typing import Optional

@airtable_model(table_name="Projects")
class Project(BaseModel):
    name: str
    description: Optional[str] = None
    metadata: Optional[str] = None  # Store JSON as string

    def set_metadata(self, data: dict):
        """Store dict as JSON string"""
        self.metadata = json.dumps(data)

    def get_metadata(self) -> dict:
        """Parse JSON string to dict"""
        return json.loads(self.metadata) if self.metadata else {}

Multiple Models

Define multiple models for different tables:

@airtable_model(table_name="Users")
class User(BaseModel):
    name: str
    email: str
    department_id: Optional[str] = None

@airtable_model(table_name="Departments")
class Department(BaseModel):
    name: str
    manager: Optional[str] = None

@airtable_model(table_name="Tasks")
class Task(BaseModel):
    title: str
    assignee_id: Optional[str] = None  # Reference to User
    department_id: Optional[str] = None  # Reference to Department

Model Validation

Pydantic validation works normally:

from pydantic import BaseModel, EmailStr, Field, field_validator

@airtable_model(table_name="Users")
class User(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    email: str
    age: Optional[int] = Field(default=None, ge=0, le=150)

    @field_validator('email')
    @classmethod
    def validate_email(cls, v):
        if '@' not in v:
            raise ValueError('Invalid email format')
        return v.lower()
# Validation happens before Airtable operations
try:
    user = User.create(name="", email="invalid")
except ValueError as e:
    print(f"Validation error: {e}")

Model Configuration

Pydantic Config

Standard Pydantic configuration works:

from pydantic import ConfigDict

@airtable_model(table_name="Users")
class User(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True
    )

    name: str
    email: str

Extra Fields

By default, extra fields from Airtable are ignored. This is important because Airtable tables often have fields that your model doesn't define:

  • Auto-generated inverse LINKED_RECORD fields
  • Formula, Rollup, or Lookup fields
  • Fields added by other collaborators
@airtable_model(table_name="Users")
class User(BaseModel):
    name: str
    email: str

# If Airtable has extra fields like "Tasks" (inverse link) or "created_by",
# they are silently ignored when loading records
users = User.all()  # Works even if Airtable has more fields than the model

Creating Records

When creating records, you should only pass fields defined in your model. Extra fields passed to create() will be sent to Airtable but won't be validated by Pydantic.


Model Methods Summary

Class Methods

Method Description
create(**data) Create a new record
get(record_id) Get record by ID
all(**filters) Get all records
find_by(**filters) Find by field values
first(**filters) Get first match
bulk_create(data_list) Create multiple records
create_table() Create Airtable table
sync_table(**options) Sync schema to table

Instance Methods

Method Description
save() Save changes to record
delete() Delete the record

See CRUD Operations for detailed usage.


Best Practices

Do

  • Use descriptive field names for type detection
  • Define enums for fixed choice fields
  • Use Optional for truly optional fields
  • Add Pydantic validators for data integrity

Don't

  • Use generic names like field1, data
  • Mix different data types in the same field
  • Store complex nested objects (use separate tables)

Next Steps