From 91a05e12f2237a8504edb07fb1873b0367109aa6 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:44:40 -0400 Subject: [PATCH 01/41] WIP --- docs/guides/using-org-slugs.mdx | 59 +++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/guides/using-org-slugs.mdx diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx new file mode 100644 index 0000000000..7dfd41e20f --- /dev/null +++ b/docs/guides/using-org-slugs.mdx @@ -0,0 +1,59 @@ +--- +title: 'using-org-slugs' +description: Learn about using organization slugs in URLs to manage the active Clerk organization. +--- + +With Clerk Organizations, users can create entities that represent their companies within your +application. Users can be members of multiple organizations, but can only have one "active" at +any given time. Clerk automatically sets organizations as active when a user picks an organization +with a pre-built component (e.g. the Organization Switcher), and clerk provides APIs (like +auth() and useAuth()), that let you determine the active organization and render the appropriate +content. + +Historically, many applications have also chosen to represent organization scoped spaces within +their app by prefixing their URLs with an organization slug. For example, imagine a b2b application +named "Petstore" that has two customers, "Acmecorp" and "Widgetco". The application may choose to +have urls like: + +`https://petstore.example.com/orgs/acmecorp/dashboard` <- indicates Acmecorp's dashboard +`https://petstore.example.com/orgs/widgetco/dashboard` <- indicates Widgetco's dashboard + +Caution: Think carefully about whether you need to include organization slugs in URLs! Consider: + +- Will organization-specific links be more useful for your application than +organization-agnostic links? If most users will only ever be in one organization (that represents +their company), they won't need the organization slug to disambiguate the org when sharing +links with their coworkers. Public documentation, marketing, and third-party blogs are also +made easier if links aren't tied to any specific organization. +- Adding an additional piece of state that indicates the organization will require additional +complexity in your application, as opposed to managing it soley and transparently in the clerk +session. + +If your application does need to manage the active organization via the URl though, this guide +will cover the best practices for doing so, specifically for nextjs applications that use +server components. + +## Setting and Customizing the Organization Slug + +When your users create organizations, a `slug` is automatically created based on +the organization name. You can also allow your users to set or modify the organization +slug via our components. + +TODO(izaak): examples + + +## Nextjs: Using clerk middleware to set the active organization + +Using ClerkMiddleware, it's possible to tell Clerk which URL patterns indicate that a +specific organization or the user's personal account are active. If clerk detects one +of these patterns in nextjs middleware, and it notices that a different organization is +currently active according to the session, it will activate the indicated organization if +possible [1] + +## Other applications: Using the setActive + + +[^1] Implementation Note: If clerk realizes a mismatch, it will attempt to resolve it by +using a handshake, wherein it will redirect the browser back to clerk with a request to +activate the given organization. If it's possible to do so, Clerk will respond with a new +session token with the desired organization as active and redirect back to your application. From 3d68044d9f64c24eca5f9fd8206aa0059977ddf0 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Fri, 25 Oct 2024 17:18:17 -0400 Subject: [PATCH 02/41] wip --- docs/guides/using-org-slugs.mdx | 281 ++++++++++++++++++++++++++++++-- docs/manifest.json | 4 + 2 files changed, 269 insertions(+), 16 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 7dfd41e20f..888bc8824b 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -1,8 +1,41 @@ --- -title: 'using-org-slugs' +title: 'Using Organization Slugs in URLs' description: Learn about using organization slugs in URLs to manage the active Clerk organization. --- + + - Allow users to choose an organization slug + - Include an organization slug in your URLs + - Render organization-specific content + + +## Introduction + +In this guide, you will learn how to include an organization slug in your applications URLs, and +how to see information about the active organization at runtime. + +This guide will be written for Next.js applications using App Router. + +## Background + With Clerk Organizations, users can create entities that represent their companies within your application. Users can be members of multiple organizations, but can only have one "active" at any given time. Clerk automatically sets organizations as active when a user picks an organization @@ -10,13 +43,12 @@ with a pre-built component (e.g. the Organization Switcher), and clerk provides auth() and useAuth()), that let you determine the active organization and render the appropriate content. -Historically, many applications have also chosen to represent organization scoped spaces within -their app by prefixing their URLs with an organization slug. For example, imagine a b2b application -named "Petstore" that has two customers, "Acmecorp" and "Widgetco". The application may choose to -have urls like: +A relatively common practice is to represent organization scoped spaces within an app by prefixing +URLs with an organization slug. For example, imagine a b2b application named "Petstore" that has +two customers, "Acmecorp" and "Widgetco". The application may choose to have urls like: -`https://petstore.example.com/orgs/acmecorp/dashboard` <- indicates Acmecorp's dashboard -`https://petstore.example.com/orgs/widgetco/dashboard` <- indicates Widgetco's dashboard +`https://petstore.example.com/orgs/``acmecorp``/dashboard` ⬅ indicates **Acmecorp**'s dashboard +`https://petstore.example.com/orgs/``widgetco``/dashboard` ⬅ indicates **Widgetco**'s dashboard Caution: Think carefully about whether you need to include organization slugs in URLs! Consider: @@ -33,25 +65,242 @@ If your application does need to manage the active organization via the URl thou will cover the best practices for doing so, specifically for nextjs applications that use server components. -## Setting and Customizing the Organization Slug + + ### Defining URL patterns + + To begin with, we'll define which sections of our application are scoped to organizations and + the personal account. For this example, we'll assume that the prefix "orgs" indicates an organizaiton + is active, followed by the org slug, and the prefix "me" indicates that the personal account is active. + + | URL | What should be active? | What should be displayed? | + | - | - | - | + | `/orgs/acmecorp` | Organization Acmecorp | Acmecorp's home | + | `/orgs/acmecorp/settings` | Organization Acmecorp | Acmecorp's settings | + | `/me` | Personal account | Personal home | + | `/me/settings` | Personal account | Personal settings | + + ### Configuring the Organization Switcher + + The Organization Switcher and Organization List components each have a powerful set of options for + working with slugs. + + Our application uses the personal account, so we will set hidePersonal to `false` to ensure the personal + account is selectable. We will also allow our users to customize their organization's URL slug when they + create their organization initially, so we'll also set `hideSlug` to `false. -When your users create organizations, a `slug` is automatically created based on -the organization name. You can also allow your users to set or modify the organization -slug via our components. + Finally, we'll give the components navigation instructions. If the user selects or creates an organization + with the slug `acmecorp`, the values of `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` will + instruct the component to navigate them to `/orgs/acmecorp`. If the user selects their personal account, + they will be navigated to `/me` via `afterSelectPersonalUrl`. -TODO(izaak): examples + ", ""]}> + + ```tsx {{ filename: 'app/sidebar.tsx', mark: [6] }} + import { OrganizationSwitcher } from '@clerk/nextjs' + export default function Sidebar() { + return ( + + ) + } + ``` + + + ```tsx {{ filename: 'app/organization-list/[[...organization-list]]/page.tsx', mark: [6] }} + import { OrganizationList } from '@clerk/nextjs' -## Nextjs: Using clerk middleware to set the active organization + export default function OrganizationListPage() { + return ( + + ) + } + ``` + + + +Note that if you'd prefer to have organization IDs appear in your URL rather than slugs, you can use `:id` instead of +`:slug` with `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl`. + +### Using Clerk Middleware to set the active organization Using ClerkMiddleware, it's possible to tell Clerk which URL patterns indicate that a specific organization or the user's personal account are active. If clerk detects one of these patterns in nextjs middleware, and it notices that a different organization is -currently active according to the session, it will activate the indicated organization if -possible [1] +currently active according to the session, it will activate the indicated organization [1] if +possible. + +```tsx {{ filename: 'middleware.ts' }} +import { clerkMiddleware } from '@clerk/nextjs/server' + +export default clerkMiddleware((auth, req) => { + if (isProtectedRoute(req)) auth().protect(); +}, + { + organizationSyncOptions: { + organizationPatterns: [ + "/orgs/:slug", + "/orgs/:slug/(.*)", + ], + personalAccountPatterns: [ + "/me", + "/me/(.*)" + ], + }, + } +); + +export const config = { + matcher: [ + // Skip Next.js internals and all static files, unless found in search params + "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", + // Always run for API routes + "/(api|trpc)(.*)", + ], +}; +``` + +Note that if you'd prefer to have organization IDs appear in your URL rather than slugs, you can use `:id` instead +of `:slug` in `organizationPatterns` and `personalAccountPatterns`. + +### Server Rendered Organization Page + +Now we're ready to build an organization-specific landing page. + +First, we need to handle the case where the middleware cannot activate the organization based on the URL. This can +happen if no organization with the given slug exists, or if the given user isn't a member of the organization. + +When this happens, the middleware will not change the active organization, so whatever organization was previously +active will remain active. To help with troubleshooting, a message will also be emitted by the middleware to the server +log: + +> Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. +Skipping organization activation. + +It's the ultimate responsibility of the page to ensure that it renders the appropriate content for a given URL, and +to handle the case where the expected organization is not active. In this case, we detect the actual organization slug +as a nextjs [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and +compare it to the active organization slug. If they don't match, we render an error message and the OrganizationList +component to allow the user to select a valid organization. + + +```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} +import { auth } from '@clerk/nextjs/server'; +import {OrganizationList} from "@clerk/nextjs"; + +export default function Home({params}:{ + params: { slug: string } +}) { + const authObject = auth(); + const orgSlug = authObject.orgSlug + + if (params.slug != orgSlug ) { + return ( + <> +

Sorry, organization {params.slug} is not valid.

+ + + ) + } + + return ( + <>TODO + ) +} +``` + +### Rendering Organization-specific content + +Next, let's render some organization-specific content, including the organization's name. To do this, we'll +[customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include +the organization name. + +1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=sessions). +1. In the navigation sidebar, select **Sessions**. +1. In the **Customize session token** section, select the **Edit** button. +1. In the modal that opens, you can add any claim to your session token that you need. For this guide, add the following: + +```json +{ + "org_name": "{{org.name}}" +} +``` +1. Select **Save**. + +Now, let's render the organization name in our page. We'll use the organization name, and also the organization role, +which is included in the +[default session claims](https://clerk.com/docs/backend-requests/resources/session-tokens#default-session-claims). + +```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} +import { auth } from '@clerk/nextjs/server'; +import {OrganizationList} from "@clerk/nextjs"; + +export default function Home({params}:{ + params: { slug: string } +}) { + const authObject = auth(); + const orgSlug = authObject.orgSlug + + if (params.slug != orgSlug ) { + return ( + <> +

Sorry, organization {params.slug} is not valid.

+ + + ) + } + let orgName = authObject.sessionClaims['org_name'] as string + + return ( +
+ {orgName && ( +

+ Welcome to organization {orgName} +

+ )} +

+ Your role in this organization: {authObject.orgRole} +

+
+ ) +} +``` + +### The Personal Account and Client-side Routing + +Now let's build the page for the personal account as a nextjs client-side component. This page will be +rendered when the user navigates to `/me`. + + +
-## Other applications: Using the setActive +--------------------- Footer --------------------------- [^1] Implementation Note: If clerk realizes a mismatch, it will attempt to resolve it by using a handshake, wherein it will redirect the browser back to clerk with a request to diff --git a/docs/manifest.json b/docs/manifest.json index 8bbb9087a2..4957c2f4c9 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -127,6 +127,10 @@ "title": "Hide Personal Accounts and force organizations", "href": "/docs/guides/force-organizations" }, + { + "title": "Using organization slugs in URLs", + "href": "/docs/guides/using-org-slugs" + }, { "title": "Migrating to Clerk from Auth.js", "href": "/docs/guides/authjs-migration" From edf08923f02f326a860696b21237d942e2cc317a Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:11:34 -0400 Subject: [PATCH 03/41] Fixing up formatting --- docs/guides/using-org-slugs.mdx | 233 +++++++++++++++++++++++--------- 1 file changed, 169 insertions(+), 64 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 888bc8824b..253c3c45b1 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -1,6 +1,6 @@ --- title: 'Using Organization Slugs in URLs' -description: Learn about using organization slugs in URLs to manage the active Clerk organization. +description: Learn how to use organization slugs in URLs to manage the active Clerk organization. --- auth()](/docs/references/nextjs/auth) and [useAuth()](/docs/references/react/use-auth)), that let you determine the active organization and render the appropriate content. -A relatively common practice is to represent organization scoped spaces within an app by prefixing -URLs with an organization slug. For example, imagine a b2b application named "Petstore" that has +A relatively common practice is to represent organization-scoped spaces within an app by prefixing +URLs with an organization slug. For example, imagine a B2B application named "Petstore" that has two customers, "Acmecorp" and "Widgetco". The application may choose to have urls like: `https://petstore.example.com/orgs/``acmecorp``/dashboard` ⬅ indicates **Acmecorp**'s dashboard `https://petstore.example.com/orgs/``widgetco``/dashboard` ⬅ indicates **Widgetco**'s dashboard -Caution: Think carefully about whether you need to include organization slugs in URLs! Consider: +It is also possible to use an [organization ID](/docs/references/javascript/organization/organization#properties) to +indicate the active organization in URLs: -- Will organization-specific links be more useful for your application than -organization-agnostic links? If most users will only ever be in one organization (that represents -their company), they won't need the organization slug to disambiguate the org when sharing -links with their coworkers. Public documentation, marketing, and third-party blogs are also -made easier if links aren't tied to any specific organization. -- Adding an additional piece of state that indicates the organization will require additional -complexity in your application, as opposed to managing it soley and transparently in the clerk -session. +`https://petstore.example.com/orgs/``org_1a2b3c4d5e6f7g8e``/dashboard` ⬅ **Acmecorp's ID** indicates Acmecorp's dashboard -If your application does need to manage the active organization via the URl though, this guide -will cover the best practices for doing so, specifically for nextjs applications that use -server components. +This guide will focus on using a human-comprehensible slug, but IDs are just as well-supported in Clerk's component +configuration. + +> [!CAUTION] +> Think carefully about whether you need to include organization slugs in URLs! Consider: +> +> - Will organization-specific links be more useful to your users than +> organization-agnostic links? If most users will only ever be in one organization (that represents +> their company), they won't need the slug to disambiguate the organization when sharing +> links with their coworkers. Public documentation, marketing, and third-party blogs are also +> made easier to write if links aren't tied to any specific organization. +> - Adding an additional piece of state (i.e. the URL) that indicates the organization will require additional +> complexity in your application, as opposed to managing it soley and transparently in the Clerk +> session. +> +> We at Clerk generally recommend against including organization slugs in URLs. If your application +> does have this requirement though, this guide will cover the best practices for doing so with a NextJS application. ### Defining URL patterns - To begin with, we'll define which sections of our application are scoped to organizations and - the personal account. For this example, we'll assume that the prefix "orgs" indicates an organizaiton - is active, followed by the org slug, and the prefix "me" indicates that the personal account is active. + To begin, you can define which sections of your application are scoped to organizations and which belong to + the [personal account](https://clerk.com/glossary#personal-account). For this example, we'll assume that the prefix + `/orgs/` indicates that an organization is active, followed by the organization slug, and the prefix `/me/` indicates that the + personal account is active. | URL | What should be active? | What should be displayed? | | - | - | - | @@ -81,16 +89,17 @@ server components. ### Configuring the Organization Switcher - The Organization Switcher and Organization List components each have a powerful set of options for - working with slugs. + The [Organization Switcher](/docs/components/organization/organization-switcher) and + [Organization List](/docs/components/organization/organization-list) components each have a powerful set of options for + working with slugs and IDs. - Our application uses the personal account, so we will set hidePersonal to `false` to ensure the personal - account is selectable. We will also allow our users to customize their organization's URL slug when they - create their organization initially, so we'll also set `hideSlug` to `false. + This sample application uses the [personal account](https://clerk.com/glossary#personal-account), so you can set `hidePersonal` to `false` to ensure the personal + account is selectable. You can also allow users to customize their organization's URL slug when they + create their organization initially by setting `hideSlug` to `false`. - Finally, we'll give the components navigation instructions. If the user selects or creates an organization - with the slug `acmecorp`, the values of `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` will - instruct the component to navigate them to `/orgs/acmecorp`. If the user selects their personal account, + Finally, you can give the components navigation instructions. If the user selects or creates an organization + with the slug `acmecorp`, by setting `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` to `'/orgs/:slug'`, + the component will navigate the user to `/orgs/acmecorp`. If the user selects their [personal account](https://clerk.com/glossary#personal-account), they will be navigated to `/me` via `afterSelectPersonalUrl`. ", ""]}> @@ -132,17 +141,43 @@ server components. -Note that if you'd prefer to have organization IDs appear in your URL rather than slugs, you can use `:id` instead of -`:slug` with `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl`. -### Using Clerk Middleware to set the active organization +> [!TIP] +> If you prefer to have an [organization ID](/docs/references/javascript/organization/organization#properties) appear in +> your URL rather than a slug, you can use `:id` instead of `:slug` with both `afterCreateOrganizationUrl` and +> `afterSelectOrganizationUrl`. + +### Using clerkMiddleware to set the active organization -Using ClerkMiddleware, it's possible to tell Clerk which URL patterns indicate that a -specific organization or the user's personal account are active. If clerk detects one -of these patterns in nextjs middleware, and it notices that a different organization is -currently active according to the session, it will activate the indicated organization [1] if +With [clerkMiddleware](https://clerk.com/docs/references/nextjs/clerk-middleware), you can +declare URL patterns that indicate that a specific organization or the user's [personal account](https://clerk.com/glossary#personal-account) +should be activated. If the middleware detects one +of these patterns in the URL, and it notices that a different organization is +currently active according to the session, it will activate the indicated organization if possible. +> [!NOTE] +> Implementation Detail: To resolve a mismatch, Clerk middleware initiaties a [Handshake](docs/references/sdk/terminology#:~:text=each:Handshake). +> The middleware will respond to the browser with a redirect to Clerk's API with a request to activate the URL-indicated +> organization. If it's possible to do so, Clerk's API will respond with a redirect back to the original URL, including +> a session token with the desired organization as active and redirect back to your application. + +For this application, the path `/orgs/:slug` with any optional trailing path segments should activate the +organization indicated by the slug. To achieve this, you can define two `organizationPatterns`: one for the +root (e.g. `/orgs/acmecorp`), and one using the wildcard matcher `(.*)` that will match +`/orgs/acmecorp/any/other/resource`. + +You can use the same approach for the [personal account](https://clerk.com/glossary#personal-account) with `personalAccountPatterns`. + +> [!WARNING] +> It may not always be possible to activate the organization requested in the URL. This can happen if no organization +> with the given slug exists, or the user isn't a member of the given organization. In these cases, clerkMiddleware +> **will not modify** the active organization, leaving as active whichever organization was previously active on the +> clerk session. + +See the documentation for [organizationSyncOptions](/docs/references/nextjs/clerk-middleware#organization-sync-options) +for more specific usage of the middleware options. + ```tsx {{ filename: 'middleware.ts' }} import { clerkMiddleware } from '@clerk/nextjs/server' @@ -173,14 +208,16 @@ export const config = { }; ``` -Note that if you'd prefer to have organization IDs appear in your URL rather than slugs, you can use `:id` instead -of `:slug` in `organizationPatterns` and `personalAccountPatterns`. +> [!TIP] +> If you prefer to have an [organization ID](/docs/references/javascript/organization/organization#properties) +> appear in your URL rather than slugs, you can use `:id` placeholder instead of `:slug` in both `organizationPatterns` +> and `personalAccountPatterns`. ### Server Rendered Organization Page -Now we're ready to build an organization-specific landing page. +Now that clerkMiddleware is configured to activate organizations, you can build an organization-specific home page. -First, we need to handle the case where the middleware cannot activate the organization based on the URL. This can +First, you must handle the case where the middleware cannot activate the organization based on the URL. This can happen if no organization with the given slug exists, or if the given user isn't a member of the organization. When this happens, the middleware will not change the active organization, so whatever organization was previously @@ -190,11 +227,12 @@ log: > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. -It's the ultimate responsibility of the page to ensure that it renders the appropriate content for a given URL, and -to handle the case where the expected organization is not active. In this case, we detect the actual organization slug -as a nextjs [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and -compare it to the active organization slug. If they don't match, we render an error message and the OrganizationList -component to allow the user to select a valid organization. +> [!CAUTION] +> It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and +> to handle the case where the expected organization is not active. In this case, we detect the actual organization slug +> as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and +> compare it to the active organization slug. If they don't match, we render an error message and the [Organization List](/docs/components/organization/organization-list) +> component to allow the user to select a valid organization. ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} @@ -230,9 +268,9 @@ export default function Home({params}:{ ### Rendering Organization-specific content -Next, let's render some organization-specific content, including the organization's name. To do this, we'll +Next, you can render organization-specific content, including the organization's name. To do this, you can [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include -the organization name. +the organization name: 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=sessions). 1. In the navigation sidebar, select **Sessions**. @@ -246,9 +284,8 @@ the organization name. ``` 1. Select **Save**. -Now, let's render the organization name in our page. We'll use the organization name, and also the organization role, -which is included in the -[default session claims](https://clerk.com/docs/backend-requests/resources/session-tokens#default-session-claims). +Now you can render the organization name in your page. This example also displays the user's role within the +organization (`authObject.orgRole`), which is included in the [default session claims](https://clerk.com/docs/backend-requests/resources/session-tokens#default-session-claims). ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} import { auth } from '@clerk/nextjs/server'; @@ -291,18 +328,86 @@ export default function Home({params}:{ } ``` -### The Personal Account and Client-side Routing +### Client-side Routing -Now let's build the page for the personal account as a nextjs client-side component. This page will be -rendered when the user navigates to `/me`. +You can also build client-side pages that are organization-scoped according to your URLs. `clerkMiddleware` executes +before your client components render, so you don't need to explicitly activate any organization. This example shows a +client-side settings page for organizations using [OrganizationProfile](/docs/components/organization/organization-profile). +Rather than the server-specific [auth()](/docs/references/nextjs/auth) hook, you can use the client-side +[useOrganization()](/docs/references/react/use-organization) hook to get the active organization. You must +verify that the desired organization has been activated. If so, this example renders the +[OrganizationProfile](/docs/components/organization/organization-profile). + +```tsx {{ filename: 'src/app/orgs/[slug]/settings/[[...rest]]/page.tsx' }} +"use client"; + +import {OrganizationList, OrganizationProfile, useOrganization} from "@clerk/nextjs"; + +export default function Home({params}:{ + params: { slug: string } +}) { +const {organization} = useOrganization() + +if (!organization || organization.slug != params.slug) { + return ( + <> +

