Cosmic

How to Build Secure Workspace Invites with Clerk: Owners, Members and Invite Links

Saad Ahmed · Founder5 min read

Short answer

As of October 2026, model each workspace as a Clerk organization, make its creator the Admin owner, and cap its membership at your plan's seat limit. For invites, Clerk's email invitations work when people sign up by email; if they sign up with Google or GitHub, use single-use, expiring, hashed invite links that add the person through Clerk's membership API. Redeem each link in one transaction that locks the code and the workspace, and undo the Clerk add if anything fails.

Key takeaways

  • A secure invite works once, expires, can be revoked, respects the seat limit under a lock and stores only a hash of the code.
  • Clerk's email invitations need email sign-up turned on; without it Clerk refuses every invitation.
  • Invite links that call Clerk's membership API work with Google or GitHub sign-up and any email address.
  • Clerk treats a brand-new account as pending and signed out by default, so the redeem route must accept pending sessions.
  • Treat Clerk timeouts and server errors as unavailable, never as an invalid user, or one outage signs everyone out.
  • Check what Clerk's Member role actually grants on your instance; ours could rename the workspace despite the documented default.

If you're building a SaaS app where people need their own workspaces, can add members to them, have a designated owner, and you want all of it done securely with an auth provider like Clerk, this post is for you.

Written in October 2026. Clerk's features, settings and limits are described as they stood that month.

Cosmic built this for ecco, an agent work coordinator: a shared task board where people and their AI agents work in the same workspace. Everything below came out of shipping it to production, including five bugs we hit along the way.

A secure invite follows six rules

Most invite bugs are one of these rules missing. Check your design against the table before you write any code.

The six rules, and what each one stops
RuleWhat it stops
Only an owner can create an inviteA member quietly adding people the owner never approved
An invite works onceOne leaked link letting a whole group in
An invite expiresAn old link in a chat history working months later
An owner can revoke itA link sent to the wrong person staying live
Redeeming checks the seat limit, under a lockTwo people joining at once and pushing a workspace past its plan
A wrong code looks like every other wrong codeA guesser learning which codes exist

Store only a hash of each code, never the code itself. If your database leaks, the open invites don't leak with it.

Clerk models workspaces as organizations

In Clerk, a workspace is an organization. People belong to it through a membership, and each membership has a role.

Clerk's diagram of applications containing organizations, each with users, roles and permissions
Applications hold organizations; organizations hold users with roles and permissions. · Diagram: Clerk docs

Two settings carry most of the security:

  • Creator's initial role. Whoever creates an organization gets this role. Set it to Admin, and the creator becomes the owner.
  • Default membership limit. Clerk caps how many people an organization holds, and the cap counts pending invitations. Set it to your plan's seat limit.
Clerk's Organizations Settings page with the creator's initial role set to Admin and a membership limit of 5
Organizations Settings in Clerk: the creator's initial role and the membership limit. · Screenshot: Clerk

Clerk's two built-in roles, Admin and Member, are enough for "one owner, everyone else a member". As of October 2026, custom roles are a paid Clerk feature, so we kept to the two built-in ones: the creator is the only Admin, everyone invited joins as a Member, and the app never offers Admin. That also means no one can remove the owner.

Clerk's organization switcher showing a member of one organization, a personal account and a create-organization option
The organization switcher. Each workspace shows the person's role in it. · Screenshot: Clerk

One rule on your server: check membership with Clerk's API, not with role claims in the session token. A token can be up to a minute old. A removed member should lose access on their next request, not after the token expires.

Clerk's email invitations need email sign-up

Clerk's built-in invitations send an email with a link. They work well when your app signs people up by email. They stop working in two cases.

First, they need email addresses turned on for sign-up. With email sign-up off, Clerk refuses every invitation, even one that sends no email: "Invitations are only supported on instances that accept email addresses."

Second, they need Clerk to send the email. During heavy testing before an OpenAI plugin submission, Clerk paused email and SMS sending for our production app. We moved sign-up to Google and GitHub, which need no emailed code, and that switched invitations off with it.

Which invite method fits your app
If your appUse
Signs people up by email and Clerk sends your emailClerk's email invitations
Signs people up only with Google, GitHub or other social sign-inInvite links
Needs invites that don't depend on email deliveryInvite links
Lets people join with a different address than the one invitedInvite links

