Skip to main content
SnackBase uses a shared database, row-level isolation multi-tenancy model. This guide explains how accounts work, how data is isolated, and what you need to know when building multi-tenant applications.

Overview

SnackBase enables Software-as-a-Service (SaaS) applications by allowing multiple independent tenants (accounts) to coexist in a single database while maintaining complete data isolation.

Key Characteristics

Account Model

What is an Account?

An Account (also called a “tenant” or “organization”) represents an isolated workspace containing:
  • Users who belong to the account
  • Collections (data schemas) defined for the account
  • Records (actual data) created by the account’s users
  • Roles and permissions specific to the account
  • Groups for organizing users
  • Configuration overrides for providers (auth, email, storage)

Account Hierarchy

Data Isolation

How Isolation Works

Most tables in SnackBase include an account_id column that references the accounts table:

Tables WITHOUT account_id (Global Tables)

The following tables do not have an account_id column because they define global structures shared by all accounts:

Automatic Filtering

SnackBase automatically filters all queries by account_id. Users never see data from other accounts. Example API Request:
The user doesn’t need to specify account_id—it’s automatically added based on their authentication context.

Enforcement Layers

Isolation is enforced at multiple layers for defense-in-depth:

Two-Tier Architecture

SnackBase uses a two-tier table architecture that’s critical to understand:

Tier 1: Core System Tables

These tables define the platform structure and are shared across all accounts: Important: Schema changes to these tables only happen via SnackBase releases.

Tier 2: User-Created Collections

User collections are single physical tables shared by ALL accounts: Critical Concept: When you create a collection named “posts”, you’re creating:
  1. A schema definition in the collections table (metadata)
  2. A physical table named col_posts (if it doesn’t exist)
  3. All accounts’ post data goes into this single shared table

Physical Table Naming Convention

Collection tables are prefixed with col_ to avoid conflicts with system tables: This prefix:
  • Prevents naming conflicts with system tables
  • Makes it clear which tables are user-created collections
  • Allows easy identification of collection tables in database dumps

Why This Architecture?

Account Identifiers

Accounts have three distinct identifiers that serve different purposes:

Identifier Comparison

Account ID (UUID)

The internal primary key for accounts is a standard UUID:
  • Purpose: Primary key, used in foreign key references
  • Format: Standard UUID v4 (36 characters)
  • Used by: account_id columns in all tenant-scoped tables
  • Human-readable: No (designed for systems, not humans)

Account Code (XX####)

The human-readable identifier for accounts:
  • Letters (XX): Random uppercase letters A-Z
  • Digits (####): Sequential number starting from 0001
  • Total Capacity: 6,760,000 unique codes (26×26×10,000)
  • Reserved Range: SY#### (skipped during generation)

Account Code Generation

Account codes are generated sequentially from the highest existing code:
Important Notes:
  • Codes are never reused
  • Sequential generation ensures predictability
  • SY#### range is permanently reserved
  • System account uses SY0000

Identifier Usage

System Account vs User Accounts

System Account (SY0000)

The system account is a special reserved account for superadmin operations: Superadmin users are linked to the system account and have:
  • Access to ALL accounts
  • Ability to create/manage accounts
  • Ability to manage global collections
  • System-wide visibility (can pass account_id=None to see all data)

User Accounts

User accounts are regular tenant accounts created by superadmins: Regular users (even with “admin” role) are linked to a specific account and have:
  • Access ONLY to their account
  • No cross-account visibility
  • Full CRUD within their account (based on permissions)

Multi-Account Users

Enterprise Multi-Account Model

SnackBase supports enterprise multi-account scenarios where a single user can belong to multiple accounts with different roles and permissions.

User Identity

A user’s identity is defined by the (email, account_id) tuple:
Key Point: The same email ([email protected]) can exist in multiple accounts with different roles.

Password Scope

Passwords are per-account, not per-email. This means:
  • [email protected] in account AB1001 has password Password1!
  • [email protected] in account XY2048 has password Password2!
  • These are different credentials even though the email is the same

Login Flow

When logging in, users must specify their account: Option 1: Account in URL
Option 2: Account in Request Body

Configuration Hierarchy

SnackBase uses a hierarchical configuration model for provider settings (authentication, email, storage, etc.):

Two-Level Hierarchy

Configuration Resolution

When resolving a provider configuration:
  1. Check account-level config for the specific account
  2. If not found, use system-level default
  3. Merge with fallback values for any missing keys

Use Cases

Key Points

  • System-level configs use the nil UUID (00000000-0000-0000-0000-000000000000)
  • Account-level configs use the account’s UUID as account_id
  • Resolution is cached for 5 minutes for performance
  • Built-in providers are marked with is_builtin flag (cannot be deleted)

Implications for Developers

When Building Applications

Understanding multi-tenancy is critical when building on SnackBase:

1. Never Store Account ID Manually

2. Account Isolation is Automatic

You don’t need to write WHERE clauses for account filtering:

3. Cross-Account Queries Are Impossible

By design, you cannot query across accounts:
Superadmin Exception: Superadmins can explicitly pass account_id=None to bypass filtering:

4. Collections Are Global

When creating a collection, remember:
  • The collection schema is shared across ALL accounts
  • The physical table (col_<name>) is shared across ALL accounts
  • Each account only sees their own data (via account_id filtering)

5. Migrations Affect All Accounts

Database migrations affect ALL accounts simultaneously:
Always test migrations thoroughly before deploying!

6. Use Account Code for Display

When displaying account identifiers in UI or logs:

Summary