Sorry, organization {params.slug} is not valid.

+ + + ) +} + +return <> + + +} +``` + +### Personal account + +The [personal account](https://clerk.com/glossary#personal-account) is represented by the absense of any other active +organization. Barring unexpected errors, Clerk middleware should always be able to activate the user's personal account, +so checking for an organization mismatch is optional. + +To display personal-account specific settings, this example builds a server rendered page that verifies that the personal +account is active (e.g. that no other organization is active), and the render something personal-account specific. +Remember, you can gain access to other user-specific information, like the username, by [customizing the session token](/docs/backend-requests/making/custom-session-token). + +```tsx {{ filename: 'src/app/me/settings/page.tsx' }} +import { auth } from '@clerk/nextjs/server'; +import {notFound} from "next/navigation"; + +export default function Home():{} { + const authObject = auth(); + + if (authObject.orgId != null ) { + notFound(); + } + + return ( + <> +

Welcome, user {authObject.userId} to your own personal account settings!

+ + ) +} +```
---------------------- Footer --------------------------- -[^1] Implementation Note: If clerk realizes a mismatch, it will attempt to resolve it by -using a handshake, wherein it will redirect the browser back to clerk with a request to -activate the given organization. If it's possible to do so, Clerk will respond with a new -session token with the desired organization as active and redirect back to your application. + +### Further Reading +To see all of these pages working in the context of an application, visit the +[sync-orgs-with-url](https://github.com/clerk/orgs/tree/main/examples/sync-org-with-url) sample repository on Github. + +The active organization can also be controlled via the client-side [setActive](https://clerk.com/docs/references/javascript/clerk/session-methods) +session method. See our [Hide Personal Accounts and force organizations](https://clerk.com/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) +guide to learn how to manually activate a specific organization based on the URL. From 6ffcb5d9683fa59953f24856e7b741bb1edd839a Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:20:45 -0400 Subject: [PATCH 04/41] Ran `npm run format` --- docs/guides/using-org-slugs.mdx | 450 +++++++++++++++----------------- 1 file changed, 217 insertions(+), 233 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 253c3c45b1..9af62c3663 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -61,13 +61,13 @@ configuration. > Think carefully about whether you need to include organization slugs in URLs! Consider: > > - Will organization-specific links be more useful to your users than -> organization-agnostic links? If most users will only ever be in one organization (that represents -> their company), they won't need the slug to disambiguate the organization when sharing -> links with their coworkers. Public documentation, marketing, and third-party blogs are also -> made easier to write if links aren't tied to any specific organization. +> organization-agnostic links? If most users will only ever be in one organization (that represents +> their company), they won't need the slug to disambiguate the organization when sharing +> links with their coworkers. Public documentation, marketing, and third-party blogs are also +> made easier to write if links aren't tied to any specific organization. > - Adding an additional piece of state (i.e. the URL) that indicates the organization will require additional -> complexity in your application, as opposed to managing it soley and transparently in the Clerk -> session. +> complexity in your application, as opposed to managing it soley and transparently in the Clerk +> session. > > We at Clerk generally recommend against including organization slugs in URLs. If your application > does have this requirement though, this guide will cover the best practices for doing so with a NextJS application. @@ -113,14 +113,15 @@ configuration. // prettier-ignore hidePersonal={false} hideSlug={false} - afterCreateOrganizationUrl='/orgs/:slug' - afterSelectOrganizationUrl='/orgs/:slug' - afterSelectPersonalUrl='/me' + afterCreateOrganizationUrl="/orgs/:slug" + afterSelectOrganizationUrl="/orgs/:slug" + afterSelectPersonalUrl="/me" /> ) } - ``` - + ``` + + ```tsx {{ filename: 'app/organization-list/[[...organization-list]]/page.tsx', mark: [6] }} import { OrganizationList } from '@clerk/nextjs' @@ -131,280 +132,263 @@ configuration. // prettier-ignore hidePersonal={false} hideSlug={false} - afterCreateOrganizationUrl='/orgs/:slug' - afterSelectOrganizationUrl='/orgs/:slug' - afterSelectPersonalUrl='/me' + afterCreateOrganizationUrl="/orgs/:slug" + afterSelectOrganizationUrl="/orgs/:slug" + afterSelectPersonalUrl="/me" /> ) } - ``` - - - + ``` + + + + > [!TIP] + > If you prefer to have an [organization ID](/docs/references/javascript/organization/organization#properties) appear in + > your URL rather than a slug, you can use `:id` instead of `:slug` with both `afterCreateOrganizationUrl` and + > `afterSelectOrganizationUrl`. + + ### Using clerkMiddleware to set the active organization + + With [clerkMiddleware](https://clerk.com/docs/references/nextjs/clerk-middleware), you can + declare URL patterns that indicate that a specific organization or the user's [personal account](https://clerk.com/glossary#personal-account) + should be activated. If the middleware detects one + of these patterns in the URL, and it notices that a different organization is + currently active according to the session, it will activate the indicated organization if + possible. + + > [!NOTE] + > Implementation Detail: To resolve a mismatch, Clerk middleware initiaties a [Handshake](docs/references/sdk/terminology#:~:text=each:Handshake). + > The middleware will respond to the browser with a redirect to Clerk's API with a request to activate the URL-indicated + > organization. If it's possible to do so, Clerk's API will respond with a redirect back to the original URL, including + > a session token with the desired organization as active and redirect back to your application. + + For this application, the path `/orgs/:slug` with any optional trailing path segments should activate the + organization indicated by the slug. To achieve this, you can define two `organizationPatterns`: one for the + root (e.g. `/orgs/acmecorp`), and one using the wildcard matcher `(.*)` that will match + `/orgs/acmecorp/any/other/resource`. + + You can use the same approach for the [personal account](https://clerk.com/glossary#personal-account) with `personalAccountPatterns`. + + > [!WARNING] + > It may not always be possible to activate the organization requested in the URL. This can happen if no organization + > with the given slug exists, or the user isn't a member of the given organization. In these cases, clerkMiddleware + > **will not modify** the active organization, leaving as active whichever organization was previously active on the + > clerk session. + + See the documentation for [organizationSyncOptions](/docs/references/nextjs/clerk-middleware#organization-sync-options) + for more specific usage of the middleware options. + + ```tsx {{ filename: 'middleware.ts' }} + import { clerkMiddleware } from '@clerk/nextjs/server' + + export default clerkMiddleware( + (auth, req) => { + if (isProtectedRoute(req)) auth().protect() + }, + { + organizationSyncOptions: { + organizationPatterns: ['/orgs/:slug', '/orgs/:slug/(.*)'], + personalAccountPatterns: ['/me', '/me/(.*)'], + }, + }, + ) -> [!TIP] -> If you prefer to have an [organization ID](/docs/references/javascript/organization/organization#properties) appear in -> your URL rather than a slug, you can use `:id` instead of `:slug` with both `afterCreateOrganizationUrl` and -> `afterSelectOrganizationUrl`. + export const config = { + matcher: [ + // Skip Next.js internals and all static files, unless found in search params + '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', + // Always run for API routes + '/(api|trpc)(.*)', + ], + } + ``` -### Using clerkMiddleware to set the active organization + > [!TIP] + > If you prefer to have an [organization ID](/docs/references/javascript/organization/organization#properties) + > appear in your URL rather than slugs, you can use `:id` placeholder instead of `:slug` in both `organizationPatterns` + > and `personalAccountPatterns`. -With [clerkMiddleware](https://clerk.com/docs/references/nextjs/clerk-middleware), you can -declare URL patterns that indicate that a specific organization or the user's [personal account](https://clerk.com/glossary#personal-account) -should be activated. If the middleware detects one -of these patterns in the URL, and it notices that a different organization is -currently active according to the session, it will activate the indicated organization if -possible. + ### Server Rendered Organization Page -> [!NOTE] -> Implementation Detail: To resolve a mismatch, Clerk middleware initiaties a [Handshake](docs/references/sdk/terminology#:~:text=each:Handshake). -> The middleware will respond to the browser with a redirect to Clerk's API with a request to activate the URL-indicated -> organization. If it's possible to do so, Clerk's API will respond with a redirect back to the original URL, including -> a session token with the desired organization as active and redirect back to your application. + Now that clerkMiddleware is configured to activate organizations, you can build an organization-specific home page. -For this application, the path `/orgs/:slug` with any optional trailing path segments should activate the -organization indicated by the slug. To achieve this, you can define two `organizationPatterns`: one for the -root (e.g. `/orgs/acmecorp`), and one using the wildcard matcher `(.*)` that will match -`/orgs/acmecorp/any/other/resource`. + First, you must handle the case where the middleware cannot activate the organization based on the URL. This can + happen if no organization with the given slug exists, or if the given user isn't a member of the organization. -You can use the same approach for the [personal account](https://clerk.com/glossary#personal-account) with `personalAccountPatterns`. + When this happens, the middleware will not change the active organization, so whatever organization was previously + active will remain active. To help with troubleshooting, a message will also be emitted by the middleware to the server + log: -> [!WARNING] -> It may not always be possible to activate the organization requested in the URL. This can happen if no organization -> with the given slug exists, or the user isn't a member of the given organization. In these cases, clerkMiddleware -> **will not modify** the active organization, leaving as active whichever organization was previously active on the -> clerk session. + > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. + > Skipping organization activation. -See the documentation for [organizationSyncOptions](/docs/references/nextjs/clerk-middleware#organization-sync-options) -for more specific usage of the middleware options. + > [!CAUTION] + > It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and + > to handle the case where the expected organization is not active. In this case, we detect the actual organization slug + > as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and + > compare it to the active organization slug. If they don't match, we render an error message and the [Organization List](/docs/components/organization/organization-list) + > component to allow the user to select a valid organization. -```tsx {{ filename: 'middleware.ts' }} -import { clerkMiddleware } from '@clerk/nextjs/server' + ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} + import { auth } from '@clerk/nextjs/server' + import { OrganizationList } from '@clerk/nextjs' -export default clerkMiddleware((auth, req) => { - if (isProtectedRoute(req)) auth().protect(); -}, - { - organizationSyncOptions: { - organizationPatterns: [ - "/orgs/:slug", - "/orgs/:slug/(.*)", - ], - personalAccountPatterns: [ - "/me", - "/me/(.*)" - ], - }, - } -); + export default function Home({ params }: { params: { slug: string } }) { + const authObject = auth() + const orgSlug = authObject.orgSlug -export const config = { - matcher: [ - // Skip Next.js internals and all static files, unless found in search params - "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", - // Always run for API routes - "/(api|trpc)(.*)", - ], -}; -``` + if (params.slug != orgSlug) { + return ( + <> +

