feather check helps by
failing on a tenant-scoped model queried without a tenant filter, but it checks the query
shape rather than your intent.
What you’ll have
- Users assigned to a company automatically, by email domain
- Boards, columns, cards and attachments isolated per company
- A tenant admin who manages users inside their own company
- A platform admin who creates companies but doesn’t touch their user lists
- Files in cloud storage separated by tenant as well
Which parts of Feather this uses
Multi-tenancy
TenantScopedMixin, get_current_tenant_id() and require_same_tenant().The two-axis model
Tenant role and platform authority are separate, and admin doesn’t cross companies.
feather check
The
tenant-isolation rule turns a missed filter into a failed build.File storage
Tenant IDs go in the storage path, so buckets are isolated too.
Step 1: Scaffold
Multi-tenancy changes the user model and the admin panel, so start fresh again.
There’s no database type prompt this time. Multi-tenant apps require PostgreSQL.
feather db migrate -m "Initial migration" and feather db upgrade first.
The scaffold is meaningfully different from part 4’s:
Step 2: Run the prompt
The prompt
Step 3: Test it
Clicking through your own account proves nothing here. You need two, on different email domains.1
Sign in as the platform admin
Create a board, open it, add columns and cards. Then go to Admin, then Tenants.
2
Create a tenant
Name “Acme Corp”, slug “acme”, domain “acme.com”, set to Active.
3
Sign in as an acme.com user in a private window
They land on the pending approval page.
4
Promote them to tenant admin
As platform admin: Admin, Tenants, Acme Corp, Users. Set their role to admin and
activate them.
5
Create boards as the Acme user
Then confirm the platform admin’s dashboard doesn’t show them, and the Acme user’s
doesn’t show the platform admin’s.
6
Try to cross the line
Copy a board ID from one account and open
/kanban/<that-id> from the other. You want
a 403, not a board.tenant-isolation errors is the result that matters here.
What the framework did
Isolation is declarative at the model.TenantScopedMixin added tenant_id and the
for_tenant() query. Columns and cards don’t carry one, because their owner is reachable
through the board, and duplicating it would mean two sources of truth that can disagree.
require_same_tenant() raises rather than filters. A filter that silently returns
nothing looks identical to a genuinely empty result, which hides the bug. An exception
becomes a 403 you can see in the logs. See
Multi-tenancy.
The linter is the backstop. feather check’s tenant-isolation rule catches a query
on a tenant-scoped model with no tenant filter, so the mistake fails CI rather than
shipping. See feather check.
Approval belongs to the tenant. Platform admins create companies.
Each company’s admin approves its own users. That’s what keeps a platform operator out of
a customer’s user list, and it’s a product decision the framework encodes rather than
leaves to you.
Next: put it on the internet
A VPS, automatic TLS, backups, and a deploy on every push.