A fully working multi-tenant SaaS demo built with SvelteKit and the Svelte 5 Context API. Tenant branding, feature flags, and role-based access control — all driven by context, zero prop-drilling.
acme.localhost) or a ?tenant= query param; all isolation happens in a single server hook before any load function runsfree, pro, and enterprise plans control which features are visible per tenantmember, admin, owner, and superadmin roles gate different parts of the UI; the platform superadmin can see all customer tenants across the platform| Layer | Technology |
|---|---|
| Framework | SvelteKit 2 + Svelte 5 |
| Language | TypeScript 5 |
| Bundler | Vite 7 |
| Icons | Lucide Svelte |
| Package manager | pnpm |
# 1. Clone the repo
git clone https://github.com/stanskrivanek/sv-saas-mt.git
cd sv-saas-mt
# 2. Install dependencies
pnpm install
# 3. Start the dev server
pnpm dev
Open http://localhost:5173 — you'll land on the Acme Corp tenant by default.
Use ?tenant=<slug> or a subdomain — both work out of the box (*.localhost resolves automatically in modern browsers, no /etc/hosts needed).
| Password | Role | Tenant | Plan | |
|---|---|---|---|---|
| admin@platform.com | password123 | superadmin | SaaS Platform | — |
| alice@acme.com | password123 | owner | Acme Corp | enterprise |
| bob@acme.com | password123 | member | Acme Corp | enterprise |
| carol@globex.com | password123 | owner | Globex Inc | pro |
| dave@initech.com | password123 | owner | Initech LLC | free |
Query param:
http://localhost:5173?tenant=acmehttp://localhost:5173?tenant=globexhttp://localhost:5173?tenant=initechSubdomain (no setup needed):
http://platform.localhost:5173 — superadmin loginhttp://acme.localhost:5173http://globex.localhost:5173http://initech.localhost:5173Cross-tenant credentials are silently rejected — each account only works on its own tenant's URL.
Request → hooks.server.ts
├── Resolve tenant from subdomain / ?tenant= param
├── Validate session cookie against that tenant's users
└── Attach tenant + session to event.locals
Load functions → read event.locals, pass data to the layout
Root layout → set Svelte 5 context (tenant, session, features, theme)
Components → getTenantCtx() / getSessionCtx() / getFeaturesCtx()
— no prop drilling, fully reactive
src/
├── hooks.server.ts # Tenant + session resolution
├── app.d.ts # Locals type augmentation
├── lib/
│ ├── components/ # Shared UI components
│ │ ├── AppHeader.svelte
│ │ ├── AppSidebar.svelte
│ │ ├── AdminNav.svelte
│ │ ├── RequireFeature.svelte
│ │ ├── StatsCard.svelte
│ │ └── TenantLogo.svelte
│ ├── context-helpers.ts # Typed getContext() wrappers
│ ├── types/context.ts # Shared TypeScript interfaces
│ └── server/db.ts # In-memory DB + seed data
└── routes/
├── +page.svelte # Landing / demo hub
├── login/ # Login form (tenant-scoped)
├── logout/ # Session cleanup
├── (app)/dashboard/ # Authenticated app shell
├── (admin)/admin/panel/ # Superadmin — all tenants view
└── (admin)/admin/members/ # Admin/owner — tenant members view
pnpm dev # Start dev server
pnpm build # Production build
pnpm preview # Preview production build
pnpm check # Svelte type-check
pnpm lint # Prettier + ESLint
pnpm format # Auto-format with Prettier