Sorry, organization {params.slug} is not valid.

+ + + ) + } -> [!TIP] -> If you prefer to have an [organization ID](/docs/references/javascript/organization/organization#properties) -> appear in your URL rather than slugs, you can use `:id` placeholder instead of `:slug` in both `organizationPatterns` -> and `personalAccountPatterns`. + return <>TODO + } + ``` -### Server Rendered Organization Page + ### Rendering Organization-specific content -Now that clerkMiddleware is configured to activate organizations, you can build an organization-specific home page. + Next, you can render organization-specific content, including the organization's name. To do this, you can + [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include + the organization name: -First, you must handle the case where the middleware cannot activate the organization based on the URL. This can -happen if no organization with the given slug exists, or if the given user isn't a member of the organization. + 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=sessions). + 1. In the navigation sidebar, select **Sessions**. + 1. In the **Customize session token** section, select the **Edit** button. + 1. In the modal that opens, you can add any claim to your session token that you need. For this guide, add the following: -When this happens, the middleware will not change the active organization, so whatever organization was previously -active will remain active. To help with troubleshooting, a message will also be emitted by the middleware to the server -log: + ```json + { + "org_name": "{{org.name}}" + } + ``` -> Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. -Skipping organization activation. + 1. Select **Save**. -> [!CAUTION] -> It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and -> to handle the case where the expected organization is not active. In this case, we detect the actual organization slug -> as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and -> compare it to the active organization slug. If they don't match, we render an error message and the [Organization List](/docs/components/organization/organization-list) -> component to allow the user to select a valid organization. + Now you can render the organization name in your page. This example also displays the user's role within the + organization (`authObject.orgRole`), which is included in the [default session claims](https://clerk.com/docs/backend-requests/resources/session-tokens#default-session-claims). + ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} + import { auth } from '@clerk/nextjs/server' + import { OrganizationList } from '@clerk/nextjs' -```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} -import { auth } from '@clerk/nextjs/server'; -import {OrganizationList} from "@clerk/nextjs"; + export default function Home({ params }: { params: { slug: string } }) { + const authObject = auth() + const orgSlug = authObject.orgSlug -export default function Home({params}:{ - params: { slug: string } -}) { - const authObject = auth(); - const orgSlug = authObject.orgSlug + if (params.slug != orgSlug) { + return ( + <> +

Sorry, organization {params.slug} is not valid.

+ + + ) + } + let orgName = authObject.sessionClaims['org_name'] as string - if (params.slug != orgSlug ) { return ( - <> -

Sorry, organization {params.slug} is not valid.

- - +
+ {orgName &&

Welcome to organization {orgName}

} +

+ Your role in this organization: {authObject.orgRole} +

+
) } + ``` - return ( - <>TODO - ) -} -``` - -### Rendering Organization-specific content + ### Client-side Routing -Next, you can render organization-specific content, including the organization's name. To do this, you can -[customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include -the organization name: + You can also build client-side pages that are organization-scoped according to your URLs. `clerkMiddleware` executes + before your client components render, so you don't need to explicitly activate any organization. This example shows a + client-side settings page for organizations using [OrganizationProfile](/docs/components/organization/organization-profile). -1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=sessions). -1. In the navigation sidebar, select **Sessions**. -1. In the **Customize session token** section, select the **Edit** button. -1. In the modal that opens, you can add any claim to your session token that you need. For this guide, add the following: + Rather than the server-specific [auth()](/docs/references/nextjs/auth) hook, you can use the client-side + [useOrganization()](/docs/references/react/use-organization) hook to get the active organization. You must + verify that the desired organization has been activated. If so, this example renders the + [OrganizationProfile](/docs/components/organization/organization-profile). -```json -{ - "org_name": "{{org.name}}" -} -``` -1. Select **Save**. + ```tsx {{ filename: 'src/app/orgs/[slug]/settings/[[...rest]]/page.tsx' }} + 'use client' -Now you can render the organization name in your page. This example also displays the user's role within the -organization (`authObject.orgRole`), which is included in the [default session claims](https://clerk.com/docs/backend-requests/resources/session-tokens#default-session-claims). + import { OrganizationList, OrganizationProfile, useOrganization } from '@clerk/nextjs' -```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} -import { auth } from '@clerk/nextjs/server'; -import {OrganizationList} from "@clerk/nextjs"; + export default function Home({ params }: { params: { slug: string } }) { + const { organization } = useOrganization() -export default function Home({params}:{ - params: { slug: string } -}) { - const authObject = auth(); - const orgSlug = authObject.orgSlug + if (!organization || organization.slug != params.slug) { + return ( + <> +

Sorry, organization {params.slug} is not valid.

+ + + ) + } - if (params.slug != orgSlug ) { return ( <> -

Sorry, organization {params.slug} is not valid.

- + ) } - let orgName = authObject.sessionClaims['org_name'] as string - - return ( -
- {orgName && ( -

- Welcome to organization {orgName} -

- )} -

- Your role in this organization: {authObject.orgRole} -

-
- ) -} -``` - -### Client-side Routing - -You can also build client-side pages that are organization-scoped according to your URLs. `clerkMiddleware` executes -before your client components render, so you don't need to explicitly activate any organization. This example shows a -client-side settings page for organizations using [OrganizationProfile](/docs/components/organization/organization-profile). - -Rather than the server-specific [auth()](/docs/references/nextjs/auth) hook, you can use the client-side -[useOrganization()](/docs/references/react/use-organization) hook to get the active organization. You must -verify that the desired organization has been activated. If so, this example renders the -[OrganizationProfile](/docs/components/organization/organization-profile). - -```tsx {{ filename: 'src/app/orgs/[slug]/settings/[[...rest]]/page.tsx' }} -"use client"; - -import {OrganizationList, OrganizationProfile, useOrganization} from "@clerk/nextjs"; - -export default function Home({params}:{ - params: { slug: string } -}) { -const {organization} = useOrganization() - -if (!organization || organization.slug != params.slug) { - return ( - <> -

Sorry, organization {params.slug} is not valid.

- - - ) -} + ``` -return <> - - -} -``` + ### Personal account -### Personal account + The [personal account](https://clerk.com/glossary#personal-account) is represented by the absense of any other active + organization. Barring unexpected errors, Clerk middleware should always be able to activate the user's personal account, + so checking for an organization mismatch is optional. -The [personal account](https://clerk.com/glossary#personal-account) is represented by the absense of any other active -organization. Barring unexpected errors, Clerk middleware should always be able to activate the user's personal account, -so checking for an organization mismatch is optional. + To display personal-account specific settings, this example builds a server rendered page that verifies that the personal + account is active (e.g. that no other organization is active), and the render something personal-account specific. + Remember, you can gain access to other user-specific information, like the username, by [customizing the session token](/docs/backend-requests/making/custom-session-token). -To display personal-account specific settings, this example builds a server rendered page that verifies that the personal -account is active (e.g. that no other organization is active), and the render something personal-account specific. -Remember, you can gain access to other user-specific information, like the username, by [customizing the session token](/docs/backend-requests/making/custom-session-token). + ```tsx {{ filename: 'src/app/me/settings/page.tsx' }} + import { auth } from '@clerk/nextjs/server' + import { notFound } from 'next/navigation' -```tsx {{ filename: 'src/app/me/settings/page.tsx' }} -import { auth } from '@clerk/nextjs/server'; -import {notFound} from "next/navigation"; + export default function Home(): {} { + const authObject = auth() -export default function Home():{} { - const authObject = auth(); + if (authObject.orgId != null) { + notFound() + } - if (authObject.orgId != null ) { - notFound(); + return ( + <> +

+ Welcome, user {authObject.userId} to your own personal account settings! +

+ + ) } - - return ( - <> -

Welcome, user {authObject.userId} to your own personal account settings!

- - ) -} -``` - + ``` - - - ### Further Reading + To see all of these pages working in the context of an application, visit the [sync-orgs-with-url](https://github.com/clerk/orgs/tree/main/examples/sync-org-with-url) sample repository on Github. From a1bf3960676a268bd74889887c42e78bce4dbd41 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:02:19 -0400 Subject: [PATCH 05/41] Apply suggestions from code review Thank you @lauraBeatris for the suggestions! Co-authored-by: victoria Co-authored-by: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> --- docs/guides/using-org-slugs.mdx | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 9af62c3663..daa9802076 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -1,5 +1,5 @@ --- -title: 'Using Organization Slugs in URLs' +title: 'Using organization slugs in URLs' description: Learn how to use organization slugs in URLs to manage the active Clerk organization. --- @@ -16,7 +16,7 @@ description: Learn how to use organization slugs in URLs to manage the active Cl icon: "globe", }, { - title: "Examine the complete sample repository on Github", + title: "Explore the full sample repository on GitHub", link: "https://github.com/clerk/orgs/tree/main/examples/sync-org-with-url", icon: "git", } @@ -69,8 +69,8 @@ configuration. > complexity in your application, as opposed to managing it soley and transparently in the Clerk > session. > -> We at Clerk generally recommend against including organization slugs in URLs. If your application -> does have this requirement though, this guide will cover the best practices for doing so with a NextJS application. +> We recommend against including organization slugs in URLs. If your application +> does have this requirement though, this guide will cover the best practices for doing so with a Next.js application. ### Defining URL patterns @@ -87,10 +87,10 @@ configuration. | `/me` | Personal account | Personal home | | `/me/settings` | Personal account | Personal settings | - ### Configuring the Organization Switcher + ### Configuring `` - The [Organization Switcher](/docs/components/organization/organization-switcher) and - [Organization List](/docs/components/organization/organization-list) components each have a powerful set of options for + The [``](/docs/components/organization/organization-switcher) and + [](/docs/components/organization/organization-list) components each have a powerful set of options for working with slugs and IDs. This sample application uses the [personal account](https://clerk.com/glossary#personal-account), so you can set `hidePersonal` to `false` to ensure the personal @@ -147,7 +147,7 @@ configuration. > your URL rather than a slug, you can use `:id` instead of `:slug` with both `afterCreateOrganizationUrl` and > `afterSelectOrganizationUrl`. - ### Using clerkMiddleware to set the active organization + ### Using `clerkMiddleware` to set the active organization With [clerkMiddleware](https://clerk.com/docs/references/nextjs/clerk-middleware), you can declare URL patterns that indicate that a specific organization or the user's [personal account](https://clerk.com/glossary#personal-account) @@ -170,10 +170,9 @@ configuration. You can use the same approach for the [personal account](https://clerk.com/glossary#personal-account) with `personalAccountPatterns`. > [!WARNING] - > It may not always be possible to activate the organization requested in the URL. This can happen if no organization - > with the given slug exists, or the user isn't a member of the given organization. In these cases, clerkMiddleware - > **will not modify** the active organization, leaving as active whichever organization was previously active on the - > clerk session. + > If no organization with the given slug exists, or the user isn't a member of the given organization, then `clerkMiddleware` + > **will not modify** the active organization, leaving whichever was previously active on the + > Clerk session. See the documentation for [organizationSyncOptions](/docs/references/nextjs/clerk-middleware#organization-sync-options) for more specific usage of the middleware options. @@ -208,9 +207,9 @@ configuration. > appear in your URL rather than slugs, you can use `:id` placeholder instead of `:slug` in both `organizationPatterns` > and `personalAccountPatterns`. - ### Server Rendered Organization Page + ### Building server-rendered page - Now that clerkMiddleware is configured to activate organizations, you can build an organization-specific home page. + Now that `clerkMiddleware` is configured to activate organizations, you can build an organization-specific home page. First, you must handle the case where the middleware cannot activate the organization based on the URL. This can happen if no organization with the given slug exists, or if the given user isn't a member of the organization. From a51a8e2197ce5868109666489c038b60fea366e5 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:17:44 -0400 Subject: [PATCH 06/41] Adding missing code annotation --- docs/guides/using-org-slugs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index daa9802076..3fedd1c192 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -90,7 +90,7 @@ configuration. ### Configuring `` The [``](/docs/components/organization/organization-switcher) and - [](/docs/components/organization/organization-list) components each have a powerful set of options for + [``](/docs/components/organization/organization-list) components each have a powerful set of options for working with slugs and IDs. This sample application uses the [personal account](https://clerk.com/glossary#personal-account), so you can set `hidePersonal` to `false` to ensure the personal From e56fb5416b0b5b8113d56f76ed2871c1a11207e5 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:17:59 -0400 Subject: [PATCH 07/41] Better paragraph deliniation Thanks @LauraBeatris! https://github.com/clerk/clerk-docs/pull/1664/files#r1819753686 --- docs/guides/using-org-slugs.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 3fedd1c192..937239b9f4 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -151,7 +151,9 @@ configuration. With [clerkMiddleware](https://clerk.com/docs/references/nextjs/clerk-middleware), you can declare URL patterns that indicate that a specific organization or the user's [personal account](https://clerk.com/glossary#personal-account) - should be activated. If the middleware detects one + should be activated. + + If the middleware detects one of these patterns in the URL, and it notices that a different organization is currently active according to the session, it will activate the indicated organization if possible. From a4819206444c6ce24c751e29fbb1f5437434f508 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:27:50 -0400 Subject: [PATCH 08/41] Update docs/guides/using-org-slugs.mdx Co-authored-by: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> --- docs/guides/using-org-slugs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 937239b9f4..e7fc1163c7 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -184,7 +184,7 @@ configuration. export default clerkMiddleware( (auth, req) => { - if (isProtectedRoute(req)) auth().protect() + // Add your middleware checks }, { organizationSyncOptions: { From 90a4442713667c1881425bd6b6c6ba5d5804de28 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:48:08 -0400 Subject: [PATCH 09/41] Combining client and server component sections Good feedback @LauraBeatris, thank you! https://github.com/clerk/clerk-docs/pull/1664/files#r1819776550 --- docs/guides/using-org-slugs.mdx | 179 ++++++++++++-------------------- 1 file changed, 68 insertions(+), 111 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index e7fc1163c7..ce8f3963b9 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -184,7 +184,7 @@ configuration. export default clerkMiddleware( (auth, req) => { - // Add your middleware checks + // Add your middleware checks }, { organizationSyncOptions: { @@ -209,7 +209,7 @@ configuration. > appear in your URL rather than slugs, you can use `:id` placeholder instead of `:slug` in both `organizationPatterns` > and `personalAccountPatterns`. - ### Building server-rendered page + ### Building a component Now that `clerkMiddleware` is configured to activate organizations, you can build an organization-specific home page. @@ -259,9 +259,9 @@ configuration. ### Rendering Organization-specific content - Next, you can render organization-specific content, including the organization's name. To do this, you can - [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include - the organization name: + Next, you can render organization-specific content, including the organization's name. To see this data in + a server component, you can [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) + to include the organization name: 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=sessions). 1. In the navigation sidebar, select **Sessions**. @@ -276,116 +276,73 @@ configuration. 1. Select **Save**. - Now you can render the organization name in your page. This example also displays the user's role within the - organization (`authObject.orgRole`), which is included in the [default session claims](https://clerk.com/docs/backend-requests/resources/session-tokens#default-session-claims). + Now you can access the organizaiton name within the [sessionClaims](/docs/references/nextjs/auth-object#:~:text=sessionClaims) + returned by `auth()`. - ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} - import { auth } from '@clerk/nextjs/server' - import { OrganizationList } from '@clerk/nextjs' + In client components, rather than the server-specific [auth()](/docs/references/nextjs/auth) hook, you + can use the client-side [useOrganization()](/docs/references/react/use-organization) hook to get the + active organization. - export default function Home({ params }: { params: { slug: string } }) { - const authObject = auth() - const orgSlug = authObject.orgSlug - - if (params.slug != orgSlug) { - return ( - <> -

