Extending IdentityPlanKit Models

This guide shows how to extend IdentityPlanKit with custom models for your application.

Quick Start (New Project)

1. Project Structure

my_app/ ├── alembic/ │ ├── versions/ │ └── env.py ├── alembic.ini ├── models/ │ ├── __init__.py │ └── profile.py # Your custom models ├── main.py └── .env

2. Create Custom Models

Use IdentityPlanKit's BaseModel which provides UUID primary keys and timestamps:

# models/profile.py
from uuid import UUID
from sqlalchemy import ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from identity_plan_kit.shared.models import BaseModel

class UserProfile(BaseModel):
    """Extended user profile - separate table linked to IPK users."""
    __tablename__ = "user_profiles"

    user_id: Mapped[UUID] = mapped_column(
        ForeignKey("users.id", ondelete="CASCADE"),
        unique=True,
        index=True,
    )
    phone: Mapped[str | None] = mapped_column(String(20))
    bio: Mapped[str | None] = mapped_column(Text)
    company: Mapped[str | None] = mapped_column(String(255))

3. Configure Alembic

alembic/env.py:

from identity_plan_kit.shared.database import Base

# Import IPK models
from identity_plan_kit.auth.models import user, user_provider, refresh_token
from identity_plan_kit.rbac.models import role, permission, role_permission
from identity_plan_kit.plans.models import plan, feature, plan_limit, user_plan, feature_usage

# Import YOUR models
from models.profile import UserProfile

target_metadata = Base.metadata

4. Run Migrations

# Apply IPK base tables first
ipk db upgrade

# Generate migration for your models
alembic revision --autogenerate -m "Add user profiles"

# Apply your migration
alembic upgrade head

5. Use in Your App

from fastapi import FastAPI
from sqlalchemy import select
from identity_plan_kit import IdentityPlanKit, IdentityPlanKitConfig, CurrentUser
from models.profile import UserProfile

config = IdentityPlanKitConfig()
kit = IdentityPlanKit(config)
app = FastAPI(lifespan=kit.lifespan)
kit.setup(app)

@app.post("/profile")
async def create_profile(user: CurrentUser):
    async with kit.session_factory() as session:
        profile = UserProfile(user_id=user.id, bio="Hello!")
        session.add(profile)
        await session.commit()
        return {"id": str(profile.id)}

@app.get("/profile")
async def get_profile(user: CurrentUser):
    async with kit.session_factory() as session:
        result = await session.execute(
            select(UserProfile).where(UserProfile.user_id == user.id)
        )
        profile = result.scalar_one_or_none()
        return profile or {"error": "No profile"}

Adding to Existing Project

If You Have Existing Auth Tables

Create a link table to connect IPK users to your legacy users:

class UserLegacyLink(BaseModel):
    __tablename__ = "user_legacy_links"

    ipk_user_id: Mapped[UUID] = mapped_column(
        ForeignKey("users.id", ondelete="CASCADE"),
        unique=True,
    )
    legacy_user_id: Mapped[int] = mapped_column(
        ForeignKey("legacy_users.id", ondelete="CASCADE"),
        unique=True,
    )

Then migrate existing users with a data migration.

If You Have No Auth Tables

Just install IPK and reference its users table:

class Project(BaseModel):
    __tablename__ = "projects"

    name: Mapped[str] = mapped_column(String(255))
    owner_id: Mapped[UUID] = mapped_column(ForeignKey("users.id"))

Best Practices

  1. Use separate tables - Don't modify IPK's core tables
  2. Use ForeignKey constraints - Link to IPK's users, roles, plans tables
  3. Use BaseModel - Get UUID primary keys and timestamps automatically
  4. Run IPK migrations first - Always run ipk db upgrade before your own migrations
  5. Test on staging - Never run untested migrations on production

Complete Example

See extension_example.py for a full working example with: