Skip to content
v3.0.1 — MCP OAuth, organizations, passkeys, SSR adaptersChangelog
Convex Auth
Esc
↑↓navigate↵open⌘Jpreview
On this page

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, and imageUrl
  • a set of members with a roleKey
  • pending invitations with an expiresAt and roleKey
  • 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

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:

  1. Calling the component writer to insert the org.
  2. Creating or ensuring default roles.
  3. 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, suspendMember
  • listRoles, 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 — the components.convexAuthOrganizations handle.
  • resolveLocalOrganizationId and resolveLocalUserId — bridge lookup callbacks.
  • roleCatalog — a roleKey → 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.

Was this page helpful?