Docs

Hide Personal Accounts and force organizations

You will learn the following:

  • Hide Personal Accounts from UI components
  • Detect an active organization
  • Set an active organization
  • Limit access to users with active organizations only

Introduction

In this guide, you will learn how to hide a user's Personal Account in order to appear as if they only have access to organizations. You will also learn how to limit access to your application to only users with active organizations, further enforcing organization-centric access. This is useful for applications that are built for organizations only, such as B2B applications.

This guide will be written for Next.js applications using App Router, but the same concepts can be applied to any application using Clerk.

Hide Personal Accounts from UI components

To begin forcing organizations in your application, you need to remove a user's Personal Account from the UI. A user's Personal Account cannot be disabled; it can only be hidden.

If you have an application set up to use organizations, you might have the <OrganizationList /> and <OrganizationSwitcher /> components in your application. These components will show a user's Personal Account by default, but you can hide it by passing the hidePersonal prop.

app/organization-list/[[...organization-list]]/page.tsx
import { OrganizationList } from '@clerk/nextjs'

export default function OrganizationListPage() {
  return (
    <OrganizationList
      hidePersonal={true}
    />
  );
};
app/sidebar.tsx
import { OrganizationSwitcher } from '@clerk/nextjs'

export default function Sidebar() {
  return (
    <OrganizationSwitcher
      hidePersonal={true}
    />
  );
};

Detect and set an active organization

With Clerk, a user can have many organization memberships, but only one of them can be active at a time.

Detect an active organization

A session will always include an orgId via the Auth object. This can be used to detect if a user has an active organization.

The auth() helper function can be used to get the orgId from the session server-side. If the orgId is null, then the user does not have an active organization.

import { auth } from '@clerk/nextjs/server'

export default function Layout() {
  const { orgId } = auth();

  const hasActiveOrg = orgId !== null;
  ...
}

The useAuth() hook can be used to get the orgId from the session client-side. If the orgId is null, then the user does not have an active organization.

"use client"
import { useAuth } from '@clerk/nextjs'

export default function Layout() {
  const { orgId } = useAuth();

  const hasActiveOrg = orgId !== null;
  ...
}

Set an active organization

In the case of Clerk components, an organization will automatically be set as active each time the user creates an organization, accepts an invitation, or selects a membership from the organization switcher. In the case of custom flows, you will need to implement the logic for setting an organization as active. Clerk's useOrganizationList() hook provides a setActive method to help you with this.

In the example below, a custom organization switcher is created. It allows a user to select an organization from a list of their memberships. The useOrganizationList() hook is used to fetch a list of the user's memberships, and the setActive method is used to set the selected organization as active.

Warning

Setting an active organization can only be performed client-side.

app/custom-org-switcher.tsx
"use client"

import { useOrganizationList } from "@clerk/nextjs";

export const CustomOrgSwitcher = () => {
  const { isLoaded, setActive, userMemberships } = useOrganizationList({
    userMemberships: {
      infinite: true,
    },
  });

  if (!isLoaded) {
    return <>Loading</>;
  }

  return (
    <>
      <ul>
        {userMemberships.data?.map((mem) => (
          <li key={mem.id}>
            <span>{mem.organization.name}</span>
            <button
              onClick={() => setActive({ organization: mem.organization.id })}
            >
              Select
            </button>
          </li>
        ))}
      </ul>

      <button
        disabled={!userMemberships.hasNextPage}
        onClick={() => userMemberships.fetchNext()}
      >
        Load more
      </button>
    </>
  );
};

Set an active organization based on the URL

Warning

This approach still depends on the setActive method, which only runs on the client. Due to this limitation, during SSR, it is possible that orgId from auth() returns an incorrect value that does not match the organization ID in the URL.

In some cases, you might want to set an active organization based on the URL. For example, if you have a URL like /organizations/org_123, you might want to set org_123 as the active organization.

In the example below, a component called SyncActiveOrganization is created. The Next.js useParams() hook is used to get the organization ID from the URL, and then the setActive method is used to set the organization as active.

app/utils/sync-active-organization.tsx
"use client"

import { useEffect } from "react"
import { redirect, useParams } from "next/navigation"
import { useAuth, useOrganizationList } from "@clerk/nextjs"

export function SyncActiveOrganization() {
  const { setActive, isLoaded } = useOrganizationList();

  // Get the organization ID from the session
  const { orgId } = useAuth();

  // Get the organization ID from the URL
  const { orgId: urlOrgId } = useParams() as { orgId: string };

  useEffect(() => {
    if (!isLoaded) return;

    // If the org ID in the URL is not valid, redirect to the homepage
    if (!urlOrgId?.startsWith("org_")) {
      redirect("/");
      return;
    }

    // If the org ID in the URL is not the same as the org ID in the session (the active organization), set the active organization to be the org ID from the URL
    if (urlOrgId && urlOrgId !== orgId) {
      void setActive({ organization: urlOrgId });
    }
  }, [orgId, isLoaded, setActive, urlOrgId])

  return null;
}

Now you can place the SyncActiveOrganization helper that you created above in a layout and use the layout for all pages that require an active organization. In the example below, the SyncActiveOrganization component will run on every page in the /app/[orgId] directory.

app/[orgId]/layout.tsx

import { type PropsWithChildren } from "react"
import { SyncActiveOrganization } from '../utils/sync-active-organization'

export default function OrganizationLayout(props: PropsWithChildren) {
  return (
    <>
      <SyncActiveOrganization/>
      {props.children}
    </>
  );
}

Limit access to users with active organizations only

