Guild Multitenancy Architecture

Overview

Guild uses @payloadcms/plugin-multi-tenant with Organization as the tenant boundary. This was implemented in April 2026 on branch feat/multi-tenant-plugin.

Data Hierarchy

Organization (business entity, e.g. "Script Wizards")
  → 1 Stripe account (all subscriptions/billing)
  → N Shops (physical locations, e.g. "Dungeon Books JC")
      → each Shop has its own Square account/location
      → each Shop has its own Tiers (Stripe prices under the org's account)
      → Purchases, CheckIns, LoyaltyTransactions are shop-scoped
  → Members belong to the org, tied to a specific shop via their tier

Role System

Two-level roles:

  • Global: super-admin (platform operator) vs user (everyone else)
  • Per-tenant: admin (org owner like Phil) vs staff (employees)

Super-admins see all tenants. Users only see orgs they’re assigned to via the tenants array on the Users collection.

Payment Provider Mapping

LevelStripeSquare
Organization1 Stripe account, customer records, subscriptions
ShopTier prices (under org’s Stripe)1 Square location, POS transactions

Membership Model

  • Members are org-scoped (plugin injects organization field)
  • Members are implicitly shop-scoped through their tier (tiers belong to a shop)
  • A member at Shop A picks Shop A’s tiers. A member at Shop B picks Shop B’s tiers. Both under the same org’s Stripe account
  • Multi-shop membership (one subscription covers all locations) is a future product decision, not needed in the data model today

Domain Routing (planned)

Each shop will have a domain field. Next.js middleware resolves hostname → shop → org.

dungeon.club → Shop "Dungeon Books JC" → Org "Script Wizards"
club.victorypointjc.com → Shop "Victory Point JC" → Org "Victory Point"

Frontend pages (signup, dashboard, tiers) will read the resolved shop to show the right branding, lingo, and tiers.

Onboarding a New Org

  1. Create Organization in admin panel
  2. Create Shop under that org (with Square location ID)
  3. Create Tiers for the shop (with Stripe price IDs from org’s Stripe account)
  4. Create User with role: user, tenants: [{ tenant: orgId, roles: ['admin'], shop: shopId }]

Key Decisions

  • Organization is the tenant, not Shop — keeps admin boundary clean for multi-location owners
  • Plugin field named organization (tenantField.name: 'organization') to match existing field names and minimize code changes
  • No domain field on Organization — domain routing is at the shop level since shops are customer-facing
  • Points never transfer between shops — each shop has its own loyalty economy