Sorry, organization {params.slug} is not valid.

- - - ) - } - let orgName = authObject.sessionClaims['org_name'] as string - - return ( -
- {orgName &&

Welcome to organization {orgName}

} -

- Your role in this organization: {authObject.orgRole} -

-
- ) - } - ``` - - ### Client-side Routing - - You can also build client-side pages that are organization-scoped according to your URLs. `clerkMiddleware` executes - before your client components render, so you don't need to explicitly activate any organization. This example shows a - client-side settings page for organizations using [OrganizationProfile](/docs/components/organization/organization-profile). - - Rather than the server-specific [auth()](/docs/references/nextjs/auth) hook, you can use the client-side - [useOrganization()](/docs/references/react/use-organization) hook to get the active organization. You must - verify that the desired organization has been activated. If so, this example renders the - [OrganizationProfile](/docs/components/organization/organization-profile). - - ```tsx {{ filename: 'src/app/orgs/[slug]/settings/[[...rest]]/page.tsx' }} - 'use client' - - import { OrganizationList, OrganizationProfile, useOrganization } from '@clerk/nextjs' - - export default function Home({ params }: { params: { slug: string } }) { - const { organization } = useOrganization() - - if (!organization || organization.slug != params.slug) { - return ( - <> -

Sorry, organization {params.slug} is not valid.

- - - ) - } - - return ( - <> - - - ) - } - ``` - - ### Personal account - - The [personal account](https://clerk.com/glossary#personal-account) is represented by the absense of any other active - organization. Barring unexpected errors, Clerk middleware should always be able to activate the user's personal account, - so checking for an organization mismatch is optional. - - To display personal-account specific settings, this example builds a server rendered page that verifies that the personal - account is active (e.g. that no other organization is active), and the render something personal-account specific. - Remember, you can gain access to other user-specific information, like the username, by [customizing the session token](/docs/backend-requests/making/custom-session-token). - - ```tsx {{ filename: 'src/app/me/settings/page.tsx' }} - import { auth } from '@clerk/nextjs/server' - import { notFound } from 'next/navigation' - - export default function Home(): {} { - const authObject = auth() + + + ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} + import { auth } from '@clerk/nextjs/server' + import { OrganizationList } from '@clerk/nextjs' - if (authObject.orgId != null) { - notFound() - } + export default function Home({ params }: { params: { slug: string } }) { + const authObject = auth() + const orgSlug = authObject.orgSlug + + if (params.slug != orgSlug) { + return ( + <> +

Sorry, organization {params.slug} is not valid.

+ + + ) + } + let orgName = authObject.sessionClaims['org_name'] as string + + return
{orgName &&

Welcome to organization {orgName}

}
+ } + ``` +
- return ( - <> -

- Welcome, user {authObject.userId} to your own personal account settings! -

- - ) - } - ``` + + ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} + 'use client' + + import { OrganizationList, useOrganization } from '@clerk/nextjs' + + export default function Home({ params }: { params: { slug: string } }) { + const { organization } = useOrganization() + + if (!organization || organization.slug != params.slug) { + return ( + <> +

Sorry, organization {params.slug} is not valid.

+ + + ) + } + + return
{organization &&

Welcome to organization {organization.name}

}
+ } + ``` +
+
### Further Reading From 6154d63ff2bf11947cca847b01a4dee9df61b2a8 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:52:20 -0400 Subject: [PATCH 10/41] Updating personal account links --- docs/guides/using-org-slugs.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index ce8f3963b9..c5c48faea9 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -76,7 +76,7 @@ configuration. ### Defining URL patterns To begin, you can define which sections of your application are scoped to organizations and which belong to - the [personal account](https://clerk.com/glossary#personal-account). For this example, we'll assume that the prefix + the [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account). For this example, we'll assume that the prefix `/orgs/` indicates that an organization is active, followed by the organization slug, and the prefix `/me/` indicates that the personal account is active. @@ -93,13 +93,13 @@ configuration. [``](/docs/components/organization/organization-list) components each have a powerful set of options for working with slugs and IDs. - This sample application uses the [personal account](https://clerk.com/glossary#personal-account), so you can set `hidePersonal` to `false` to ensure the personal + This sample application uses the [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account), so you can set `hidePersonal` to `false` to ensure the personal account is selectable. You can also allow users to customize their organization's URL slug when they create their organization initially by setting `hideSlug` to `false`. Finally, you can give the components navigation instructions. If the user selects or creates an organization with the slug `acmecorp`, by setting `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` to `'/orgs/:slug'`, - the component will navigate the user to `/orgs/acmecorp`. If the user selects their [personal account](https://clerk.com/glossary#personal-account), + the component will navigate the user to `/orgs/acmecorp`. If the user selects their [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account), they will be navigated to `/me` via `afterSelectPersonalUrl`. ", ""]}> @@ -150,7 +150,7 @@ configuration. ### Using `clerkMiddleware` to set the active organization With [clerkMiddleware](https://clerk.com/docs/references/nextjs/clerk-middleware), you can - declare URL patterns that indicate that a specific organization or the user's [personal account](https://clerk.com/glossary#personal-account) + declare URL patterns that indicate that a specific organization or the user's [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account) should be activated. If the middleware detects one @@ -169,7 +169,7 @@ configuration. root (e.g. `/orgs/acmecorp`), and one using the wildcard matcher `(.*)` that will match `/orgs/acmecorp/any/other/resource`. - You can use the same approach for the [personal account](https://clerk.com/glossary#personal-account) with `personalAccountPatterns`. + You can use the same approach for the [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account) with `personalAccountPatterns`. > [!WARNING] > If no organization with the given slug exists, or the user isn't a member of the given organization, then `clerkMiddleware` From 30139c315a2103a4b5987f9bd35f35f15798e993 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:49:05 -0400 Subject: [PATCH 11/41] Apply suggestions from code review Thank you @victoriaxyz and @alexisintech for the suggestions! Co-authored-by: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Co-authored-by: victoria --- docs/guides/using-org-slugs.mdx | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index c5c48faea9..60ee6f039f 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -1,12 +1,12 @@ --- -title: 'Using organization slugs in URLs' +title: 'Use organization slugs in URLs' description: Learn how to use organization slugs in URLs to manage the active Clerk organization. --- -## Introduction +In this guide, you'll learn how to include an organization slug in your app's URLs and render information about the user's active organization at runtime. -In this guide, you will learn how to include an organization slug in your application's URLs, and -how to render information about the user's active organization at runtime. +This guide is intended for apps that require organization slugs in URLs. **We generally recommend against doing this unless necessary**. Consider the following points before implementing: +- User Relevance: Will organization-specific links add value for your users? If most users belong to a single organization (representing their company), they may not need slugs to identify the organization when sharing links with coworkers. Public documentation, marketing, and third-party blogs are also easier to write if links aren't tied to any specific organization. +- Application Complexity: Adding an organization slug to URLs introduces an extra piece of state to manage, increasing complexity compared to handling organization context within the Clerk session alone. +With [organizations](/docs/organizations/overview), users can create entities that represent their companies within your app. Users belong to multiple organizations but can only have one [active organization](/docs/organizations/overview#active-organization) at a time. Clerk sets an organization as +active when a user picks an organization with a prebuilt component (e.g., the +[``](/docs/components/organization/organization-switcher)). Additionally, Clerk provides APIs such as [`auth()`](/docs/references/nextjs/auth) and [`useAuth()`](/docs/references/react/use-auth)) that let you determine the active organization and render the appropriate content accordingly. -## Background +A common practice for organization-scoped spaces in an app is to prefix URLs with an organization slug. For example, imagine a B2B application named "Petstore" that has two customers: "Acmecorp" and "Widgetco." The app might use URLs such as the following: -With [Organizations](/docs/organizations/overview), users can create entities that represent their companies within your -application. Users can be members of multiple organizations, but can only have one -[active organization](/docs/organizations/overview#active-organization) at any given time. Clerk automatically sets organizations as -active when a user picks an organization with a pre-built component (e.g. the -[Organization Switcher](/docs/components/organization/organization-switcher)), and Clerk provides -APIs (like [auth()](/docs/references/nextjs/auth) and [useAuth()](/docs/references/react/use-auth)), that let you determine the active organization and render the appropriate -content. - -A relatively common practice is to represent organization-scoped spaces within an app by prefixing -URLs with an organization slug. For example, imagine a B2B application named "Petstore" that has -two customers, "Acmecorp" and "Widgetco". The application may choose to have urls like: - -`https://petstore.example.com/orgs/``acmecorp``/dashboard` ⬅ indicates **Acmecorp**'s dashboard -`https://petstore.example.com/orgs/``widgetco``/dashboard` ⬅ indicates **Widgetco**'s dashboard +`https://petstore.example.com/orgs/``acmecorp``/dashboard` indicates **Acmecorp**'s dashboard +`https://petstore.example.com/orgs/``widgetco``/dashboard` indicates **Widgetco**'s dashboard It is also possible to use an [organization ID](/docs/references/javascript/organization/organization#properties) to indicate the active organization in URLs: From 4c4769eac0c2284452f2a08c5cdde9d233a26385 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:50:45 -0400 Subject: [PATCH 12/41] Using the proper example repository in the title Thank you @alexisintech! https://github.com/clerk/clerk-docs/pull/1664/files#r1823068218 --- docs/guides/using-org-slugs.mdx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 60ee6f039f..6f45ea1d83 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -14,11 +14,12 @@ description: Learn how to use organization slugs in URLs to manage the active Cl title: "Enable organizations for your instance", link: "/docs/organizations/overview", icon: "globe", - }, + } + ]} + exampleRepo={[ { - title: "Explore the full sample repository on GitHub", - link: "https://github.com/clerk/orgs/tree/main/examples/sync-org-with-url", - icon: "git", + title: "Sync org with URL demo", + link: "https://github.com/clerk/orgs/tree/main/examples/sync-org-with-url" } ]} > From 06c736d18a30cf187cbfce26586180c58029ad1c Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:52:24 -0400 Subject: [PATCH 13/41] Apply suggestions from code review Co-authored-by: victoria Co-authored-by: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> --- docs/guides/using-org-slugs.mdx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 6f45ea1d83..a207883912 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -42,10 +42,9 @@ A common practice for organization-scoped spaces in an app is to prefix URLs wit `https://petstore.example.com/orgs/``acmecorp``/dashboard` indicates **Acmecorp**'s dashboard `https://petstore.example.com/orgs/``widgetco``/dashboard` indicates **Widgetco**'s dashboard -It is also possible to use an [organization ID](/docs/references/javascript/organization/organization#properties) to -indicate the active organization in URLs: +You can also use an [organization ID](/docs/references/javascript/organization/organization#properties) in URLs to indicate the active organization: -`https://petstore.example.com/orgs/``org_1a2b3c4d5e6f7g8e``/dashboard` ⬅ **Acmecorp's ID** indicates Acmecorp's dashboard +`https://petstore.example.com/orgs/``org_1a2b3c4d5e6f7g8e``/dashboard` **Acmecorp's ID** indicates Acmecorp's dashboard This guide will focus on using a human-comprehensible slug, but IDs are just as well-supported in Clerk's component configuration. From f7bb4b5a4e7636f88c702598ba7c39a0ce527249 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:53:57 -0400 Subject: [PATCH 14/41] Clarifying **organization** id Thanks @alexisintech! https://github.com/clerk/clerk-docs/pull/1664#discussion_r1823081952 --- docs/guides/using-org-slugs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index a207883912..da3f1fbcc4 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -44,7 +44,7 @@ A common practice for organization-scoped spaces in an app is to prefix URLs wit You can also use an [organization ID](/docs/references/javascript/organization/organization#properties) in URLs to indicate the active organization: -`https://petstore.example.com/orgs/``org_1a2b3c4d5e6f7g8e``/dashboard` **Acmecorp's ID** indicates Acmecorp's dashboard +`https://petstore.example.com/orgs/``org_1a2b3c4d5e6f7g8e``/dashboard` **Acmecorp's organization ID** indicates Acmecorp's dashboard This guide will focus on using a human-comprehensible slug, but IDs are just as well-supported in Clerk's component configuration. From 50e2dbc494d64bef2c4af9c0921fdd417d070ede Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:51:03 -0400 Subject: [PATCH 15/41] Apply suggestions from code review Co-authored-by: victoria Co-authored-by: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> --- docs/guides/using-org-slugs.mdx | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index da3f1fbcc4..7c627612e4 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -46,8 +46,7 @@ You can also use an [organization ID](/docs/references/javascript/organization/o `https://petstore.example.com/orgs/``org_1a2b3c4d5e6f7g8e``/dashboard` **Acmecorp's organization ID** indicates Acmecorp's dashboard -This guide will focus on using a human-comprehensible slug, but IDs are just as well-supported in Clerk's component -configuration. +This guide focuses on using a human-readable slug, but IDs are equally well-supported in Clerk's components. > [!CAUTION] > Think carefully about whether you need to include organization slugs in URLs! Consider: @@ -65,12 +64,9 @@ configuration. > does have this requirement though, this guide will cover the best practices for doing so with a Next.js application. - ### Defining URL patterns + ### Define URL patterns - To begin, you can define which sections of your application are scoped to organizations and which belong to - the [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account). For this example, we'll assume that the prefix - `/orgs/` indicates that an organization is active, followed by the organization slug, and the prefix `/me/` indicates that the - personal account is active. + Define which sections of your app are scoped to organizations and which belong to [personal accounts](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account). The following example assumes that the `/orgs/` prefix indicates an active organization, followed by the organization slug, and that the `/me/` prefix indicates an active personal account. | URL | What should be active? | What should be displayed? | | - | - | - | @@ -79,20 +75,12 @@ configuration. | `/me` | Personal account | Personal home | | `/me/settings` | Personal account | Personal settings | - ### Configuring `` + ### Configure `` +The [``](/docs/components/organization/organization-switcher) and [``](/docs/components/organization/organization-list) components provide a robust set of options for managing slugs and IDs. - The [``](/docs/components/organization/organization-switcher) and - [``](/docs/components/organization/organization-list) components each have a powerful set of options for - working with slugs and IDs. +Since the sample app uses the personal account, set `hidePersonal` to `false` to ensure the personal account is selectable. You can allow users to customize their organization's URL slug when they initially create their organization by setting `hideSlug` to `false`. Additionally, you can add navigation instructions to these components. - This sample application uses the [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account), so you can set `hidePersonal` to `false` to ensure the personal - account is selectable. You can also allow users to customize their organization's URL slug when they - create their organization initially by setting `hideSlug` to `false`. - - Finally, you can give the components navigation instructions. If the user selects or creates an organization - with the slug `acmecorp`, by setting `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` to `'/orgs/:slug'`, - the component will navigate the user to `/orgs/acmecorp`. If the user selects their [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account), - they will be navigated to `/me` via `afterSelectPersonalUrl`. +For example, if the user selects or creates an organization with the slug `acmecorp`, then setting `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` to `/orgs/:slug` navigates the user to `/orgs/acmecorp`. If the user selects their personal account, they'll be navigated to `/me` via `afterSelectPersonalUrl`. ", ""]}> From 9b2d01ed9ecd751f8caaeecf30270396f779cc5a Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:55:04 -0400 Subject: [PATCH 16/41] More direct slug -> id tip --- docs/guides/using-org-slugs.mdx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 7c627612e4..40742c4382 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -123,9 +123,7 @@ For example, if the user selects or creates an organization with the slug `acmec > [!TIP] - > If you prefer to have an [organization ID](/docs/references/javascript/organization/organization#properties) appear in - > your URL rather than a slug, you can use `:id` instead of `:slug` with both `afterCreateOrganizationUrl` and - > `afterSelectOrganizationUrl`. + > For applications that use [organization IDs](/docs/references/javascript/organization/organization#properties) rather replace `:slug` with `:id` in both `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl`. ### Using `clerkMiddleware` to set the active organization @@ -184,10 +182,9 @@ For example, if the user selects or creates an organization with the slug `acmec } ``` + > [!TIP] - > If you prefer to have an [organization ID](/docs/references/javascript/organization/organization#properties) - > appear in your URL rather than slugs, you can use `:id` placeholder instead of `:slug` in both `organizationPatterns` - > and `personalAccountPatterns`. + > For applications that use [organization IDs](/docs/references/javascript/organization/organization#properties) rather replace `:slug` with `:id` in both `organizationPatterns` and `personalAccountPatterns`. ### Building a component From 4400a267cf1b40dbcbb836385f710f023c85bce0 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:56:26 -0400 Subject: [PATCH 17/41] Apply suggestions from code review Co-authored-by: victoria Co-authored-by: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> --- docs/guides/using-org-slugs.mdx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 40742c4382..2b76a7c68e 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -125,16 +125,11 @@ For example, if the user selects or creates an organization with the slug `acmec > [!TIP] > For applications that use [organization IDs](/docs/references/javascript/organization/organization#properties) rather replace `:slug` with `:id` in both `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl`. - ### Using `clerkMiddleware` to set the active organization + ### Use `clerkMiddleware` to set the active organization - With [clerkMiddleware](https://clerk.com/docs/references/nextjs/clerk-middleware), you can - declare URL patterns that indicate that a specific organization or the user's [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account) - should be activated. - - If the middleware detects one - of these patterns in the URL, and it notices that a different organization is - currently active according to the session, it will activate the indicated organization if - possible. + With [`clerkMiddleware()`](https://clerk.com/docs/references/nextjs/clerk-middleware), you can declare URL patterns that determine whether a specific organization or the user's personal account should be activated. + + If the middleware detects one of these patterns in the URL and finds that a different organization is active in the session, it'll attempt to activate the specified organization. > [!NOTE] > Implementation Detail: To resolve a mismatch, Clerk middleware initiaties a [Handshake](docs/references/sdk/terminology#:~:text=each:Handshake). From 44b5482fff38ab78f72e46ad80a6e7897af7fd74 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:58:01 -0400 Subject: [PATCH 18/41] Removing handshake explanation Thanks @lauraneatris and @alexisintech! https://github.com/clerk/clerk-docs/pull/1664/files#r1819756698 --- docs/guides/using-org-slugs.mdx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 2b76a7c68e..c0a8caa50c 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -131,12 +131,6 @@ For example, if the user selects or creates an organization with the slug `acmec If the middleware detects one of these patterns in the URL and finds that a different organization is active in the session, it'll attempt to activate the specified organization. - > [!NOTE] - > Implementation Detail: To resolve a mismatch, Clerk middleware initiaties a [Handshake](docs/references/sdk/terminology#:~:text=each:Handshake). - > The middleware will respond to the browser with a redirect to Clerk's API with a request to activate the URL-indicated - > organization. If it's possible to do so, Clerk's API will respond with a redirect back to the original URL, including - > a session token with the desired organization as active and redirect back to your application. - For this application, the path `/orgs/:slug` with any optional trailing path segments should activate the organization indicated by the slug. To achieve this, you can define two `organizationPatterns`: one for the root (e.g. `/orgs/acmecorp`), and one using the wildcard matcher `(.*)` that will match From d3b05e6e9f5a3ea03208c5d0abe64f32db26b5cd Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:59:26 -0400 Subject: [PATCH 19/41] Apply suggestions from code review Co-authored-by: victoria Co-authored-by: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> --- docs/guides/using-org-slugs.mdx | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index c0a8caa50c..3b0cdab743 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -131,20 +131,14 @@ For example, if the user selects or creates an organization with the slug `acmec If the middleware detects one of these patterns in the URL and finds that a different organization is active in the session, it'll attempt to activate the specified organization. - For this application, the path `/orgs/:slug` with any optional trailing path segments should activate the - organization indicated by the slug. To achieve this, you can define two `organizationPatterns`: one for the - root (e.g. `/orgs/acmecorp`), and one using the wildcard matcher `(.*)` that will match - `/orgs/acmecorp/any/other/resource`. +For this app, the path `/orgs/:slug` with any optional trailing path segments should activate the organization indicated by the slug. To achieve this, define two `organizationPatterns`: one for the root (e.g., `/orgs/acmecorp`) and one using the wildcard matcher `(.*)` to match `/orgs/acmecorp/any/other/resource`. - You can use the same approach for the [personal account](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account) with `personalAccountPatterns`. +Use the same approach for the personal account with `personalAccountPatterns`. > [!WARNING] - > If no organization with the given slug exists, or the user isn't a member of the given organization, then `clerkMiddleware` - > **will not modify** the active organization, leaving whichever was previously active on the - > Clerk session. + > If no organization with the specified slug exists, or the user isn't a member of the organization, then `clerkMiddleware()` **won't** modify the active organization, leaving the previously active one on the Clerk session. - See the documentation for [organizationSyncOptions](/docs/references/nextjs/clerk-middleware#organization-sync-options) - for more specific usage of the middleware options. +For guidance on specific middleware options, see the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) guide. ```tsx {{ filename: 'middleware.ts' }} import { clerkMiddleware } from '@clerk/nextjs/server' @@ -175,19 +169,14 @@ For example, if the user selects or creates an organization with the slug `acmec > [!TIP] > For applications that use [organization IDs](/docs/references/javascript/organization/organization#properties) rather replace `:slug` with `:id` in both `organizationPatterns` and `personalAccountPatterns`. - ### Building a component + ### Build a component - Now that `clerkMiddleware` is configured to activate organizations, you can build an organization-specific home page. + Now that `clerkMiddleware()` is configured to activate organizations, you can build an organization-specific home page. - First, you must handle the case where the middleware cannot activate the organization based on the URL. This can - happen if no organization with the given slug exists, or if the given user isn't a member of the organization. + First, handle cases where the middleware can't activate the organization based on the URL. This occurs if no organization with the specified slug exists, or if the given user isn't a member of the organization. - When this happens, the middleware will not change the active organization, so whatever organization was previously - active will remain active. To help with troubleshooting, a message will also be emitted by the middleware to the server - log: - - > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. - > Skipping organization activation. +When this happens, the middleware won't change the active organization, leaving the previously active one unchanged. For troubleshooting, a message will also be logged on the server: +> Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. > [!CAUTION] > It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and From 88d88114a9a7999279ee49266e939fb72b511b59 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:04:40 -0400 Subject: [PATCH 20/41] Apply suggestions from code review Co-authored-by: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Co-authored-by: victoria --- docs/guides/using-org-slugs.mdx | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/docs/guides/using-org-slugs.mdx b/docs/guides/using-org-slugs.mdx index 3b0cdab743..a3fc73ccce 100644 --- a/docs/guides/using-org-slugs.mdx +++ b/docs/guides/using-org-slugs.mdx @@ -190,9 +190,9 @@ When this happens, the middleware won't change the active organization, leaving import { OrganizationList } from '@clerk/nextjs' export default function Home({ params }: { params: { slug: string } }) { - const authObject = auth() - const orgSlug = authObject.orgSlug - + const { orgSlug } = auth() + + // Verify mismatch between route param and active organization slug from user's session if (params.slug != orgSlug) { return ( <> @@ -212,16 +212,12 @@ When this happens, the middleware won't change the active organization, leaving } ``` - ### Rendering Organization-specific content - - Next, you can render organization-specific content, including the organization's name. To see this data in - a server component, you can [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) - to include the organization name: + ### Render organization-specific content - 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=sessions). - 1. In the navigation sidebar, select **Sessions**. - 1. In the **Customize session token** section, select the **Edit** button. - 1. In the modal that opens, you can add any claim to your session token that you need. For this guide, add the following: +Render organization-specific content, including the organization's name. To see this data in a server component, [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include the organization name: +1. In the Clerk Dashboard, navigate to the [Sessions](https://dashboard.clerk.com/last-active?path=sessions) page. +1. In the **Customize session token** section, select **Edit**. +1. In the modal that opens, add any claim you need to your session token. For this guide, add the following: ```json { @@ -231,12 +227,10 @@ When this happens, the middleware won't change the active organization, leaving 1. Select **Save**. - Now you can access the organizaiton name within the [sessionClaims](/docs/references/nextjs/auth-object#:~:text=sessionClaims) + Now you can access the organization name in the [`sessionClaims`](/docs/references/nextjs/auth-object#:~:text=sessionClaims) returned by `auth()`. - In client components, rather than the server-specific [auth()](/docs/references/nextjs/auth) hook, you - can use the client-side [useOrganization()](/docs/references/react/use-organization) hook to get the - active organization. +In client components, use the client-side [`useOrganization()`](/docs/references/react/use-organization) hook instead of the server-specific [`auth()`](/docs/references/nextjs/auth) hook to get the active organization. From d5c6100b07826f51ba4d2e5153cdb29ccbb5cfb6 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:30:23 -0400 Subject: [PATCH 21/41] Moving from /guides/ to /organizations/ Thanks @alexisintech! https://github.com/clerk/clerk-docs/pull/1664/files#r1823183604 --- docs/manifest.json | 8 ++++---- docs/{guides => organizations}/using-org-slugs.mdx | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename docs/{guides => organizations}/using-org-slugs.mdx (100%) diff --git a/docs/manifest.json b/docs/manifest.json index 4957c2f4c9..71d57c7c28 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -127,10 +127,6 @@ "title": "Hide Personal Accounts and force organizations", "href": "/docs/guides/force-organizations" }, - { - "title": "Using organization slugs in URLs", - "href": "/docs/guides/using-org-slugs" - }, { "title": "Migrating to Clerk from Auth.js", "href": "/docs/guides/authjs-migration" @@ -619,6 +615,10 @@ { "title": "Organization workspaces", "href": "/docs/organizations/organization-workspaces" + }, + { + "title": "Using organization slugs in URLs", + "href": "/docs/organizations/using-org-slugs" } ] ] diff --git a/docs/guides/using-org-slugs.mdx b/docs/organizations/using-org-slugs.mdx similarity index 100% rename from docs/guides/using-org-slugs.mdx rename to docs/organizations/using-org-slugs.mdx From 787772b29a4398584875d446db2a7447c29683c2 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:42:30 -0400 Subject: [PATCH 22/41] Running `npm run format` --- docs/organizations/using-org-slugs.mdx | 45 ++++++++++++++------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/organizations/using-org-slugs.mdx b/docs/organizations/using-org-slugs.mdx index a3fc73ccce..b7b36fc138 100644 --- a/docs/organizations/using-org-slugs.mdx +++ b/docs/organizations/using-org-slugs.mdx @@ -31,11 +31,12 @@ description: Learn how to use organization slugs in URLs to manage the active Cl In this guide, you'll learn how to include an organization slug in your app's URLs and render information about the user's active organization at runtime. This guide is intended for apps that require organization slugs in URLs. **We generally recommend against doing this unless necessary**. Consider the following points before implementing: + - User Relevance: Will organization-specific links add value for your users? If most users belong to a single organization (representing their company), they may not need slugs to identify the organization when sharing links with coworkers. Public documentation, marketing, and third-party blogs are also easier to write if links aren't tied to any specific organization. - Application Complexity: Adding an organization slug to URLs introduces an extra piece of state to manage, increasing complexity compared to handling organization context within the Clerk session alone. -With [organizations](/docs/organizations/overview), users can create entities that represent their companies within your app. Users belong to multiple organizations but can only have one [active organization](/docs/organizations/overview#active-organization) at a time. Clerk sets an organization as -active when a user picks an organization with a prebuilt component (e.g., the -[``](/docs/components/organization/organization-switcher)). Additionally, Clerk provides APIs such as [`auth()`](/docs/references/nextjs/auth) and [`useAuth()`](/docs/references/react/use-auth)) that let you determine the active organization and render the appropriate content accordingly. + With [organizations](/docs/organizations/overview), users can create entities that represent their companies within your app. Users belong to multiple organizations but can only have one [active organization](/docs/organizations/overview#active-organization) at a time. Clerk sets an organization as + active when a user picks an organization with a prebuilt component (e.g., the + [``](/docs/components/organization/organization-switcher)). Additionally, Clerk provides APIs such as [`auth()`](/docs/references/nextjs/auth) and [`useAuth()`](/docs/references/react/use-auth)) that let you determine the active organization and render the appropriate content accordingly. A common practice for organization-scoped spaces in an app is to prefix URLs with an organization slug. For example, imagine a B2B application named "Petstore" that has two customers: "Acmecorp" and "Widgetco." The app might use URLs such as the following: @@ -76,11 +77,12 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp | `/me/settings` | Personal account | Personal settings | ### Configure `` -The [``](/docs/components/organization/organization-switcher) and [``](/docs/components/organization/organization-list) components provide a robust set of options for managing slugs and IDs. -Since the sample app uses the personal account, set `hidePersonal` to `false` to ensure the personal account is selectable. You can allow users to customize their organization's URL slug when they initially create their organization by setting `hideSlug` to `false`. Additionally, you can add navigation instructions to these components. + The [``](/docs/components/organization/organization-switcher) and [``](/docs/components/organization/organization-list) components provide a robust set of options for managing slugs and IDs. + + Since the sample app uses the personal account, set `hidePersonal` to `false` to ensure the personal account is selectable. You can allow users to customize their organization's URL slug when they initially create their organization by setting `hideSlug` to `false`. Additionally, you can add navigation instructions to these components. -For example, if the user selects or creates an organization with the slug `acmecorp`, then setting `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` to `/orgs/:slug` navigates the user to `/orgs/acmecorp`. If the user selects their personal account, they'll be navigated to `/me` via `afterSelectPersonalUrl`. + For example, if the user selects or creates an organization with the slug `acmecorp`, then setting `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` to `/orgs/:slug` navigates the user to `/orgs/acmecorp`. If the user selects their personal account, they'll be navigated to `/me` via `afterSelectPersonalUrl`. ", ""]}> @@ -128,17 +130,17 @@ For example, if the user selects or creates an organization with the slug `acmec ### Use `clerkMiddleware` to set the active organization With [`clerkMiddleware()`](https://clerk.com/docs/references/nextjs/clerk-middleware), you can declare URL patterns that determine whether a specific organization or the user's personal account should be activated. - - If the middleware detects one of these patterns in the URL and finds that a different organization is active in the session, it'll attempt to activate the specified organization. -For this app, the path `/orgs/:slug` with any optional trailing path segments should activate the organization indicated by the slug. To achieve this, define two `organizationPatterns`: one for the root (e.g., `/orgs/acmecorp`) and one using the wildcard matcher `(.*)` to match `/orgs/acmecorp/any/other/resource`. + If the middleware detects one of these patterns in the URL and finds that a different organization is active in the session, it'll attempt to activate the specified organization. -Use the same approach for the personal account with `personalAccountPatterns`. + For this app, the path `/orgs/:slug` with any optional trailing path segments should activate the organization indicated by the slug. To achieve this, define two `organizationPatterns`: one for the root (e.g., `/orgs/acmecorp`) and one using the wildcard matcher `(.*)` to match `/orgs/acmecorp/any/other/resource`. + + Use the same approach for the personal account with `personalAccountPatterns`. > [!WARNING] > If no organization with the specified slug exists, or the user isn't a member of the organization, then `clerkMiddleware()` **won't** modify the active organization, leaving the previously active one on the Clerk session. -For guidance on specific middleware options, see the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) guide. + For guidance on specific middleware options, see the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) guide. ```tsx {{ filename: 'middleware.ts' }} import { clerkMiddleware } from '@clerk/nextjs/server' @@ -165,7 +167,6 @@ For guidance on specific middleware options, see the [`organizationSyncOptions`] } ``` - > [!TIP] > For applications that use [organization IDs](/docs/references/javascript/organization/organization#properties) rather replace `:slug` with `:id` in both `organizationPatterns` and `personalAccountPatterns`. @@ -175,8 +176,9 @@ For guidance on specific middleware options, see the [`organizationSyncOptions`] First, handle cases where the middleware can't activate the organization based on the URL. This occurs if no organization with the specified slug exists, or if the given user isn't a member of the organization. -When this happens, the middleware won't change the active organization, leaving the previously active one unchanged. For troubleshooting, a message will also be logged on the server: -> Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. + When this happens, the middleware won't change the active organization, leaving the previously active one unchanged. For troubleshooting, a message will also be logged on the server: + + > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. > [!CAUTION] > It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and @@ -191,8 +193,8 @@ When this happens, the middleware won't change the active organization, leaving export default function Home({ params }: { params: { slug: string } }) { const { orgSlug } = auth() - - // Verify mismatch between route param and active organization slug from user's session + + // Verify mismatch between route param and active organization slug from user's session if (params.slug != orgSlug) { return ( <> @@ -214,10 +216,11 @@ When this happens, the middleware won't change the active organization, leaving ### Render organization-specific content -Render organization-specific content, including the organization's name. To see this data in a server component, [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include the organization name: -1. In the Clerk Dashboard, navigate to the [Sessions](https://dashboard.clerk.com/last-active?path=sessions) page. -1. In the **Customize session token** section, select **Edit**. -1. In the modal that opens, add any claim you need to your session token. For this guide, add the following: + Render organization-specific content, including the organization's name. To see this data in a server component, [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include the organization name: + + 1. In the Clerk Dashboard, navigate to the [Sessions](https://dashboard.clerk.com/last-active?path=sessions) page. + 1. In the **Customize session token** section, select **Edit**. + 1. In the modal that opens, add any claim you need to your session token. For this guide, add the following: ```json { @@ -230,7 +233,7 @@ Render organization-specific content, including the organization's name. To see Now you can access the organization name in the [`sessionClaims`](/docs/references/nextjs/auth-object#:~:text=sessionClaims) returned by `auth()`. -In client components, use the client-side [`useOrganization()`](/docs/references/react/use-organization) hook instead of the server-specific [`auth()`](/docs/references/nextjs/auth) hook to get the active organization. + In client components, use the client-side [`useOrganization()`](/docs/references/react/use-organization) hook instead of the server-specific [`auth()`](/docs/references/nextjs/auth) hook to get the active organization. From c924ca3141f3bee5f8493b13eed14903e7e7305a Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:54:10 -0400 Subject: [PATCH 23/41] Removing Further Reading --- docs/organizations/using-org-slugs.mdx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/organizations/using-org-slugs.mdx b/docs/organizations/using-org-slugs.mdx index b7b36fc138..fc5cb6cb75 100644 --- a/docs/organizations/using-org-slugs.mdx +++ b/docs/organizations/using-org-slugs.mdx @@ -214,6 +214,8 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp } ``` + In addition to middleware config, the active organization can controlled via the client-side [setActive](https://clerk.com/docs/references/javascript/clerk/session-methods) session method. See the [Hide Personal Accounts and force organizations](https://clerk.com/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) guide to learn how to manually activate a specific organization based on the URL. + ### Render organization-specific content Render organization-specific content, including the organization's name. To see this data in a server component, [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include the organization name: @@ -296,12 +298,3 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp - -### Further Reading - -To see all of these pages working in the context of an application, visit the -[sync-orgs-with-url](https://github.com/clerk/orgs/tree/main/examples/sync-org-with-url) sample repository on Github. - -The active organization can also be controlled via the client-side [setActive](https://clerk.com/docs/references/javascript/clerk/session-methods) -session method. See our [Hide Personal Accounts and force organizations](https://clerk.com/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) -guide to learn how to manually activate a specific organization based on the URL. From 7da51941bf07b6b9ae96ab8c736eb59e9b506bae Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:59:34 -0400 Subject: [PATCH 24/41] Deleting big caution block --- docs/organizations/using-org-slugs.mdx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/organizations/using-org-slugs.mdx b/docs/organizations/using-org-slugs.mdx index fc5cb6cb75..cf9fe5c711 100644 --- a/docs/organizations/using-org-slugs.mdx +++ b/docs/organizations/using-org-slugs.mdx @@ -180,13 +180,6 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. - > [!CAUTION] - > It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and - > to handle the case where the expected organization is not active. In this case, we detect the actual organization slug - > as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and - > compare it to the active organization slug. If they don't match, we render an error message and the [Organization List](/docs/components/organization/organization-list) - > component to allow the user to select a valid organization. - ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} import { auth } from '@clerk/nextjs/server' import { OrganizationList } from '@clerk/nextjs' From 6e3000bf338be7532bb097a6d46380982aad2b03 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:01:57 -0400 Subject: [PATCH 25/41] Revert "Deleting big caution block" This reverts commit 7da51941bf07b6b9ae96ab8c736eb59e9b506bae. It was the wrong caution block! --- docs/organizations/using-org-slugs.mdx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/organizations/using-org-slugs.mdx b/docs/organizations/using-org-slugs.mdx index cf9fe5c711..fc5cb6cb75 100644 --- a/docs/organizations/using-org-slugs.mdx +++ b/docs/organizations/using-org-slugs.mdx @@ -180,6 +180,13 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. + > [!CAUTION] + > It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and + > to handle the case where the expected organization is not active. In this case, we detect the actual organization slug + > as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and + > compare it to the active organization slug. If they don't match, we render an error message and the [Organization List](/docs/components/organization/organization-list) + > component to allow the user to select a valid organization. + ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} import { auth } from '@clerk/nextjs/server' import { OrganizationList } from '@clerk/nextjs' From fe23e75bc462d961a9adad4c0b847a439304f480 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:02:22 -0400 Subject: [PATCH 26/41] Deleting the *correct* big caution block. Specifically the leading "recondsider slugs" block. --- docs/organizations/using-org-slugs.mdx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/docs/organizations/using-org-slugs.mdx b/docs/organizations/using-org-slugs.mdx index fc5cb6cb75..a285bd52e5 100644 --- a/docs/organizations/using-org-slugs.mdx +++ b/docs/organizations/using-org-slugs.mdx @@ -49,21 +49,6 @@ You can also use an [organization ID](/docs/references/javascript/organization/o This guide focuses on using a human-readable slug, but IDs are equally well-supported in Clerk's components. -> [!CAUTION] -> Think carefully about whether you need to include organization slugs in URLs! Consider: -> -> - Will organization-specific links be more useful to your users than -> organization-agnostic links? If most users will only ever be in one organization (that represents -> their company), they won't need the slug to disambiguate the organization when sharing -> links with their coworkers. Public documentation, marketing, and third-party blogs are also -> made easier to write if links aren't tied to any specific organization. -> - Adding an additional piece of state (i.e. the URL) that indicates the organization will require additional -> complexity in your application, as opposed to managing it soley and transparently in the Clerk -> session. -> -> We recommend against including organization slugs in URLs. If your application -> does have this requirement though, this guide will cover the best practices for doing so with a Next.js application. - ### Define URL patterns From 1b5b76fca494c00c91398af7d98a1ddf38fbf8d8 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:55:12 -0500 Subject: [PATCH 27/41] update file name --- docs/manifest.json | 4 ++-- .../{using-org-slugs.mdx => org-slugs-in-urls.mdx} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/organizations/{using-org-slugs.mdx => org-slugs-in-urls.mdx} (100%) diff --git a/docs/manifest.json b/docs/manifest.json index 71d57c7c28..16d63519a0 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -617,8 +617,8 @@ "href": "/docs/organizations/organization-workspaces" }, { - "title": "Using organization slugs in URLs", - "href": "/docs/organizations/using-org-slugs" + "title": "Use organization slugs in URLs", + "href": "/docs/organizations/org-slugs-in-urls" } ] ] diff --git a/docs/organizations/using-org-slugs.mdx b/docs/organizations/org-slugs-in-urls.mdx similarity index 100% rename from docs/organizations/using-org-slugs.mdx rename to docs/organizations/org-slugs-in-urls.mdx From 5d7de1ae9bcf4080c131cf45ddd64c491498982f Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:55:33 -0500 Subject: [PATCH 28/41] edit organization of doc; edit copy --- docs/organizations/org-slugs-in-urls.mdx | 126 +++++++++++------------ 1 file changed, 60 insertions(+), 66 deletions(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index a285bd52e5..7ffed676a8 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -28,31 +28,26 @@ description: Learn how to use organization slugs in URLs to manage the active Cl - Render organization-specific content -In this guide, you'll learn how to include an organization slug in your app's URLs and render information about the user's active organization at runtime. - -This guide is intended for apps that require organization slugs in URLs. **We generally recommend against doing this unless necessary**. Consider the following points before implementing: - -- User Relevance: Will organization-specific links add value for your users? If most users belong to a single organization (representing their company), they may not need slugs to identify the organization when sharing links with coworkers. Public documentation, marketing, and third-party blogs are also easier to write if links aren't tied to any specific organization. -- Application Complexity: Adding an organization slug to URLs introduces an extra piece of state to manage, increasing complexity compared to handling organization context within the Clerk session alone. - With [organizations](/docs/organizations/overview), users can create entities that represent their companies within your app. Users belong to multiple organizations but can only have one [active organization](/docs/organizations/overview#active-organization) at a time. Clerk sets an organization as - active when a user picks an organization with a prebuilt component (e.g., the - [``](/docs/components/organization/organization-switcher)). Additionally, Clerk provides APIs such as [`auth()`](/docs/references/nextjs/auth) and [`useAuth()`](/docs/references/react/use-auth)) that let you determine the active organization and render the appropriate content accordingly. - A common practice for organization-scoped spaces in an app is to prefix URLs with an organization slug. For example, imagine a B2B application named "Petstore" that has two customers: "Acmecorp" and "Widgetco." The app might use URLs such as the following: `https://petstore.example.com/orgs/``acmecorp``/dashboard` indicates **Acmecorp**'s dashboard `https://petstore.example.com/orgs/``widgetco``/dashboard` indicates **Widgetco**'s dashboard -You can also use an [organization ID](/docs/references/javascript/organization/organization#properties) in URLs to indicate the active organization: +You can also use an [org ID](/docs/references/javascript/organization/organization#properties) in URLs to indicate the active org: + +`https://petstore.example.com/orgs/``org_1a2b3c4d5e6f7g8e``/dashboard` **Acmecorp's org ID** indicates Acmecorp's dashboard + +In this guide, you'll learn how to include an org slug in your app's URLs and render information about the user's active org at runtime. -`https://petstore.example.com/orgs/``org_1a2b3c4d5e6f7g8e``/dashboard` **Acmecorp's organization ID** indicates Acmecorp's dashboard +This guide is intended for apps that require org slugs in URLs. **It's recommended against doing this unless necessary**. Consider the following points before implementing: -This guide focuses on using a human-readable slug, but IDs are equally well-supported in Clerk's components. +- User relevance: Will org-specific links add value for your users? If most users belong to a single org (representing their company), they may not need slugs to identify the org when sharing links with coworkers. Public documentation, marketing, and third-party blogs are also easier to write if links aren't tied to any specific org. +- Application complexity: Adding an org slug to URLs introduces an extra piece of state to manage, increasing complexity compared to handling org context within the Clerk session alone. ### Define URL patterns - Define which sections of your app are scoped to organizations and which belong to [personal accounts](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account). The following example assumes that the `/orgs/` prefix indicates an active organization, followed by the organization slug, and that the `/me/` prefix indicates an active personal account. + Define which sections of your app are scoped to orgs and which belong to [personal accounts](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account). The following example assumes that the `/orgs/` prefix indicates an active org, followed by the org slug, and that the `/me/` prefix indicates an active personal account. | URL | What should be active? | What should be displayed? | | - | - | - | @@ -61,20 +56,25 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp | `/me` | Personal account | Personal home | | `/me/settings` | Personal account | Personal settings | - ### Configure `` + ### Configure Clerk components The [``](/docs/components/organization/organization-switcher) and [``](/docs/components/organization/organization-list) components provide a robust set of options for managing slugs and IDs. - Since the sample app uses the personal account, set `hidePersonal` to `false` to ensure the personal account is selectable. You can allow users to customize their organization's URL slug when they initially create their organization by setting `hideSlug` to `false`. Additionally, you can add navigation instructions to these components. + Configure the components as follows: - For example, if the user selects or creates an organization with the slug `acmecorp`, then setting `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` to `/orgs/:slug` navigates the user to `/orgs/acmecorp`. If the user selects their personal account, they'll be navigated to `/me` via `afterSelectPersonalUrl`. + 1. Set `hideSlug` to `false` to allow users to customize their org's URL slug when they initially create their org. + 1. Set `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` to `/orgs/:slug` to navigate the user to the org's slug after they create or select an org, respectively. + 1. Set `hidePersonal` to `false` to ensure the personal account is selectable. + 1. Set `afterSelectPersonalUrl` to `/me` to navigate the user to their personal account after they select their personal account. + + For example, say the slug of the org is `acmecorp`. After the user uses the `` or `` component to create or select an org, they'll be redirected to `/orgs/acmecorp`. After the user uses one of these components to select their personal account, they'll be redirected to `/me`. ", ""]}> - ```tsx {{ filename: 'app/sidebar.tsx', mark: [6] }} + ```tsx {{ filename: 'app/header.tsx' }} import { OrganizationSwitcher } from '@clerk/nextjs' - export default function Sidebar() { + export default function Header() { return ( - ```tsx {{ filename: 'app/organization-list/[[...organization-list]]/page.tsx', mark: [6] }} + ```tsx {{ filename: 'app/organization-list/[[...organization-list]]/page.tsx' }} import { OrganizationList } from '@clerk/nextjs' export default function OrganizationListPage() { @@ -109,25 +109,23 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp - > [!TIP] - > For applications that use [organization IDs](/docs/references/javascript/organization/organization#properties) rather replace `:slug` with `:id` in both `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl`. + ### Use `clerkMiddleware()` to set the active organization - ### Use `clerkMiddleware` to set the active organization + > [!TIP] + > If your app doesn't use `clerkMiddleware()`, or you prefer to manually set the active org, the active org can be controlled via the client-side [`setActive()`](https://clerk.com/docs/references/javascript/clerk/session-methods) method. Refer to [this guide](https://clerk.com/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) to learn how to manually activate a specific org based on the URL. - With [`clerkMiddleware()`](https://clerk.com/docs/references/nextjs/clerk-middleware), you can declare URL patterns that determine whether a specific organization or the user's personal account should be activated. + With [`clerkMiddleware()`](https://clerk.com/docs/references/nextjs/clerk-middleware), you can use the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) property to declare URL patterns that determine whether a specific org or the user's personal account should be activated. - If the middleware detects one of these patterns in the URL and finds that a different organization is active in the session, it'll attempt to activate the specified organization. + If the middleware detects one of these patterns in the URL and finds that a different org is active in the session, it'll attempt to set the specified org as the active one. - For this app, the path `/orgs/:slug` with any optional trailing path segments should activate the organization indicated by the slug. To achieve this, define two `organizationPatterns`: one for the root (e.g., `/orgs/acmecorp`) and one using the wildcard matcher `(.*)` to match `/orgs/acmecorp/any/other/resource`. + In the following example, two `organizationPatterns` are defined: one for the root (e.g., `/orgs/acmecorp`) and one using the wildcard matcher `(.*)` to match `/orgs/acmecorp/any/other/resource`. This configuration ensures that the path `/orgs/:slug` with any optional trailing path segments will set the org indicated by the slug as the active one. - Use the same approach for the personal account with `personalAccountPatterns`. + The same approach is used with `personalAccountPatterns` to match the user's personal account. > [!WARNING] - > If no organization with the specified slug exists, or the user isn't a member of the organization, then `clerkMiddleware()` **won't** modify the active organization, leaving the previously active one on the Clerk session. + > If no org with the specified slug exists, or the user isn't a member of the org, then `clerkMiddleware()` **won't** modify the active org, leaving the previously active one on the Clerk session. - For guidance on specific middleware options, see the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) guide. - - ```tsx {{ filename: 'middleware.ts' }} + ```tsx {{ filename: 'middleware.ts', mark: [[7, 11]] }} import { clerkMiddleware } from '@clerk/nextjs/server' export default clerkMiddleware( @@ -152,25 +150,18 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp } ``` - > [!TIP] - > For applications that use [organization IDs](/docs/references/javascript/organization/organization#properties) rather replace `:slug` with `:id` in both `organizationPatterns` and `personalAccountPatterns`. - ### Build a component - Now that `clerkMiddleware()` is configured to activate organizations, you can build an organization-specific home page. - - First, handle cases where the middleware can't activate the organization based on the URL. This occurs if no organization with the specified slug exists, or if the given user isn't a member of the organization. + Now that `clerkMiddleware()` is configured to activate orgs, you can build an org-specific page. - When this happens, the middleware won't change the active organization, leaving the previously active one unchanged. For troubleshooting, a message will also be logged on the server: + First, handle cases where the middleware can't activate the org based on the URL. This occurs if no org with the specified slug exists, or if the given user isn't a member of the org. When this happens, the middleware won't change the active org, leaving the previously active one unchanged. For troubleshooting, a message will also be logged on the server: - > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. + > Clerk: org activation handshake loop detected. This is likely due to an invalid org ID or slug. Skipping org activation. > [!CAUTION] - > It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and - > to handle the case where the expected organization is not active. In this case, we detect the actual organization slug - > as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and - > compare it to the active organization slug. If they don't match, we render an error message and the [Organization List](/docs/components/organization/organization-list) - > component to allow the user to select a valid organization. + > It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and to handle the case where the expected org is not active. In this case, we detect the actual org slug as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and compare it to the active org slug. If they don't match, we render an error message and the [``](/docs/components/organization/organization-list) component to allow the user to select a valid org. + + The following example demonstrates how to handle the case where the active org slug doesn't match the URL slug: ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} import { auth } from '@clerk/nextjs/server' @@ -195,42 +186,43 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp ) } - return <>TODO + return ( + <> +

