RBAC (Roles & Permissions)

Manage roles, permissions, and access scopes.

Role-based access control (RBAC) lets you define who can do what in an organization.

New to RBAC?

If you're new to the RBAC concept, a simple mental model is:

  • Users belong to organizations.
  • Users get roles.
  • Roles map to permissions on resources.

In TurboStarter, we primarily rely on the Better Auth plugin for the heavy lifting—roles, permissions, teams, and member management—while handling critical logic with our own code.

This provides a flexible access control system, letting you control user access based on their role in the organization. You can also define custom permissions per role.

Everything is configured out of the box!

TurboStarter ships with the default RBAC system configured out of the box. This setup may be enough if you're not planning a very complex access control system, but you can also easily customize it to your needs.

On mobile, use conditional UI (disable or hide actions) together with client helpers to match each member's role.

Roles

Roles are named bundles of permissions. Keep them few and well-defined. By default, we have the following roles:

const MemberRole = {
  MEMBER: "member",
  ADMIN: "admin",
  OWNER: "owner",
} as const;

A user can have multiple roles in an organization. For example, a user can be a member and an admin (if it makes sense for your application).

Don't confuse organization admin with super admin

The organization's admin role is different from the user's global admin role.

The organization admin governs permissions only inside the organization, whereas the global admin controls access to the super admin dashboard.

To create additional roles with custom permissions, see the official documentation for more details.

Permissions

Permissions represent what actions a role can perform on which resources.

To check if the current user has permission to perform an action on mobile, use the client helper and handle the boolean result in your component logic.

create-project.tsx
import { useQuery, useMutation } from "@tanstack/react-query";
import { authClient } from "~/lib/auth";

export function CreateProject() {
  const { data: canCreate } = useQuery({
    queryKey: ["permission", "project", "create"],
    queryFn: () =>
      authClient.organization.hasPermission({
        permissions: { project: ["create"] },
      }),
  });

  const { mutate, isPending } = useMutation({
    mutationFn: async () => {
      // perform the create action
    },
  });

  return (
    <Button
      disabled={canCreate === false}
      loading={isPending}
      onPress={() => canCreate && mutate()}
    >
      Create
    </Button>
  );
}

When you already have the active member's role, prefer the client-side checkRolePermission to avoid extra API calls.

update-project.tsx
import { authClient } from "~/lib/auth";

export function UpdateProject() {
  const activeMember = authClient.useActiveMember();

  const canUpdate = authClient.organization.checkRolePermission({
    permission: {
      project: ["update"],
    },
    role: activeMember.role,
  });

  return <Button disabled={!canUpdate}>Update</Button>;
}

We leverage the existing hook to retrieve the active member role within the active organization context. That way, you can easily check whether a member has permission to perform an action without a server round trip.

This does not include any dynamic roles or permissions because everything runs synchronously on the client-side. Use the hasPermission APIs to include checks for dynamic roles and permissions.

If you need to add more granular permissions to existing roles, or create new ones, use the createAccessControl API.

For further customization—such as dynamic access control, lifecycle hooks, or team management—see the guidance in the official documentation and the web guide.

How is this guide?

Last updated on

On this page

Ship your startup everywhere. In minutes.Get TurboStarter