Now that you have hidden Personal Accounts from the UI and can detect and set an active organization, you can limit access to your application to users with active organizations only. This will ensure that users without active organizations cannot access your application.

It's possible for a user to be signed in, but not have an active organization. This can happen in two cases:

  • The user created an account and hasn't created an organization yet
  • The user joined or created multiple organizations, and left or deleted the active organization

There are two ways to limit access to users with active organizations only:

Based on your use case, you can decide to limit users either in the entire app or a specific part of it. For example, a B2B application might need to limit access to only users with an active organization, whereas a B2B2C application might limit only the /dashboard path to users with an active organization.

Limit access using the clerkMiddleware() helper

Clerk's clerkMiddleware() helper can be used in both App Router and Pages Router applications to limit access to users with active organizations only.

In the example below, the clerkMiddleware() helper is used to redirect signed in users to the organization selection page if they are not active in an organization.

middleware.ts
import { clerkMiddleware } from '@clerk/nextjs/server';

export default clerkMiddleware((auth, req) => {
  const { userId, orgId } = auth();

  // Redirect signed in users to organization selection page if they are not active in an organization
  if(userId && !orgId && req.nextUrl.pathname !== "/org-selection"){
    const searchParams = new URLSearchParams({redirectUrl: req.url});

    const orgSelection = new URL(`/org-selection?${searchParams.toString()}`, req.url);

    return NextResponse.redirect(orgSelection);
  }
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

To limit access to a specific part of the application, you can check if the path the user is visiting is the path you want to limit access to. In this example, the /dashboard path is limited to users with active organizations only. If the user is not active in an organization, they will be redirected to the /dashboard/org-selection page.

middleware.ts
import { clerkMiddleware } from '@clerk/nextjs/server';

export default clerkMiddleware((auth, req) => {
  const { userId, orgId } = auth();

  // Redirect signed in users to organization selection page if they are not active in an organization
  if(
    userId &&
    !orgId &&
    req.nextUrl.pathname.startsWith('/dashboard') &&
    req.nextUrl.pathname !== "/dashboard/org-selection"
  ){
    const searchParams = new URLSearchParams({redirectUrl: req.url})

    const orgSelection = new URL(`/dashboard/org-selection?${searchParams.toString()}`, req.url);

    return NextResponse.redirect(orgSelection);
  }
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

Now that you have configured your middleware to redirect users to the /org-selection page if they are not active in an organization, you need to create the /org-selection page. This page will allow users to select an organization or create their own organization.

app/org-selection/page.tsx
"use client"

import { OrganizationList } from "@clerk/nextjs"
import { useSearchParams } from "next/navigation";

export default function OrganizationSelection() {
  const searchParams = useSearchParams();
  const redirectUrl = searchParams.get('redirectUrl') ?? "/";

  return (
    <section>
      <h1>Welcome to the Organization Selection page.</h1>
      <p>
        This part of the application requires the user to select an
        organization in order to proceed. If you are not part of an
        organization, you can accept an invitation or create your own
        organization.
      </p>
      <OrganizationList
        hidePersonal={true}
        afterCreateOrganizationUrl={redirectUrl}
        afterSelectOrganizationUrl={redirectUrl}
      />
    </section>
  )
}
pages/org-selection.tsx
import { OrganizationList } from "@clerk/nextjs"
import { useRouter } from "next/router";

export default function OrganizationSelection() {
  const { query } = useRouter();
  const redirectUrl = query.redirectUrl ?? "/"

  return (
    <section>
      <h1>Welcome to the Organization Selection page.</h1>
      <p>
        This part of the application requires the user to select an
        organization in order to proceed. If you are not part of an
        organization, you can accept an invitation or create your own
        organization.
      </p>
      <OrganizationList
        hidePersonal={true}
        afterCreateOrganizationUrl={redirectUrl}
        afterSelectOrganizationUrl={redirectUrl}
      />
    </section>
  )
}

Limit access using an App Router layout

In Next.js App Router applications, instead of using clerkMiddleware(), you also have the option to use a layout to limit access to users with active organizations only.

In the example below, a component called RequiredActiveOrgLayout is created. This component will be used as a layout for all pages that require an active organization. If the user has an active organization, the route the user is visting will be rendered. If the user does not have an active organization, the organization selection page will be rendered.

app/(with-active-organization)/layout.tsx
  import { OrganizationList } from "@clerk/nextjs"
  import { auth } from "@clerk/nextjs/server"
  import { PropsWithChildren } from "react"

  export default function RequiredActiveOrgLayout(props: PropsWithChildren) {
    // Get the organization ID from the session
    const { orgId } = auth()

    // If the user has an active organization, render the children
    if (orgId) {
      return props.children
    }

    // If the user does not have an active organization, render the organization selection page
    return (
      <section>
        <h1>Welcome to the Organization Selection page.</h1>
        <p>
          This part of the application requires the user to select an
          organization in order to proceed. If you are not part of an
          organization, you can accept an invitation or create your own
          organization.
        </p>
        <OrganizationList hidePersonal={true} />
      </section>
    )
  }

Finished 🎉

You have configured your Clerk application to hide personal accounts and enforce organization-centric access. By following this guide, you've learned how to:

  • hide Personal Accounts from UI components
  • detect an active organization by using Clerk's auth() helper function or useAuth() hook
  • set an active organization automatically through Clerk components or manually using the setActive method
  • set an active organization based on the URL
  • limit access to your application to only users with active organizations by utilizing Clerk's clerkMiddleware() helper or by implementing an App Router layout

Now that you've completed these steps, your application is well-configured for organizational use, providing a streamlined experience for users associated with organizations.

Feedback

What did you think of this content?