Welcome to organization {orgSlug}

+ + ) } ``` - In addition to middleware config, the active organization can controlled via the client-side [setActive](https://clerk.com/docs/references/javascript/clerk/session-methods) session method. See the [Hide Personal Accounts and force organizations](https://clerk.com/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) guide to learn how to manually activate a specific organization based on the URL. - - ### Render organization-specific content - - Render organization-specific content, including the organization's name. To see this data in a server component, [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include the organization name: + ### Render org-specific content - 1. In the Clerk Dashboard, navigate to the [Sessions](https://dashboard.clerk.com/last-active?path=sessions) page. - 1. In the **Customize session token** section, select **Edit**. - 1. In the modal that opens, add any claim you need to your session token. For this guide, add the following: + Use the following tabs to learn how to access org information on the server-side and client-side. - ```json - { - "org_name": "{{org.name}}" - } - ``` + + + To get org information on the server-side, access the [`Auth`](/docs/references/nextjs/auth-object){{ target: '_blank' }} object. In Next.js apps, this object is returned by [`auth()`](/docs/references/nextjs/auth){{ target: '_blank' }}. In other frameworks, you can access `Auth` via the [`getAuth()`](/docs/references/nextjs/get-auth){{ target: '_blank' }} helper. - 1. Select **Save**. + To access other org information, such as the org name, [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include it: - Now you can access the organization name in the [`sessionClaims`](/docs/references/nextjs/auth-object#:~:text=sessionClaims) - returned by `auth()`. + 1. In the Clerk Dashboard, navigate to the [**Sessions**](https://dashboard.clerk.com/last-active?path=sessions) page. + 1. In the **Customize session token** section, select **Edit**. + 1. In the modal that opens, add any claim you need to your session token. For this guide, add the following: + ```json + { + "org_name": "{{org.name}}" + } + ``` + 1. Select **Save**. - In client components, use the client-side [`useOrganization()`](/docs/references/react/use-organization) hook instead of the server-specific [`auth()`](/docs/references/nextjs/auth) hook to get the active organization. + Then you can access the [`sessionClaims`](/docs/references/nextjs/auth-object#:~:text=sessionClaims) + on the `Auth` object. - - ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} import { auth } from '@clerk/nextjs/server' import { OrganizationList } from '@clerk/nextjs' export default function Home({ params }: { params: { slug: string } }) { - const authObject = auth() - const orgSlug = authObject.orgSlug + const { orgSlug } = auth() if (params.slug != orgSlug) { return ( @@ -254,6 +246,8 @@ This guide focuses on using a human-readable slug, but IDs are equally well-supp + To get org information on the client-side, use the [`useOrganization()`](/docs/references/react/use-organization) hook to access the [`organization`](/docs/references/javascript/organization/organization){{ target: '_blank' }} object. + ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} 'use client' From 29066611c88478c18525f56896216655916ea151 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:57:54 -0500 Subject: [PATCH 29/41] fix links --- docs/organizations/org-slugs-in-urls.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index 7ffed676a8..af0629aa9b 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -112,9 +112,9 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend ### Use `clerkMiddleware()` to set the active organization > [!TIP] - > If your app doesn't use `clerkMiddleware()`, or you prefer to manually set the active org, the active org can be controlled via the client-side [`setActive()`](https://clerk.com/docs/references/javascript/clerk/session-methods) method. Refer to [this guide](https://clerk.com/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) to learn how to manually activate a specific org based on the URL. + > If your app doesn't use `clerkMiddleware()`, or you prefer to manually set the active org, the active org can be controlled via the client-side [`setActive()`](/docs/references/javascript/clerk/session-methods) method. Refer to [this guide](/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) to learn how to manually activate a specific org based on the URL. - With [`clerkMiddleware()`](https://clerk.com/docs/references/nextjs/clerk-middleware), you can use the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) property to declare URL patterns that determine whether a specific org or the user's personal account should be activated. + With [`clerkMiddleware()`](/docs/references/nextjs/clerk-middleware), you can use the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) property to declare URL patterns that determine whether a specific org or the user's personal account should be activated. If the middleware detects one of these patterns in the URL and finds that a different org is active in the session, it'll attempt to set the specified org as the active one. @@ -200,9 +200,9 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend - To get org information on the server-side, access the [`Auth`](/docs/references/nextjs/auth-object){{ target: '_blank' }} object. In Next.js apps, this object is returned by [`auth()`](/docs/references/nextjs/auth){{ target: '_blank' }}. In other frameworks, you can access `Auth` via the [`getAuth()`](/docs/references/nextjs/get-auth){{ target: '_blank' }} helper. + To get org information on the server-side, access the [`Auth`](/docs/references/nextjs/auth-object) object. In Next.js apps, this object is returned by [`auth()`](/docs/references/nextjs/auth). In other frameworks, you can access `Auth` via the [`getAuth()`](/docs/references/nextjs/get-auth) helper. - To access other org information, such as the org name, [customize the Clerk session token](https://clerk.com/docs/backend-requests/making/custom-session-token) to include it: + To access other org information, such as the org name, [customize the Clerk session token](/docs/backend-requests/making/custom-session-token) to include it: 1. In the Clerk Dashboard, navigate to the [**Sessions**](https://dashboard.clerk.com/last-active?path=sessions) page. 1. In the **Customize session token** section, select **Edit**. @@ -246,7 +246,7 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend - To get org information on the client-side, use the [`useOrganization()`](/docs/references/react/use-organization) hook to access the [`organization`](/docs/references/javascript/organization/organization){{ target: '_blank' }} object. + To get org information on the client-side, use the [`useOrganization()`](/docs/references/react/use-organization) hook to access the [`organization`](/docs/references/javascript/organization/organization) object. ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} 'use client' From d8376f28ad282bd88ede1d32f1edb72fe7200b96 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:07:27 -0500 Subject: [PATCH 30/41] Apply suggestions from code review --- docs/organizations/org-slugs-in-urls.mdx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index af0629aa9b..04b6c790eb 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -156,12 +156,9 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend First, handle cases where the middleware can't activate the org based on the URL. This occurs if no org with the specified slug exists, or if the given user isn't a member of the org. When this happens, the middleware won't change the active org, leaving the previously active one unchanged. For troubleshooting, a message will also be logged on the server: - > Clerk: org activation handshake loop detected. This is likely due to an invalid org ID or slug. Skipping org activation. + > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. - > [!CAUTION] - > It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and to handle the case where the expected org is not active. In this case, we detect the actual org slug as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and compare it to the active org slug. If they don't match, we render an error message and the [``](/docs/components/organization/organization-list) component to allow the user to select a valid org. - - The following example demonstrates how to handle the case where the active org slug doesn't match the URL slug: + It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and to handle the case where the expected org **isn't** active. In the following example, the org slug is detected as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and compared to the active org slug. If they don't match, an error message is rendered and the [``](/docs/components/organization/organization-list) component allows the user to select a valid org. ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} import { auth } from '@clerk/nextjs/server' From c69d69d92f6b1c276e56caadef022e9cfeda6793 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:33:50 -0500 Subject: [PATCH 31/41] Update docs/organizations/org-slugs-in-urls.mdx Co-authored-by: victoria --- docs/organizations/org-slugs-in-urls.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index 04b6c790eb..ceeeabb96a 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -109,7 +109,7 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend - ### Use `clerkMiddleware()` to set the active organization + ### Use `clerkMiddleware()` to set the active org > [!TIP] > If your app doesn't use `clerkMiddleware()`, or you prefer to manually set the active org, the active org can be controlled via the client-side [`setActive()`](/docs/references/javascript/clerk/session-methods) method. Refer to [this guide](/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) to learn how to manually activate a specific org based on the URL. From e438200dc3f5ea7ba13ea2f41cc9a1c477787191 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:15:22 -0500 Subject: [PATCH 32/41] Removing title quotes Thanks victoria! https://github.com/clerk/clerk-docs/pull/1664#discussion_r1830200900 --- docs/organizations/org-slugs-in-urls.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index ceeeabb96a..4fa3e45134 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -1,5 +1,5 @@ --- -title: 'Use organization slugs in URLs' +title: Use organization slugs in URLs description: Learn how to use organization slugs in URLs to manage the active Clerk organization. --- From 0425c1301084c7f21199941fcda09eff5262f096 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:16:46 -0500 Subject: [PATCH 33/41] Addressing https://github.com/clerk/clerk-docs/pull/1664#discussion_r1832785573 Thanks alexis and victoria! --- docs/organizations/org-slugs-in-urls.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index 4fa3e45134..cf8c551674 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -24,8 +24,8 @@ description: Learn how to use organization slugs in URLs to manage the active Cl ]} > - Allow users to choose an organization slug - - Include an organization slug in your URLs - - Render organization-specific content + - Include an org slug in your URLs + - Render org-specific content
A common practice for organization-scoped spaces in an app is to prefix URLs with an organization slug. For example, imagine a B2B application named "Petstore" that has two customers: "Acmecorp" and "Widgetco." The app might use URLs such as the following: From d9bf42f70e03e387c9e28380fbdaf76460764e85 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:42:37 -0500 Subject: [PATCH 34/41] Updating title to make it clear it's handling failed activation. I think this is more descriptive for the body anyway. Context: https://clerkinc.slack.com/archives/C078JFZLLQ3/p1734561625263129?thread_ts=1734560220.385059&cid=C078JFZLLQ3 --- docs/organizations/org-slugs-in-urls.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index cf8c551674..eaa2e39c0a 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -150,7 +150,7 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend } ``` - ### Build a component + ### Handle failed activation Now that `clerkMiddleware()` is configured to activate orgs, you can build an org-specific page. From abf0670b03434518ea14e391850f4c84bdd36d74 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:06:38 -0500 Subject: [PATCH 35/41] Clarifying and fixing formatting of a comment --- docs/organizations/org-slugs-in-urls.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index eaa2e39c0a..571fe17057 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -167,7 +167,9 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend export default function Home({ params }: { params: { slug: string } }) { const { orgSlug } = auth() - // Verify mismatch between route param and active organization slug from user's session + // Check for a match between route param and active organization slug + // from the user's session, and if so, show the list of valid + // organizations. if (params.slug != orgSlug) { return ( <> From 7790bdb14ccc93a09f32d9af43ef2df87fbcc3dc Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:07:07 -0500 Subject: [PATCH 36/41] Clarifying what's being displayed --- docs/organizations/org-slugs-in-urls.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index 571fe17057..0ad0d8bd1e 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -187,7 +187,7 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend return ( <> -

