Organizations
B2B workspaces, members, invitations, and roles with convex-auth.
convex-auth supports multi-tenant organizations natively in Convex. Users, organizations, memberships, invitations, and roles all live in the convexAuth component tables. The @vortex-api/convex-auth/react package ships uncontrolled UI components that you wire to your own Convex queries and mutations.
How it works
Organizations are stored as first-class data in the convexAuth component. Every organization has:
- a
name,slug, andimageUrl - a set of members with a
roleKey - pending invitations with an
expiresAtandroleKey - customizable role templates with permissions
Your app keeps local anchor rows (for example, a workspaces table) that map one-to-one to the component organization rows. The createConvexAuthOrganizationOperations factory returns typed reader/writer helpers that keep the component data and your local data in sync.
Mount the organizations component
If you only need the core auth runtime, you do not have to mount this. If you need orgs, mount it alongside the core component:
import { defineApp } from "convex/server";
import { v } from "convex/values";
import authCore from "@vortex-api/convex-auth/convex.config/core";
import authOrganizations from "@vortex-api/convex-auth/convex.config/organizations";
const app = defineApp({
env: {
JWT_PRIVATE_KEY: v.string(),
JWKS: v.string(),
},
});
app.use(authCore, {
env: {
JWT_PRIVATE_KEY: app.env.JWT_PRIVATE_KEY,
JWKS: app.env.JWKS,
},
});
app.use(authOrganizations, {
env: {
JWT_PRIVATE_KEY: app.env.JWT_PRIVATE_KEY,
JWKS: app.env.JWKS,
},
});
export default app;
React components
ConvexCreateOrganization
A controlled form for name, slug, and logo. Calls your onCreate callback.
ConvexOrganizationList
Lists a user’s organizations and pending invitations.
ConvexOrganizationSwitcher
A dropdown to switch between workspaces.
ConvexOrganizationProfile
Edits the org name, slug, and image.
ConvexOrganizationMembers
Invites members, lists members, and manages member status.
ConvexOrganizationRoleManager
Creates and lists custom roles with permission checklists.
Create an organization
import { ConvexCreateOrganization } from "@vortex-api/convex-auth/react";
import { useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
export function CreateOrganization() {
const create = useMutation(api.organizations.createOrganization);
return (
<ConvexCreateOrganization
onCreate={async ({ name, slug, imageUrl }) => {
await create({ name, slug });
}}
defaultName="Acme"
defaultSlug="acme"
/>
);
}
ConvexCreateOrganization calls onCreate with { name, slug, imageUrl? }. The component validates the slug format and required fields. Your createOrganization mutation is responsible for:
- Calling the component writer to insert the org.
- Creating or ensuring default roles.
- Creating an owner membership and setting the active organization.
Switch workspaces
import { ConvexOrganizationSwitcher } from "@vortex-api/convex-auth/react";
import { useMutation, useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
export function WorkspaceSwitcher() {
const organizations = useQuery(api.organizations.listMyOrganizations) ?? [];
const active = useQuery(api.organizations.getActiveOrganization);
const setActive = useMutation(api.organizations.setActiveOrganization);
const createOrganization = useMutation(api.organizations.createOrganization);
return (
<ConvexOrganizationSwitcher
organizations={organizations}
currentOrganizationId={active?._id ?? null}
currentOrganization={active ?? null}
onSelectOrganization={async (id) => await setActive({ organizationId: id })}
onInPlaceCreateOrganization={async (name) => {
await createOrganization({ name });
}}
/>
);
}
Members and roles
import {
ConvexOrganizationMembersSurface,
ConvexOrganizationRoleManagerSurface,
useConvexOrganizationRefs,
} from "@vortex-api/convex-auth/react";
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
export function WorkspaceSettings() {
const active = useQuery(api.organizations.getActiveOrganization);
const refs = useConvexOrganizationRefs(api);
if (!active) return null;
return (
<>
<ConvexOrganizationMembersSurface
organizationId={active._id}
roleOptions={["owner", "admin", "manager", "member", "viewer"]}
refs={refs.members}
/>
<ConvexOrganizationRoleManagerSurface canCreateRoles refs={refs.roles} />
</>
);
}
useConvexOrganizationRefs(api) maps the consumer’s api.organizations.* functions to the refs the presentational components expect. The consumer functions must match the names and shapes used in the React demo:
listMembers,inviteMember,reactivateMember,setMemberRole,suspendMemberlistRoles,listPermissions,createRole
See examples/react/convex/organizations.ts for a working reference implementation.
Run the example
The React demo lives in examples/react. To see the full workspace flow locally:
pnpm dlx convex dev
pnpm --filter examples/react dev
Then sign up, create a workspace, invite a second account, and switch members/roles.
Server-side operations
The convex-auth component exposes the organization tables, but the public helpers are low-level. For a working consumer, see examples/react/convex/organizations.ts.
If you are building your own server surface, import createConvexAuthOrganizationOperations from @vortex-api/convex-auth/convex and pass it:
component— thecomponents.convexAuthOrganizationshandle.resolveLocalOrganizationIdandresolveLocalUserId— bridge lookup callbacks.roleCatalog— aroleKey → permissions[]map.validateRoleKey— a type guard for your consumer role keys.
It returns a typed suite of read and write functions for organizations, members, invitations, roles, and API keys. Use these inside your own Convex actions and mutations.