An invite link adds the person through Clerk's membership API instead of an invitation. The owner shares the link however they like, and the person joins with whatever account they sign in with.

  1. Generate the code on your server. Use a cryptographic random source and at least 80 bits: we use 16 characters of Crockford Base32, which also avoids look-alike letters. Show it to the owner once.
  2. Store only its hash, with the workspace, the role, who created it and an expiry. Seven days is a sensible default.
  3. Let only the owner create, list and revoke codes. Take the workspace from the owner's verified session, never from the request body.
  4. Redeem in one database transaction. Look the code up by its hash and lock that row, so two people can't both use it. Refuse used, revoked and expired codes with the same answer.
  5. Check the seat limit under a per-workspace lock, then add the person with Clerk's membership endpoint (POST /organizations/{organization_id}/memberships) and the code's role.
  6. Record the membership in your own database before you commit, and mark the code used. If anything fails after Clerk added the person, remove them from Clerk again, so a failed redeem never leaves a member you didn't record.
  7. Rate-limit redemption per address before any lookup. We allow 10 tries a minute, which is plenty for someone pasting a code and far too few to guess one.

The handler is a few hundred lines with its tests. The tests matter more than the handler: used, expired, revoked, a full workspace, two people racing for one code, and a failure after Clerk added someone.

Five bugs you'll hit, and the fixes

1. A brand-new account can't redeem anything. Clerk puts a new account in a pending state until it has an organization, and Clerk's own docs say a pending session is "by default, treated as signed-out". So your middleware turns away the one request that would finish the pending step. Accept pending sessions on the redeem route only (in Clerk's Next.js SDK, auth with treatPendingAsSignedOut set to false), and keep refusing them everywhere else.

2. Validation runs before authentication. Most frameworks check the request body before your handler checks the session. An unsigned request then gets a validation error instead of 401, which confirms the route exists to anyone probing it. Verify the session first, then read the body.

3. Two people use one link at the same moment. Without a row lock, both pass the "unused" check and both join. Lock the code row for the whole redeem, and lock the workspace while you count seats.

4. A Clerk outage looks like an invalid user. If your code treats every non-200 from Clerk as "this person isn't a member", a short Clerk outage logs everyone out at once. Treat 404 as a verdict about the person, and treat timeouts and server errors as "unavailable, try again".

5. A member can rename the workspace. Clerk's docs say the Member role can only read members and billing by default. On our production instance it also held the "manage organization profile" permission, so the first member we invited could rename the workspace. Your app's checks can be right and still let this through, because Clerk enforces the role as configured. Open Roles & Permissions in the Clerk dashboard, or read the roles from Clerk's API, and confirm what Member actually grants before you invite anyone.

Check these before you ship

  • The creator's initial role is Admin, and the membership limit matches your plan.
  • Membership is checked with Clerk's API, not token claims.
  • The Member role grants only what you expect: read members and billing, no profile changes.
  • Codes are random, at least 80 bits, hashed at rest and shown once.
  • Codes work once, expire and can be revoked by the owner.
  • Redeem locks the code and the workspace, and undoes the Clerk add on failure.
  • Only the redeem route accepts pending sessions.
  • Unsigned requests get 401 before any validation.
  • Redemption is rate-limited per address.
  • A Clerk outage returns "try again", never "signed out".

If I did it again, Clerk all the way. It handled organizations, roles and sessions well. The invite flow was the one place where the default path didn't fit our app, and the membership API made the custom path short.

FAQ

Can Clerk send workspace invitations without email?

No. As of October 2026, Clerk's organization invitations need email addresses enabled for sign-up and are delivered by email. To invite people who sign up with Google or GitHub, create your own single-use invite links and add the person with Clerk's organization membership API.

How do I make one person the owner of a Clerk organization?

Set the creator's initial role to Admin in Clerk's Organizations settings, invite everyone else as Member, and never offer Admin in your app. The built-in Admin and Member roles are enough; custom roles are a paid Clerk feature as of October 2026.

Why can't a new user accept an invite in my Clerk app?

A new account has no organization yet, so Clerk marks its session pending, and pending sessions are treated as signed out by default. Accept pending sessions only on the route that redeems the invite, for example with treatPendingAsSignedOut set to false.

How do I stop two people from using the same invite link?

Redeem the invite inside one database transaction that locks the code's row, so only the first request can mark it used. Lock the workspace too while you count seats, so two joins can't pass the limit together.

Sources

  1. Clerk docs: Organizations overview
  2. Clerk docs: Roles and permissions
  3. Clerk docs: Manage organization invitations
  4. Clerk docs: Session tasks (pending sessions)
  5. Clerk docs: Session tokens
  6. Clerk Backend API: Organization memberships
  7. Clerk blog: Build a team-based task manager with Organizations
  8. OWASP: Brute force attack
Saad Ahmed

Founder of Cosmic. Nearly 15 years in strategy, management and sales as a digital strategist, director of sales and product manager. He closed over $25M in new revenue at VMware Pivotal Labs, launched digital products at Capital One, and helped Viget grow from 30 to 75 people. He started Cosmic and built the Revenue Design® process after advising friends whose companies struggled to close deals and keep revenue steady.