Welcome to organization {orgSlug}

+

Welcome to organization with slug {orgSlug}

) } From 1fafbe23e1c5348ff65f43e1064b0bb03b35fa49 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:10:40 -0500 Subject: [PATCH 37/41] Updating to new async server function pattern --- docs/organizations/org-slugs-in-urls.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index 0ad0d8bd1e..ea98a88f7c 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -164,8 +164,8 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend import { auth } from '@clerk/nextjs/server' import { OrganizationList } from '@clerk/nextjs' - export default function Home({ params }: { params: { slug: string } }) { - const { orgSlug } = auth() + export default async function Home({ params }: { params: { slug: string } }) { + const { orgSlug } = await auth(); // Check for a match between route param and active organization slug // from the user's session, and if so, show the list of valid @@ -220,8 +220,8 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend import { auth } from '@clerk/nextjs/server' import { OrganizationList } from '@clerk/nextjs' - export default function Home({ params }: { params: { slug: string } }) { - const { orgSlug } = auth() + export default async function Home({ params }: { params: { slug: string } }) { + const { orgSlug } = await auth() if (params.slug != orgSlug) { return ( From f65327bb2dcdaa6e309e6a3b1d468ef3fc4bf57a Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:22:27 -0500 Subject: [PATCH 38/41] Better copy Thanks @Laura! Laura Co-authored-by: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> --- docs/organizations/org-slugs-in-urls.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index ea98a88f7c..aad56d9ac4 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -152,9 +152,9 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend ### Handle failed activation - Now that `clerkMiddleware()` is configured to activate orgs, you can build an org-specific page. + Now that `clerkMiddleware()` is configured to activate orgs, you can build an org-specific page while handling cases where the org can't be activated. - First, handle cases where the middleware can't activate the org based on the URL. This occurs if no org with the specified slug exists, or if the given user isn't a member of the org. When this happens, the middleware won't change the active org, leaving the previously active one unchanged. For troubleshooting, a message will also be logged on the server: + Failed activation occurs if no org with the specified slug exists, or if the given user isn't a member of the org. When this happens, the middleware won't change the active org, leaving the previously active one unchanged. For troubleshooting, a message will also be logged on the server: > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. From 3957f819d0534a2c1d8552081c1cf7a8d750b40d Mon Sep 17 00:00:00 2001 From: vi Date: Thu, 19 Dec 2024 17:13:32 -0500 Subject: [PATCH 39/41] update --- docs/organizations/org-slugs-in-urls.mdx | 183 +++++++++++++---------- 1 file changed, 106 insertions(+), 77 deletions(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index aad56d9ac4..c33da1cc8e 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -1,6 +1,6 @@ --- title: Use organization slugs in URLs -description: Learn how to use organization slugs in URLs to manage the active Clerk organization. +description: Learn how to use organization slugs in your application URLs to manage and switch between active Clerk organizations. --- - Allow users to choose an organization slug - - Include an org slug in your URLs - - Render org-specific content + - Include an organization slug in your URLs + - Render organization-specific content -A common practice for organization-scoped spaces in an app is to prefix URLs with an organization slug. For example, imagine a B2B application named "Petstore" that has two customers: "Acmecorp" and "Widgetco." The app might use URLs such as the following: +Organization slugs are human-readable URL identifiers that help users reference which organization they're working in. A common pattern for organization-scoped areas in an application is to include the organization slug in the URL path. -`https://petstore.example.com/orgs/``acmecorp``/dashboard` indicates **Acmecorp**'s dashboard -`https://petstore.example.com/orgs/``widgetco``/dashboard` indicates **Widgetco**'s dashboard +For example, a B2B application named "Petstore" has two customer organizations: **Acmecorp** and **Widgetco**. Each organization uses its name as a slug in the URL: -You can also use an [org ID](/docs/references/javascript/organization/organization#properties) in URLs to indicate the active org: +- **Acmecorp**: `https://petstore.example.com/orgs/`**`acmecorp`**`/dashboard` +- **Widgetco**: `https://petstore.example.com/orgs/`**`widgetco`**`/dashboard` -`https://petstore.example.com/orgs/``org_1a2b3c4d5e6f7g8e``/dashboard` **Acmecorp's org ID** indicates Acmecorp's dashboard +Alternatively, [organization IDs](/docs/references/javascript/organization/organization#properties) can be used to identify organizations in URLs: -In this guide, you'll learn how to include an org slug in your app's URLs and render information about the user's active org at runtime. +- **Acmecorp**: `https://petstore.example.com/orgs/`**`org_1a2b3c4d5e6f7g8e`**`/dashboard` +- **Widgetco**: `https://petstore.example.com/orgs/`**`org_1a2b3c4d5e6f7g8f`**`/dashboard` -This guide is intended for apps that require org slugs in URLs. **It's recommended against doing this unless necessary**. Consider the following points before implementing: +### When to use organization slugs -- User relevance: Will org-specific links add value for your users? If most users belong to a single org (representing their company), they may not need slugs to identify the org when sharing links with coworkers. Public documentation, marketing, and third-party blogs are also easier to write if links aren't tied to any specific org. -- Application complexity: Adding an org slug to URLs introduces an extra piece of state to manage, increasing complexity compared to handling org context within the Clerk session alone. +This feature is intended for apps that **require** organization slugs in URLs. **Adding slugs to URLs isn't recommended unless necessary.** + +**Use** organization slugs if: + +- Users frequently share links for public-facing content (e.g., documentation, marketing materials, and third-party blogs) +- Users regularly switch between multiple organizations +- Organization-specific URLs provide meaningful context + +**Don't** use organization slugs if: + +- Most users belong to only one organization +- You want to keep URLs simple and consistent +- You're primarily using the Clerk session for organization context + +This guide shows you how to add organization slugs to your app's URLs, configure Clerk components to handle slug-based navigation, and access organization data based on the URL slug at runtime. - ### Define URL patterns + ## Configure your app's URL structure + + Your application URLs should be structured to indicate which sections of your app are scoped to organizations versus [personal accounts](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account). + + The following example uses the following URL structure: - Define which sections of your app are scoped to orgs and which belong to [personal accounts](/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account). The following example assumes that the `/orgs/` prefix indicates an active org, followed by the org slug, and that the `/me/` prefix indicates an active personal account. + - `/orgs/` indicates the **active organization**, followed by the **organization slug** + - `/me/` indicates the **active personal account** | URL | What should be active? | What should be displayed? | | - | - | - | - | `/orgs/acmecorp` | Organization Acmecorp | Acmecorp's home | - | `/orgs/acmecorp/settings` | Organization Acmecorp | Acmecorp's settings | - | `/me` | Personal account | Personal home | - | `/me/settings` | Personal account | Personal settings | + | `/orgs/acmecorp` | Organization Acmecorp | Acmecorp's home page | + | `/orgs/acmecorp/settings` | Organization Acmecorp | Acmecorp's settings page | + | `/me` | Personal account | Personal home page | + | `/me/settings` | Personal account | Personal settings page | - ### Configure Clerk components + ## Configure `` and `` - The [``](/docs/components/organization/organization-switcher) and [``](/docs/components/organization/organization-list) components provide a robust set of options for managing slugs and IDs. + The [``](/docs/components/organization/organization-switcher) and [``](/docs/components/organization/organization-list) components provide a robust set of options to manage organization slugs and IDs in your application's URLs. - Configure the components as follows: + Set the following properties to configure the components to handle slug-based navigation: - 1. Set `hideSlug` to `false` to allow users to customize their org's URL slug when they initially create their org. - 1. Set `afterCreateOrganizationUrl` and `afterSelectOrganizationUrl` to `/orgs/:slug` to navigate the user to the org's slug after they create or select an org, respectively. - 1. Set `hidePersonal` to `false` to ensure the personal account is selectable. - 1. Set `afterSelectPersonalUrl` to `/me` to navigate the user to their personal account after they select their personal account. + - Set `hideSlug` to `false` to allow users to customize the organization's URL slug when creating an organization. + - Set `hidePersonal` to `false` to allow users to select their personal account. + - Set `afterCreateOrganizationUrl` to `/orgs/:slug` to navigate the user to the organization's slug after creating an organization. + - Set `afterSelectOrganizationUrl` to `/orgs/:slug` to navigate the user to the organization's slug after selecting it. + - Set `afterSelectPersonalUrl` to `/me` to navigate the user to their personal account after selecting it. - For example, say the slug of the org is `acmecorp`. After the user uses the `` or `` component to create or select an org, they'll be redirected to `/orgs/acmecorp`. After the user uses one of these components to select their personal account, they'll be redirected to `/me`. + For example, if the organization has the slug `acmecorp`: + + - When a user creates or selects that organization using either component, they'll be redirected to `/orgs/acmecorp`. + - When a user selects their personal account using either component, they'll be redirected to `/me`. ", ""]}> - ```tsx {{ filename: 'app/header.tsx' }} + ```tsx {{ filename: 'components/Header.tsx', mark: [[6, 10]] }} import { OrganizationSwitcher } from '@clerk/nextjs' export default function Header() { return ( ) } @@ -90,18 +112,17 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend - ```tsx {{ filename: 'app/organization-list/[[...organization-list]]/page.tsx' }} + ```tsx {{ filename: 'app/organization-list/[[...organization-list]]/page.tsx', mark: [[6, 10]] }} import { OrganizationList } from '@clerk/nextjs' export default function OrganizationListPage() { return ( ) } @@ -109,23 +130,23 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend - ### Use `clerkMiddleware()` to set the active org + ## Configure `clerkMiddleware()` to set the active organization > [!TIP] - > If your app doesn't use `clerkMiddleware()`, or you prefer to manually set the active org, the active org can be controlled via the client-side [`setActive()`](/docs/references/javascript/clerk/session-methods) method. Refer to [this guide](/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) to learn how to manually activate a specific org based on the URL. + > If your app doesn't use `clerkMiddleware()`, or you prefer to manually set the active organization, use the client-side [`setActive()`](/docs/references/javascript/clerk/session-methods) method to control the active organization via the client-side. See [this guide](/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) to learn how to manually activate a specific organization based on the URL. - With [`clerkMiddleware()`](/docs/references/nextjs/clerk-middleware), you can use the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) property to declare URL patterns that determine whether a specific org or the user's personal account should be activated. + With [`clerkMiddleware()`](/docs/references/nextjs/clerk-middleware), you can use the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) property to declare URL patterns that determine whether a specific organization or user's personal account should be activated. - If the middleware detects one of these patterns in the URL and finds that a different org is active in the session, it'll attempt to set the specified org as the active one. + If the middleware detects one of these patterns in the URL and finds that a different organization is active in the session, it'll attempt to set the specified organization as the active one. - In the following example, two `organizationPatterns` are defined: one for the root (e.g., `/orgs/acmecorp`) and one using the wildcard matcher `(.*)` to match `/orgs/acmecorp/any/other/resource`. This configuration ensures that the path `/orgs/:slug` with any optional trailing path segments will set the org indicated by the slug as the active one. + In the following example, two `organizationPatterns` are defined: one for the root (e.g., `/orgs/acmecorp`) and one as the wildcard matcher `(.*)` to match `/orgs/acmecorp/any/other/resource`. This configuration ensures that the path `/orgs/:slug` with any optional trailing path segments will set the organization indicated by the slug as the active one. - The same approach is used with `personalAccountPatterns` to match the user's personal account. + The same pattern is used with `personalAccountPatterns` to match the user's personal account. > [!WARNING] - > If no org with the specified slug exists, or the user isn't a member of the org, then `clerkMiddleware()` **won't** modify the active org, leaving the previously active one on the Clerk session. + > If no organization with the specified slug exists, or if the user isn't a member of the organization, then `clerkMiddleware()` **won't** modify the active organization. Instead, it will leave the previously active organization unchanged on the Clerk session. - ```tsx {{ filename: 'middleware.ts', mark: [[7, 11]] }} + ```tsx {{ filename: 'middleware.ts', mark: [[7, 18]] }} import { clerkMiddleware } from '@clerk/nextjs/server' export default clerkMiddleware( @@ -134,8 +155,14 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend }, { organizationSyncOptions: { - organizationPatterns: ['/orgs/:slug', '/orgs/:slug/(.*)'], - personalAccountPatterns: ['/me', '/me/(.*)'], + organizationPatterns: [ + '/orgs/:slug', // Match the org slug + '/orgs/:slug/(.*)', // Wildcard match for optional trailing path segments + ], + personalAccountPatterns: [ + '/me', // Match the personal account + '/me/(.*)', // Wildcard match for optional trailing path segments + ], }, }, ) @@ -152,31 +179,35 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend ### Handle failed activation - Now that `clerkMiddleware()` is configured to activate orgs, you can build an org-specific page while handling cases where the org can't be activated. + Now that `clerkMiddleware()` is configured to activate organizations, you can build an organization-specific page while handling cases where the organization can't be activated. + + Failed activation occurs if no organization with the specified slug exists, or if the given user isn't a member of the organization. When this happens, the middleware won't change the active organization, leaving the previously active one unchanged. - Failed activation occurs if no org with the specified slug exists, or if the given user isn't a member of the org. When this happens, the middleware won't change the active org, leaving the previously active one unchanged. For troubleshooting, a message will also be logged on the server: + For troubleshooting, a message will also be logged on the server: > Clerk: Organization activation handshake loop detected. This is likely due to an invalid organization ID or slug. Skipping organization activation. - It is ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and to handle the case where the expected org **isn't** active. In the following example, the org slug is detected as a NextJS [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param, and compared to the active org slug. If they don't match, an error message is rendered and the [``](/docs/components/organization/organization-list) component allows the user to select a valid org. + It's ultimately the responsibility of the page to ensure that it renders the appropriate content for a given URL, and to handle the case where the expected organization **isn't** active. + + In the following example, the organization slug is detected as a Next.js [Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) param and passed as a parameter to the page. If the slug doesn't match the active organization slug, an error message is rendered and the [``](/docs/components/organization/organization-list) component allows the user to select a valid organization. - ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} + ```tsx {{ filename: 'app/orgs/[slug]/page.tsx' }} import { auth } from '@clerk/nextjs/server' import { OrganizationList } from '@clerk/nextjs' export default async function Home({ params }: { params: { slug: string } }) { - const { orgSlug } = await auth(); + const { orgSlug } = await auth() - // Check for a match between route param and active organization slug - // from the user's session, and if so, show the list of valid - // organizations. + // Check if the organization slug from the URL params doesn't match + // the active organization slug from the user's session. + // If they don't match, show an error message and the list of valid organizations. if (params.slug != orgSlug) { return ( <>

Sorry, organization {params.slug} is not valid.

-

Welcome to organization with slug {orgSlug}

- - ) + return
Welcome to organization {orgSlug}
} ``` - ### Render org-specific content + ## Render organization-specific content - Use the following tabs to learn how to access org information on the server-side and client-side. + Use the following tabs to learn how to access organization information on the server-side and client-side. - To get org information on the server-side, access the [`Auth`](/docs/references/nextjs/auth-object) object. In Next.js apps, this object is returned by [`auth()`](/docs/references/nextjs/auth). In other frameworks, you can access `Auth` via the [`getAuth()`](/docs/references/nextjs/get-auth) helper. + To get organization information on the server-side, access the [`Auth`](/docs/references/nextjs/auth-object) object. In Next.js apps, this object is returned by [`auth()`](/docs/references/nextjs/auth). In other frameworks, use the [`getAuth()`](/docs/references/nextjs/get-auth) helper to get the `Auth` object. - To access other org information, such as the org name, [customize the Clerk session token](/docs/backend-requests/making/custom-session-token) to include it: + To access additional organization information like the organization name, you'll need to [customize the Clerk session token](/docs/backend-requests/making/custom-session-token) to include these details: 1. In the Clerk Dashboard, navigate to the [**Sessions**](https://dashboard.clerk.com/last-active?path=sessions) page. 1. In the **Customize session token** section, select **Edit**. @@ -213,10 +240,10 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend ``` 1. Select **Save**. - Then you can access the [`sessionClaims`](/docs/references/nextjs/auth-object#:~:text=sessionClaims) + You can now access the [`sessionClaims`](/docs/references/nextjs/auth-object#:~:text=sessionClaims) on the `Auth` object. - ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} + ```tsx {{ filename: 'app/orgs/[slug]/page.tsx', mark: [[23, 24]] }} import { auth } from '@clerk/nextjs/server' import { OrganizationList } from '@clerk/nextjs' @@ -228,8 +255,8 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend <>

Sorry, organization {params.slug} is not valid.

) } - let orgName = authObject.sessionClaims['org_name'] as string - return
{orgName &&

Welcome to organization {orgName}

}
+ // Access the organization name from the session claims + let orgName = authObject.sessionClaims['org_name'] as string + return
{orgName && `Welcome to organization ${orgName}`}
} ```
- To get org information on the client-side, use the [`useOrganization()`](/docs/references/react/use-organization) hook to access the [`organization`](/docs/references/javascript/organization/organization) object. + To get organization information on the client-side, use the [`useOrganization()`](/docs/references/react/use-organization) hook to access the [`organization`](/docs/references/javascript/organization/organization) object. - ```tsx {{ filename: 'src/app/orgs/[slug]/page.tsx' }} + ```tsx {{ filename: 'app/orgs/[slug]/page.tsx', mark: [24] }} 'use client' import { OrganizationList, useOrganization } from '@clerk/nextjs' @@ -270,7 +298,8 @@ This guide is intended for apps that require org slugs in URLs. **It's recommend ) } - return
{organization &&

Welcome to organization {organization.name}

}
+ // Access the organization name from the organization object + return
{organization && `Welcome to organization ${organization.name}`}
} ```
From ba9a40b8a7a5bb1edb128dff90d39d3839731495 Mon Sep 17 00:00:00 2001 From: vi Date: Thu, 19 Dec 2024 17:20:57 -0500 Subject: [PATCH 40/41] fix steps for tutorialhero --- docs/organizations/org-slugs-in-urls.mdx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index c33da1cc8e..3f1a6800ee 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -23,8 +23,9 @@ description: Learn how to use organization slugs in your application URLs to man } ]} > - - Allow users to choose an organization slug - - Include an organization slug in your URLs + - Configure your app's URL structure for organizations + - Configure Clerk components to handle organization slugs + - Set up middleware to sync organizations with URLs - Render organization-specific content From 42f5802e6f82a29b01a49402ddde19144bc24980 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:18:10 -0500 Subject: [PATCH 41/41] Apply suggestions from code review --- docs/organizations/org-slugs-in-urls.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/organizations/org-slugs-in-urls.mdx b/docs/organizations/org-slugs-in-urls.mdx index 3f1a6800ee..b9fc090ff9 100644 --- a/docs/organizations/org-slugs-in-urls.mdx +++ b/docs/organizations/org-slugs-in-urls.mdx @@ -18,7 +18,7 @@ description: Learn how to use organization slugs in your application URLs to man ]} exampleRepo={[ { - title: "Set up org slugs in URLs app", + title: "Demo app", link: "https://github.com/clerk/orgs/tree/main/examples/sync-org-with-url" } ]} @@ -45,7 +45,7 @@ Alternatively, [organization IDs](/docs/references/javascript/organization/organ This feature is intended for apps that **require** organization slugs in URLs. **Adding slugs to URLs isn't recommended unless necessary.** -**Use** organization slugs if: +Use organization slugs if: - Users frequently share links for public-facing content (e.g., documentation, marketing materials, and third-party blogs) - Users regularly switch between multiple organizations @@ -134,7 +134,7 @@ This guide shows you how to add organization slugs to your app's URLs, configure ## Configure `clerkMiddleware()` to set the active organization > [!TIP] - > If your app doesn't use `clerkMiddleware()`, or you prefer to manually set the active organization, use the client-side [`setActive()`](/docs/references/javascript/clerk/session-methods) method to control the active organization via the client-side. See [this guide](/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) to learn how to manually activate a specific organization based on the URL. + > If your app doesn't use `clerkMiddleware()`, or you prefer to manually set the active organization, use the [`setActive()`](/docs/references/javascript/clerk/session-methods) method to control the active organization on the client-side. See [this guide](/docs/guides/force-organizations#set-an-active-organization-based-on-the-url) to learn how to manually activate a specific organization based on the URL. With [`clerkMiddleware()`](/docs/references/nextjs/clerk-middleware), you can use the [`organizationSyncOptions`](/docs/references/nextjs/clerk-middleware#organization-sync-options) property to declare URL patterns that determine whether a specific organization or user's personal account should be activated.