Comment on page
🏢
Organizations & Multitenancy
We have provided a basic structure for organizations, which are basically groups of people. Often user want to create some kind of organization (eg. a company), and then invite their teammates into it. This is a step towards multi-tenancy, with an organization representing a tenant.
If you were introducing a payment system, it would likely be associated with the org (we will implement this in v1.3.0.

A look at how the data is organized
The following routes are provided:
live "/org/new", NewOrgLive
live "/org/:org_slug", OrgDashboardLive
live "/org/:org_slug/edit", EditOrgLive
live "/org/:org_slug/team", OrgTeamLive, :index
live "/org/:org_slug/team/invite", OrgTeamLive, :invite
live "/org/:org_slug/team/memberships/:id/edit", OrgTeamLive, :edit_membership
This provides some basic functionality to get you started:
- Any user can CRUD an org
- Basic roles (
admin
&member
) - Members can create invitations for new members
- Invitations turn into memberships upon acceptance
- Admins can delete memberships
We have some plugs to help with assigning org related data
This will assign to the conn:
Assigns | When | Description |
---|---|---|
@orgs | Always | A list of orgs the current_user is in |
@current_membership | Only if :org_slug is in the route | The current user's membership with the current org (using the provided slug). |
@current_org | Only if :org_slug is in the route | The current org (using the provided slug) |
This makes sure the current_user is a member of the current org (determined by the URL param
:org_slug
). The path must have :org_slug
in it.Same as
require_org_member
above but the user must have a role of admin on their membership for the org.You can run
on_mount
hooks in your live_session
calls. For example:live_session :require_confirmed_user,
on_mount: [
{PetalProWeb.UserOnMountHooks, :require_confirmed_user},
{PetalProWeb.OrgOnMountHooks, :assign_org_data}
] do
live "/org/new", NewOrgLive
# page_builder:live:protected
end
Currently you can only see your current org if you are in a route scoped to that org (
/:org_slug/page
), with the org slug representing the tenant (org).There are several options when it comes to identifying the current org (listed below). Please let us know if you want more options supported. We have a feedback form here.
Load the org from a body or query param (how it's currently done).
/org/my-org/path_x
/org/my-org/path_y
/org/my-org/path_z/:some_param
Set the current org as a cookie and fetch it in the plug from the cookie. Then you don't have to scope the route:
/path_x
/path_y
/path_z/:some_param
You will need to create a plug that sets the current org - you could use
set_locale_plug.ex
as inspiration.Set the tenant slug as a subdomain. Similar to "params" but means you don't have to scope all of your routes.
my-org.yourdomain.com/path_x
my-org.yourdomain.com/path_y
my-org.yourdomain.com/path_z/:some_param
You would need to create a plug to do this.
Currently, all your orgs (tenants) data is in one database. It is up to you to scope all of your Ecto queries to ensure the data shown on the page belongs to the right org.
A potentially safer option is to make a new Postgres Schema per tenant with the help of Triplex. In which case your queries will be like this:
Repo.all(User, prefix: Triplex.to_prefix("my_tenant"))
Repo.get!(User, 123, prefix: Triplex.to_prefix("my_tenant"))
Triplex provides some plugs to help set the org:
- `Triplex.ParamPlug` - loads the tenant from a body or query param
- `Triplex.SessionPlug` - loads the tenant from a session param
- `Triplex.SubdomainPlug` - loads the tenant from the url subdomain
- `Triplex.EnsurePlug` - ensures the current tenant is loaded and halts if not
You will have to extract orgs out of your code. This will take approximately 15 minutes. If you have time, consider letting us know at [email protected] if you do this so we can get an idea of how many people are doing it (if it's a lot, then we will consider making it easier to delete).
Delete the migrations:

Delete files and folders related to orgs:

Remove references in
user.ex
and log.ex:


From here, you can just do a global search for "org" and delete what's left.
Accounts.preload_org_data/2
.- Router org routes.
Router.assign_org_data/2
plug.- Reference in
Logs.build/2
. - Org related logs in
log.ex
-@action_options
UserNotifier.deliver_org_invitation/3
- Delete
org_seeder.ex
- References in
seeds.exs
- References in
email_testing_controller.ex
- References in
email.ex
- Email template:
org_invitation.html.heex
- Test folder:
org_team_live_test.exs
orgs_fixtures.ex
dashboard_live_test.exs
Last modified 6mo ago