expo-clerk-convex: email and password auth wired through Clerk and Convex
This repository is an Expo starter focused on email/password authentication with Clerk, Convex, and Expo Router route protection.
At a glance
- What is it?
- This Expo starter shows one specific path through a mobile stack: Clerk handles email and password sign-in with an emailed verification code, Expo Router's root layout decides which routes a signed-in user can reach, and Convex trusts the Clerk session for backend calls without mirroring users into its own table. It is small, opinionated and explicit that it is a guide rather than a framework.
- Who is it for?
- Use this starter if you are building an Expo app with Convex as the backend, want email and password authentication with in-app code verification, and would rather copy a working route guard than design your own session handling. Do not adopt it if you need social login, passkeys, multi-tenancy or a user profile table, since the flow shown is deliberately narrow and the README says outright that it is not a framework.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 113 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
An opinionated guide, and it says so
The README opens with a disclaimer that is unusual and, in this case, accurate. It says this is not supposed to be a template, boilerplate or a framework, and that it is an opinionated guide showing how to do some things in a certain way. You are not forced to do everything exactly as shown, and the advice is to decide what works for you and your team and stay consistent in your style.
That framing explains the size of the repository. The top level holds.env.example,.gitignore,.husky/,.oxfmtrc.json, LICENSE, app.json, babel.config.js, bun.lock, convex/, oxlint.config.ts, package.json, readme.md, src/ and tsconfig.json. There is no sea of generated boilerplate, no example apps folder, no configuration matrix. It is one path, worked through once.
The stack it wires together is specific. The description calls it an Expo starter focused on email/password authentication with Clerk, Convex and Expo Router route protection. The topics confirm the mix: clerk, convex, expo, expo-router, react-native and typescript.
The dependency list in the package manifest shows how current it is. Expo is on the 57 line, React is 19.2.3, React Native is 0.86.0, Clerk's Expo SDK is 3.1.5, Convex is 1.34.1, and TypeScript is 6.0.3 in development dependencies. The entry point is expo-router/entry, which means file-based routing is the app's structure rather than something bolted on.
The audience is a developer or small team starting an Expo project who wants authentication to work on the first day and is willing to follow someone else's decisions to get there.
The authentication flow and the missing users table
The Authentication Flow section is four bullets, and they describe the whole design.
Sign in uses Clerk email and password authentication. Sign up creates the account, sends an email verification code, and completes verification in-app. Route access is controlled by Clerk auth state in the root Expo Router layout. And Convex trusts the authenticated Clerk session for backend access, without mirroring Clerk users into a Convex users table.
That last bullet is the interesting decision, so it is worth unpacking. The usual pattern for a Convex backend with an external auth provider is to keep a users table on the server, write a record when someone signs up, and look that record up on every authenticated call. This starter skips that. The backend verifies the Clerk session and works from the claims directly, so there is no second source of truth to keep in sync and no sign-up hook to get wrong.
The cost of skipping it is that the Convex side has no place to hang data that only exists per user, such as a profile, a role or a subscription record, unless you add a table yourself. The README is explicit that Convex trusts the authenticated Clerk session for backend access, and the fact that it mentions what is not being done is a good sign about the rest.
The email code flow for sign-up is the other detail that shapes the user experience. Verification happens inside the app rather than through a link in an email, so the user types a code into a screen you built, and Clerk has to be configured for that flow before it works.
Three environment variables and a JWT issuer domain
Connecting Clerk, Expo and Convex takes a small set of configuration, and the .env.example file in the repository shows exactly which keys exist.
# Clerk
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=
# Deployment used by `npx convex dev`
CONVEX_DEPLOYMENT=
EXPO_PUBLIC_CONVEX_URL=
EXPO_PUBLIC_CONVEX_SITE_URL=The naming convention does the explaining. The two EXPO_PUBLIC_ prefixed variables are read by the app bundle, so the Clerk publishable key and the Convex deployment URL are compiled into the client. CONVEX_DEPLOYMENT is the identifier of the deployment created by the dev command, and EXPO_PUBLIC_CONVEX_SITE_URL covers the site URL for that deployment.
The third variable does not appear in .env.example because it does not live in the app. The README says to add CLERK_JWT_ISSUER_DOMAIN to your Convex environment variables, with the Clerk Frontend API URL. That single value is the trust anchor: it tells Convex which issuer to accept tokens from, so that the backend can verify a Clerk session rather than believe whatever the client sends. The setup instructions are explicit that you copy the Clerk Frontend API URL that Convex will use as CLERK_JWT_ISSUER_DOMAIN.
If you get that value wrong, the failure mode is subtle rather than loud. The app will sign in perfectly, and the backend calls will fail or appear unauthenticated. It is the first thing to check when the two halves do not talk to each other.
Getting it running from a clean checkout
The setup sequence in the README is short and each step has a clear output.
You start in the Clerk dashboard, create an application, enable email and password authentication, configure email verification to use an email code flow for sign-up, and enable the Convex integration for the Clerk application.
Then the repository comes down and the dependencies go in:
git clone https://github.com/FlemingVincent/expo-clerk-convex.gitcd expo-clerk-convexbun installBun is the package manager here, and the bun.lock file at the root is the lockfile that goes with it. That is a choice worth noting if your team standardises on npm, since you would be changing the install step rather than adding a flag to it.
Next comes the backend. Running npx convex dev creates the convex/ backend directory, prompts you to sign in to Convex, and prints the deployment URL that the Expo app needs:
npx convex devYou put that URL into .env.local along with the Clerk publishable key, and set CLERK_JWT_ISSUER_DOMAIN on the Convex side. Then the development server starts:
npx expo start --clear --reset-cacheThe clear and reset-cache flags are there because Expo's bundler cache is a common source of confusion when a native module or an environment variable changes. The package manifest makes the same choice in its start script, expo start --clear --reset-cache, so running the script gives you the same behaviour as the documented command.
Lint, format and hooks: oxlint, oxfmt and husky
The tooling choices in this starter are recent and worth a look even if you never use the authentication code.
Linting runs on oxlint rather than the long-standing eslint, with oxlint.config.ts at the root and an oxlint-config-universe package plus oxlint-tsgolint among the development dependencies. Formatting is oxfmt, configured through .oxfmtrc.json, with scripts for fmt and fmt:check.
Git hooks are installed by husky, wired through the prepare script, which runs automatically on install. The lint-staged configuration then applies oxlint --fix and oxfmt to JavaScript and TypeScript files and oxfmt to JSON and JSONC files, before they reach a commit.
The effect is that formatting and linting are enforced by the pre-commit hook rather than by a reviewer, and the two tools involved are not the usual eslint and prettier pair. For a team that wants a lighter toolchain, or for a repository that wants to avoid the eslint plugin ecosystem, that is a reasonable setup. For a team with an existing eslint configuration, adopting this starter means adopting their tooling too.
There are also native build scripts, expo run:android and expo run:ios, alongside the usual start script, and the dependencies include expo-dev-client and expo-build-properties, which are what a native build rather than Expo Go needs. The dependency list also includes expo-secure-store, which is the natural place to keep the session token on the device, and react-native-reanimated with react-native-worklets for animation work in the app.
Where this starter stops being enough
The limits of this project are the limits of its scope, and the README is upfront about them.
It covers one authentication method. Email and password with an emailed verification code is the whole flow, and the setup steps say to enable email/password authentication and configure email verification to use an email code flow. Social login, passkeys, magic links and multi-factor options are not part of what is demonstrated, even though the underlying provider supports much more than this repository shows.
It is one project, not a product. There is no versioning of the authentication scheme, no migration story, and no sign-out or account-recovery handling described in the README. If you copy it into three apps, you will copy the decisions three times.
It ties you to three services. Clerk for identity, Convex for the backend, and Expo for the client. Each of them is a reasonable choice on its own, but the combination means three vendor relationships, three sets of environment variables, and a Clerk dashboard configuration that has to be kept in step with the Convex deployment. The CLERK_JWT_ISSUER_DOMAIN value is the seam where those two worlds meet, and it is a seam you have to maintain.
On maintenance, the repository is not archived and the last push was on 2026-07-02, with no GitHub releases. The licence is MIT, with the README stating that you are granted the freedom to use, modify and distribute the code for personal or commercial purposes.
The alternative to all of this is to own the authentication layer yourself, store users in your own table and validate tokens in your own backend, which costs more engineering and gives you exactly the behaviour you want. That trade is worth making deliberately, and this starter is a reasonable way to see the delegated version first.
Editorial conclusion
Use this starter if you are building an Expo app with Convex as the backend, want email and password authentication with in-app code verification, and would rather copy a working route guard than design your own session handling. Do not adopt it if you need social login, passkeys, multi-tenancy or a user profile table, since the flow shown is deliberately narrow and the README says outright that it is not a framework. Before you build on it, create the Clerk application and the Convex deployment first, then set the three environment variables exactly as listed in .env.example, since the JWT issuer domain is what lets Convex verify the session rather than trust the client.
Frequently asked questions
What does the expo-clerk-convex starter demonstrate?
It demonstrates email and password authentication with Clerk, an email verification code completed in-app, route access controlled by Clerk auth state in the root Expo Router layout, and a Convex backend that trusts the authenticated Clerk session without mirroring users into a Convex users table.
Which environment variables does the expo-clerk-convex project need?
The app needs EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY and EXPO_PUBLIC_CONVEX_URL in .env.local, and the backend needs CLERK_JWT_ISSUER_DOMAIN set to the Clerk Frontend API URL in the Convex environment variables. The .env.example file also lists CONVEX_DEPLOYMENT and EXPO_PUBLIC_CONVEX_SITE_URL.
How do I run the expo-clerk-convex starter locally?
Run git clone, then cd into the directory, bun install, and npx convex dev to create the backend and print the deployment URL. Add the environment variables, then start the app with npx expo start --clear --reset-cache.
Does expo-clerk-convex store users in a Convex table?
No. The README states that Convex trusts the authenticated Clerk session for backend access, without mirroring Clerk users into a Convex users table, so any per-user data such as profiles or roles needs a table of your own.
What linting and formatting tools does the starter use?
Linting runs on oxlint with oxlint.config.ts, formatting runs on oxfmt configured by .oxfmtrc.json, and husky installs git hooks through the prepare script so that lint-staged applies oxlint --fix and oxfmt before a commit. The last push was on 2026-07-02 and the licence is MIT.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/flemingvincent-expo-clerk-convex)Was this useful?