Skip to content

Data Model

Grant stores all data in PostgreSQL using Drizzle ORM. The database schemas in packages/@grantjs/database/src/schemas/ are the single source of truth — the GraphQL types in @grantjs/schema are generated from them.

Entities

EntityPurposeKey relationships
UserA person who can log inOwns accounts, belongs to organizations and projects via pivots
AccountPerson-centric identity (personal or organization)Owned by a user; links to projects via account_projects
OrganizationBusiness entity that groups projects and membersContains projects and users via pivot tables
ProjectIsolated environment for managing external identitiesContains resources, users, roles, groups, permissions, API keys, signing keys, apps
Project AppOAuth/consent application in a projectBelongs to a project; tags via project_app_tags; scopes and redirect URIs
ResourceDomain entity defined by an external system (e.g. invoice, order, policy)Belongs to a project; permissions are scoped to resources
RoleNamed collection of groupsAssigned to users via user_roles; contains groups via role_groups
GroupCollection of permissionsLinked to roles via role_groups; may also attach directly to users via user_groups
PermissionA specific action on a resource (e.g. user:read)Belongs to groups; linked to a resource
TagFlexible label for categorizationApplied to users, roles, groups, permissions, organizations, and projects via pivot tables
API KeyProgrammatic access credential scoped to a projectBelongs to a user and a project; exchanged for a JWT
Signing KeyRSA key pair for JWT signing (system or per-project)Scoped to system or a project; exposed via JWKS

Entity Relationships

The diagrams below are architect-facing EERs. They intentionally omit audit-log tables and tag pivots — every mutable entity has a corresponding *_audit_logs table, and tags are an orthogonal labeling layer (see Tagging).

Diagram A — Core authorization

Tenant hierarchy plus the RBAC chain. The isolation boundary for authorization data is the Project; permission checks resolve as a union of paths that end at action + resource.

Many-to-many links are drawn as pivot entities (named after the real tables) so every relationship is visible — Mermaid’s bare }o--o{ edges often drop when entities have attributes.

A1 — Tenant hierarchy

A2 — RBAC chain and project scope

Pivot tables use soft-delete-aware unique constraints (deleted_at IS NULL). Project scope also adds attachments such as project_user_groups and project_user_permissions (same pattern; omitted here for readability).

Authorization resolution unions these paths before matching action + resource:

User → Role → Group → Permission → Resource
User → Group → Permission → Resource
User → Role → Permission → Resource
User → Permission → Resource

Diagram B — Supporting systems

Integrations and operational entities that hang off a project (and sometimes a user). API keys attach through scope-specific pivots (account_project_api_keys, organization_project_api_keys, project_user_api_keys) rather than a single FK — shown here as scoped edges.

Tenant Hierarchy

Data is organized in three levels. Each level provides full isolation — there is no data leakage between accounts, and projects within an organization are independent of each other.

Account (top-level tenant)
 └── Organization (business entity, groups projects and members)
      └── Project (isolated environment with its own users, roles, resources)
  • Account — A person's identity. One user can own multiple accounts (personal and organization types). Accounts can switch context without re-authenticating.
  • Organization — Groups related projects and team members. Users can belong to multiple organizations.
  • Project — A fully isolated environment. Each project manages its own users, roles, groups, permissions, resources, API keys, signing keys, and apps independently.

TIP

For the full isolation model including Row-Level Security, see Multi-Tenancy.

RBAC Chain

Permissions are evaluated through a union of paths:

User → Role → Group → Permission → Resource
User → Group → Permission → Resource
User → Role → Permission → Resource
User → Permission → Resource

A user is assigned roles and may have direct group or permission attachments. Each role contains groups and may have direct permissions; each group bundles permissions. Grant resolution unions all sources before matching action + resource.

TIP

For the complete permission model, evaluation flow, and standard roles, see RBAC.

Tagging

Tags provide a generic labeling system for organizing and filtering entities. A tag can be applied to any of the following via a dedicated pivot table:

  • Users, Roles, Groups, Permissions, Organizations, Projects, Project Apps

Tags are scoped to the same tenant as the entity they are applied to.

Audit Logging

Every entity has a corresponding *_audit_logs table that records a complete change history. Audit records capture the old and new values, the action performed, and who performed it.

EventDescription
CREATEEntity created
UPDATEEntity modified
DELETEEntity soft-deleted
RESTOREEntity restored
ASSIGNRole or permission assigned
REVOKERole or permission revoked

Common Patterns

All tables in the schema share these conventions:

  • UUID primary keys — Generated with gen_random_uuid()
  • Soft deletesdeleted_at column; records are never physically removed
  • Timestampscreated_at and updated_at on every table
  • Composite unique indexes — Pivot tables enforce uniqueness only where deleted_at IS NULL

Schema references

  • Drizzle schemas: packages/@grantjs/database/src/schemas/
  • GraphQL types: packages/@grantjs/schema/src/generated/
  • Migrations: pnpm --filter @grantjs/database db:generate and db:migrate
  • Permission evaluation benchmarks: Benchmark report

Next: Learn about Security to understand authentication and session management.

Released under the MIT License.