Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/guides/billing/for-b2b.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ You can create a pricing page by using the [`<PricingTable />`](/docs/reference/

You can use Clerk's features, plans, and permissions to gate access to content using [**authorization checks**](/docs/guides/secure/authorization-checks). There are a few ways to do this, but the recommended approach is to either use the [`has()`](/docs/reference/backend/types/auth-object#has) method or the [`<Protect>`](/docs/reference/components/control/protect) component.

Permission-based authorization checks link with feature-based authorization checks. This means that if you are checking a custom permission, it will only work if the feature part of the permission key (`org:<feature>:<permission>`) **is a feature included in the organization's active plan**. For example, say you want to check if an organization member has the custom permission `org:teams:manage`, where `teams` is the feature. Before performing the authorization check, you need to ensure that the user's organization is subscribed to a plan that has the `teams` feature. If the user's organization is not subscribed to a plan that has the `teams` feature, the authorization check will always return `false`, even if the user has the custom permission.
> [!IMPORTANT]
> Permission-based authorization checks link with feature-based authorization checks. This means that a permission check will only work if the feature part of the permission key (`org:<feature>:<permission>`) **is a feature included in the organization's active plan**. For example, say you want to check if an organization member has the custom permission `org:teams:manage`, where `teams` is the feature. Before performing the authorization check, you need to ensure that the user's organization is subscribed to a plan that has the `teams` feature. If not, the authorization check will always return `false`, even if the user has the custom permission.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't true, because you can assign a feature permission to a role, and give a user that role, and they will have that permission - regardless of if the organization pays for a subscription plan/feature or not.

i can try to help rewrite this - let me draft something

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a messaging that was already there - I just made it into a callout. But from my discussion with @royanger, I understood the above to be true?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naurrr, that may be only if billing is enabled?
i have a fresh app, enabled orgs, and then assigned a custom permission to a role, and that role to a user. then i did a has() check for it, and it worked.
no billing / plans required.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so i've played around with this - i have a custom permission invoices:create. i enabled billing, created an organization plan, added the invoices feature to it. i have one user whose active organization is subscribed to the plan, so they have that permission because their org plan has that feature. i have one user whose active organization is not subscribed to the plan, but they have the permission through their role. and i'm not getting any issues with authorization checks. so maybe this information doesn't ring true anymore? i see that @mzhong9723 added it in this PR: https://github.com/clerk/clerk-docs/pull/2263/files - maybe she can give us some insight on why she added it!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naurrr, that may be only if billing is enabled?

Yes, this is in the billing guide, so I'm guessing this limitation only applies when billing is enabled. But yeah interested to hear your thoughts @mzhong9723 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The correct behavior if billing is enabled should be that the features that an organization member receives should only come from the active organization plan.

i have one user whose active organization is not subscribed to the plan, but they have the permission through their role.

So this scenario seems like a bug 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just set up the following scenario:

  • user is an admin in two organizations: testorg and testorg2
  • billing is enabled, testorg has active plan expensive_org. testorg2 has plan expensive_org_copy
  • expensive_org doesn't have any features, expensive_org_copy has two features: teams and dashboard
  • role org:admin has permissions for the teams and dashboard features

Here's what I see from the user's org memberships:

testorg org memberships only has system permissions
Screenshot 2025-10-03 at 2 28 53 PM

testorg2 org membership permissions shows the teams and dashboard permissions
Screenshot 2025-10-03 at 2 28 45 PM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but the callout is talking about if you do a permission check, it will fail even if the user has the permission if the user's organization is not subscribed to the plan that has the feature

If not, the authorization check will always return false, even if the user has the custom permission.

but that's not true from what i tested o.O


The `has()` method is available for any JavaScript framework, while `<Protect>` is only available for React-based frameworks.

Expand Down
37 changes: 23 additions & 14 deletions docs/guides/organizations/roles-and-permissions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The **Creator** role must _at least_ have the following [system permissions](#sy

To reassign the **Creator** role:

1. In the Clerk Dashboard, navigate to [**Roles**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles).
1. In the Clerk Dashboard, navigate to [**Roles & Permissions**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles).
1. [Create a new role](#custom-roles) or use an existing role from the list.
1. Ensure that **Manage members**, **Read members**, and **Delete organization** system permissions are selected for the role.
1. Open the three dots icon for the role.
Expand All @@ -49,7 +49,7 @@ You cannot delete an organization role if it's used as the organization's **Defa

To reassign the **Default** role:

1. In the Clerk Dashboard, navigate to [**Roles**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles).
1. In the Clerk Dashboard, navigate to [**Roles & Permissions**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles).
1. [Create a new role](#custom-roles) or use an existing role from the list.
1. Open the three dots icon for the role.
1. From the dropdown, select the **Set as Default role** option.
Expand All @@ -65,10 +65,10 @@ Custom roles can be granted permissions and access. For example, you can create

To create a new role:

1. In the Clerk Dashboard, navigate to [**Roles**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles)
1. Select **Create new role**.
1. Give the role a name, a key to reference it by in the format `org:<role>`, and a description.
1. Select **Create role**.
1. In the Clerk Dashboard, navigate to [**Roles & Permissions**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles)
1. Select **Add role**.
1. Give the role a name, a key to reference it by, and a description. The final key will follow the format `org:<role>`.
1. Select **Save**.

### Change a user's role

Expand All @@ -85,13 +85,18 @@ To change a user's role in the Clerk Dashboard:

You cannot delete a role that is still assigned to members of an organization. Change the members to a different role before completing the following steps.

1. In the Clerk Dashboard, navigate to [**Roles**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles).
1. In the Clerk Dashboard, navigate to [**Roles & Permissions**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles).
1. Select the three dots icon next to the role.
1. Select **Delete role**.

## Permissions

Permissions grant users privileged access to resources and operations, like creating and deleting. Clerk supports two types of permissions: System and Custom.
Permissions grant users privileged access to resources and operations, like creating and deleting. Clerk supports two types of permissions: **system permissions** and **custom permissions**.

> [!IMPORTANT]
> In the Clerk Dashboard, permissions appear in the format `<feature>:<permission>`. However, the `org:` prefix is automatically applied under the hood for all organization-scoped permissions. This means that when checking permissions in your code, always include the full key with the prefix (`org:<feature>:<permission>`).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to pass the org: prefix in code when checking perms! was there somewhere that you got this information from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed it with @royanger, but also, it's in our docs code examples too.

Screenshot 2025-10-02 at 2 18 19 pm

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha! its not required when checking perms, but it is required in other places, like when passing params to certain methods and what not. here's the relevant slack discussion: https://clerkinc.slack.com/archives/C07JG7SCWCD/p1759437155781849

the solution we settled on is to just go ahead and add the orgs: prefix everywhere (docs and dash) to minimize confusion

>
> The examples and instructions in these docs follow the same `org:<feature>:<permission>` standard.

### System permissions

Expand All @@ -118,15 +123,17 @@ You can assign these system permissions to any role.
> [!WARNING]
> Custom permissions require a [paid plan](/pricing){{ target: '_blank' }} and the Enhanced B2B SaaS Add-on for production use, but free to use in development mode so that you can try out what works for you. See the [pricing](/pricing){{ target: '_blank' }} page for more information.

When creating a new permission, follow the format `org:<feature>:<permission>`. You can then assign the permission to an existing role.

For example, you could create a new permission called **Create invoices** (`org:invoices:create`) which allows only users with this permission to edit invoices. Then, you could assign this permission to a role, or multiple roles, such as **Billing** (`org:billing`) or **Sales** (`org:sales`).
Custom permissions let you define fine-tuned access control within your organization. Each permission is tied to a feature, and can be assigned to one or more roles. To create a custom permission, you must first create a role (e.g. **sales**) and a feature within that role (e.g. **invoices**). Once both exist, you can define specific permissions (e.g. **create**) related to that feature.

To create a new permission:

1. In the Clerk Dashboard, navigate to [**Permissions**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles).
1. Select **Create new permission**.
1. Give the permission a name, a key to reference it by in the format `org:<feature>:<permission>`, and a description.
1. In the Clerk Dashboard, navigate to [**Roles & Permissions**](https://dashboard.clerk.com/last-active?path=organizations-settings/roles).
1. [Create a new role](#custom-roles) or use an existing role from the list.
1. Under **Feature permissions**, select **Create feature permission**.
1. Give the feature a name, a key to reference it by, and a description. The final key will follow the format `org:<feature>`.
1. Select **Add feature**.
1. Under the newly created feature, select **Create permission**.
1. Give the permission a name, a key to reference it by, and a description. The final key will follow the format `org:<feature>:<permission>`.
> [!NOTE]
> Common permission values could be:
>
Expand All @@ -138,6 +145,8 @@ To create a new permission:
> For example, you could create a new permission called **Create invoices** (`org:invoices:create`) which allows only users with this permission to edit invoices. Then, you could assign this permission to a role, or multiple roles, such as **Billing** (`org:billing`) or **Sales** (`org:sales`).
1. Select **Create permission**.

You can also create a custom permission by navigating to the [**Features**](https://dashboard.clerk.com/last-active?path=features) tab in the Clerk Dashboard, and following steps 6-8 above.

## Verify a user's role or permission

It's best practice to always verify whether or not a user is **authorized** to access sensitive information, important content, or exclusive features. **Authorization** is the process of determining the access rights and privileges of a user, ensuring they have the necessary permissions to perform specific actions. To perform authorization checks using a user's role or permission, see the [guide on authorizing users](/docs/guides/secure/authorization-checks).
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/secure/authorization-checks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ This guide will show you how to implement authorization checks in order to prote
- Note: Using `has()` **on the server-side** to check permissions works only with **custom permissions**, as [system permissions](/docs/guides/organizations/roles-and-permissions#system-permissions) aren't included in the session token claims. To check system permissions, verify the user's role instead.
- Checking for a role or permission depends on the user having an [active organization](!active-organization). Without an active organization, the authorization checks will likely always evaluate to false by default.
- If you would like to perform role-based authorization checks **without** using Clerk's organizations feature, see [the Role Based Access Control (RBAC) guide](/docs/guides/secure/basic-rbac).
- Permission-based authorization checks link with feature-based authorization checks. This means that if you are checking a custom permission, it will only work if the feature part of the permission key (`org:<feature>:<permission>`) **is a feature included in the organization's active plan**. For example, say you want to check if an organization member has the custom permission `org:teams:manage`, where `teams` is the feature. Before performing the authorization check, you need to ensure that the user's organization is subscribed to a plan that has the `teams` feature. If the user's organization is not subscribed to a plan that has the `teams` feature, the authorization check will always return `false`, even if the user has the custom permission.
- Permission-based authorization checks link with feature-based authorization checks. This means that a permission check will only work if the feature part of the permission key (`org:<feature>:<permission>`) **is a feature included in the organization's active plan**. For example, say you want to check if an organization member has the custom permission `org:teams:manage`, where `teams` is the feature. Before performing the authorization check, you need to ensure that the user's organization is subscribed to a plan that has the `teams` feature. If not, the authorization check will always return `false`, even if the user has the custom permission.

<If sdk="nextjs">
* Be cautious when doing authorization checks in layouts, as these don't re-render on navigation, meaning the user session won't be checked on every route change. [Read more in the Next.js docs](https://nextjs.org/docs/app/building-your-application/authentication#layouts-and-auth-checks).
Expand Down