A production-ready Elixir/Phoenix platform with authentication, authorization, multi-tenancy, and white-labeling built-in.
Extracted from a real production SaaS application, this platform provides enterprise-grade features out of the box, allowing you to focus on your domain-specific business logic rather than rebuilding common infrastructure.
- ✅ Multi-Tenancy - Schema-per-tenant isolation using PostgreSQL
- ✅ Authentication - Email/password with AshAuthentication
- ✅ Authorization - Role-Based Access Control (RBAC) with policies
- ✅ Multi-Organization Support - Users can belong to multiple organizations
- ✅ White-Labeling - Per-organization branding and subdomain management
- ✅ Three-Tier Hierarchy - Platform Owner → MSP → Client organizations
- ✅ Test Suite - Comprehensive test coverage with factories
| App | Purpose | Port |
|---|---|---|
core |
Ash Resources & Domain logic (multi-tenant, RBAC) | - |
web |
Public website (Phoenix + LiveView + LiveSvelte) | 5000 |
admin |
Admin portal (multi-org management) | 5001 |
client |
Client portal (tenant-scoped UI) | 5002 |
ui |
Shared UI components (daisyUI + Svelte) | - |
build_tools |
Custom Mix tasks and testing utilities | - |
dev_tools |
Development tools (E2E test runner UI) | - |
- Elixir 1.18+
- Erlang OTP 27+
- Phoenix 1.8.1
- Ash Framework 3.0+ (declarative resource framework)
- Phoenix LiveView 1.1.13 (server-rendered interactivity)
- LiveSvelte 0.16.0 (Svelte 5 components in LiveView)
- PostgreSQL 16+ (with schema-per-tenant multitenancy)
- TailwindCSS 4.1+ & daisyUI 5.1+ (styling)
# Install asdf (version manager)
# See: https://asdf-vm.com/guide/getting-started.html
# Install plugins
asdf plugin add elixir
asdf plugin add erlang
asdf plugin add nodejs
asdf plugin add postgres# 1. Install versions from .tool-versions
asdf install
# 2. Install dependencies
mix deps.get
# 3. Create and migrate databases
mix ash_postgres.create
mix ash_postgres.migrate
# 4. Start the Phoenix apps
mix phx.server
# Access:
# - Web: http://localhost:5000
# - Admin: http://localhost:5001
# - Client: http://localhost:5002# Run in iex -S mix
Core.Resources.Identity
|> Ash.Changeset.for_create(:register_with_password, %{
email: "[email protected]",
password: "SecurePassword123!",
password_confirmation: "SecurePassword123!",
role: :super_admin
})
|> Ash.create!()Located in apps/core/lib/core/resources/:
Authentication & Authorization:
Identity- Authentication credentials (public schema)User- User profiles (tenant-scoped)Organization- Organizations (global, with tenant schemas)OrganizationMembership- User-organization relationshipsToken- API tokens and session management
RBAC:
Role- Role definitions (tenant-scoped)Permission- Fine-grained permissions (tenant-scoped)UserRole- User-role assignments (tenant-scoped)
White-Labeling:
BrandingAsset- Logo, favicon, colors (per-organization)
Optional Analytics:
AIVisit- Track AI crawler visits (ChatGPT, Claude, etc.)
Schema-per-tenant isolation:
# Organizations create separate PostgreSQL schemas
org = Organization.create!(%{name: "Acme Corp", slug: "acme"})
# Creates schema: "org_acme"
# Tenant resources require schema context
User
|> Ash.Changeset.for_create(:create, %{email: "[email protected]"})
|> Ash.Changeset.set_tenant("acme") # ← Required for tenant data
|> Ash.create!()Global vs Tenant Resources:
- Global (
multitenancy { global? true }): Organizations, Identities - Tenant (
multitenancy { strategy :context }): Users, Roles, Permissions
See: docs/architecture/MULTI_ORGANIZATION_ARCHITECTURE.md
- User logs in with email/password
Identityverified in public schema- Session includes
organization_idfor tenant context Userprofile loaded from tenant schema- Policies check role and permissions
See: docs/architecture/AUTHENTICATION_ARCHITECTURE.md
# All tests
mix test
# Unit tests only (exclude E2E)
mix test.unit
# Re-run failed tests
mix test.quick
# Coverage report
mix coverage
# E2E tests with Playwright
mix test.playwrightSee: docs/testing/TESTING_WORKFLOW.md
# Format code
mix format
# Static analysis
mix credo --strict
# Type checking
mix dialyzer
# All quality checks
mix quality# Create databases
mix ash_postgres.create
# Run migrations
mix ash_postgres.migrate
# Drop databases
mix ash_postgres.drop
# Reset (drop + create + migrate)
mix ash_postgres.reset- Create resource in
apps/core/lib/core/resources/:
defmodule Core.Resources.BlogPost do
use Ash.Resource,
domain: Core.Domain,
data_layer: AshPostgres.DataLayer
postgres do
table "blog_posts"
repo Core.Repo
end
multitenancy do
strategy :context # Tenant-scoped
end
attributes do
uuid_primary_key :id
attribute :title, :string, allow_nil?: false
attribute :content, :string
timestamps()
end
actions do
defaults [:read, :destroy, create: [:title, :content], update: [:title, :content]]
end
end- Register in domain (
apps/core/lib/core/domain.ex):
resources do
# ... existing resources ...
resource(Core.Resources.BlogPost)
end- Generate migration:
mix ash_postgres.generate_migrations --name add_blog_posts- Add tests in
apps/core/test/core/resources/blog_post_test.exs
Update web app marketing content:
apps/web/lib/web_web/live/- LiveView pagesapps/web/assets/static/images/- Logo and imagesapps/web/assets/css/app.css- Brand colors
For portal branding (admin/client):
- Managed via
BrandingAssetresource - Upload logo, set colors per organization
- UI automatically applies white-label branding
See: docs/architecture/BRANDING_PATTERNS.md
Copy .env.example to .env and configure:
# Your primary domain (for white-label subdomains)
CLOUDFLARE_DEFAULT_DOMAIN=example.ai
# Cloudflare API (optional - for branded subdomain automation)
CLOUDFLARE_PRIMARY_ZONE_ID=your_zone_id
CLOUDFLARE_PRIMARY_API_TOKEN=your_api_token
# Production hosts
ADMIN_HOST=admin.example.com
CLIENT_HOST=client.example.com
WEB_HOST=example.com- Authentication - Two-resource pattern (Identity + User)
- Multi-Organization - Multi-tenant + multi-org support
- Branding Patterns - Static vs dynamic branding
- Branded Subdomains - DNS automation
- Testing Workflow - Comprehensive testing guide
- CLAUDE.md - Development guidelines for AI assistants
- AGENTS.md - Phoenix/LiveView patterns
- Web App - Public website patterns
- Admin Portal - Multi-org management
- Client Portal - Tenant-scoped UI
- Core App - Ash resources
The platform is deployment-ready with:
- Mix releases configured
- Docker support (TODO: add Dockerfile)
- Health checks on all endpoints
- Asset digesting for production
- Migration runner for zero-downtime deploys
See: docs/deployment/ for deployment guides.
This is an extracted platform base. When using it for your project:
- Fork or copy this repository
- Customize the domain resources for your use case
- Remove unused features (e.g., AIVisit if not tracking crawlers)
- Add your business logic in new resources
- Update branding and marketing content
This platform was extracted from a production SaaS application in January 2026. The extraction process involved:
- ✅ Removed domain-specific resources (vendor management, call logs)
- ✅ Removed domain-specific UI (asset navigator, billing pages)
- ✅ Genericized configuration (domains, branding)
- ✅ Cleaned up documentation
- ✅ Verified compilation and tests
See: docs/extraction/ for extraction details.
[TODO: Add your license here]
- Documentation: See
docs/directory - Issues: [TODO: Add issue tracker URL]
- Questions: [TODO: Add discussion forum or contact]
Built with Ash Framework - Declarative resource-oriented application framework for Elixir.