[GH-ISSUE #7218] Expo iOS fails to load when importing @better-auth/expo/client (Android works) #10761

Closed
opened 2026-04-13 07:05:58 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @PinkyCodeMaster on GitHub (Jan 9, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/7218

Originally assigned to: @bytaesu on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Create a new Expo app (npx create-expo-app)

Add better-auth and @better-auth/expo

Implement Better Auth in an Expo app following the official documentation

Import expoClient from @better-auth/expo/client

Run the app on Android → app loads and works

Run the app on iOS (simulator or physical device)

Current vs. Expected behavior

Expected behavior:
The Expo app should bundle and load correctly on iOS, the same way it does on Android.

Current behavior:

Android (emulator + physical device) works correctly

iOS (simulator + physical device) fails during Metro bundling

The app never loads on iOS

iOS error:

Unable to resolve "../node_modules/@better-auth/expo/dist/client.cjs"
from "lib/auth-client.ts"

import { expoClient } from "@better-auth/expo/client";

Metro attempts to resolve:

@better-auth/expo/dist/client.cjs

and fails during iOS bundling.

What version of Better Auth are you using?

^1.4.10

System info

PS C:\Users\scott\Documents\budget-native> npx @better-auth/cli info --json          
{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Home",
    "release": "10.0.26200",
    "cpuCount": 16,
    "cpuModel": "AMD Ryzen 7 5800 8-Core Processor              ",
    "totalMemory": "15.87 GB",
    "freeMemory": "1.72 GB"
  },
  "node": {
    "version": "v24.9.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.4"
  },
  "frameworks": [
    {
      "name": "react",
      "version": "19.1.0"
    }
  ],
  "databases": null,
  "betterAuth": {
    "version": "^1.4.10",
    "config": null
  }
}
PS C:\Users\scott\Documents\budget-native>

Which area(s) are affected? (Select all that apply)

Client

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";
import { account, session, user, verification } from "@/db/schema/auth";
import { nextCookies } from "better-auth/next-js";
import { buildResetPasswordEmail, buildVerifyEmail, buildWelcomeEmail, sendMail } from "@/lib/mail";
import { expo } from "@better-auth/expo";

const appUrl = (process.env.BETTER_AUTH_URL || "http://localhost:3000").replace(/\/$/, "");

export const auth = betterAuth({
    database: drizzleAdapter(db, {
        provider: "pg",
        schema: {
            account,
            session,
            user,
            verification
        }
    }),
    emailAndPassword: {
        autoSignIn: false,
        enabled: true,
        requireEmailVerification: true,
        onPasswordReset: async ({ user }) => {
            console.log(`Password reset for ${user.email}`);
        },
        sendResetPassword: async ({ user, url }) => {
            const email = await buildResetPasswordEmail(url);
            void sendMail({
                to: user.email,
                subject: email.subject,
                text: email.text,
                html: email.html,
            }).catch((err) => console.error("send reset email failed", err));
        },
    },
    emailVerification: {
        autoSignInAfterVerification: true,
        sendVerificationEmail: async ({ user, url }) => {
            const email = await buildVerifyEmail(url);
            void sendMail({
                to: user.email,
                subject: email.subject,
                text: email.text,
                html: email.html,
            }).catch((err) => console.error("send verification email failed", err));
        },
        afterEmailVerification: async (user) => {
            const email = await buildWelcomeEmail(`${appUrl}/onboarding`);
            void sendMail({
                to: user.email,
                subject: email.subject,
                text: email.text,
                html: email.html,
            }).catch((err) => console.error("send welcome email failed", err));
        },
    },
    plugins: [
        expo(),
        nextCookies()
    ],
    trustedOrigins: ['http//localhost:3000', "http://192.168.0.27:3000", "native://"]
});

export type Session = typeof auth.$Infer.Session


import { expoClient } from "@better-auth/expo/client";
import { createAuthClient } from "better-auth/react";
import * as SecureStore from "expo-secure-store";

export const authClient = createAuthClient({
  baseURL: process.env.EXPO_PUBLIC_API_URL!,
  plugins: [
    expoClient({
      scheme: "native",
      storagePrefix: "native",
      storage: SecureStore,
    }),
  ],
});

Additional context

This is reproducible locally

Occurs on iOS only

Android works consistently

Happens even in a minimal Expo setup

No custom native code

Issue occurs immediately on import of @better-auth/expo/client

This is a basic Expo app with Better Auth implemented exactly as shown in the official documentation.

ios - iPhone

Originally created by @PinkyCodeMaster on GitHub (Jan 9, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/7218 Originally assigned to: @bytaesu on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Create a new Expo app (npx create-expo-app) Add better-auth and @better-auth/expo Implement Better Auth in an Expo app following the official documentation Import expoClient from @better-auth/expo/client Run the app on Android → app loads and works Run the app on iOS (simulator or physical device) ### Current vs. Expected behavior Expected behavior: The Expo app should bundle and load correctly on iOS, the same way it does on Android. Current behavior: Android (emulator + physical device) works correctly iOS (simulator + physical device) fails during Metro bundling The app never loads on iOS iOS error: Unable to resolve "../node_modules/@better-auth/expo/dist/client.cjs" from "lib/auth-client.ts" import { expoClient } from "@better-auth/expo/client"; Metro attempts to resolve: @better-auth/expo/dist/client.cjs and fails during iOS bundling. ### What version of Better Auth are you using? ^1.4.10 ### System info ```bash PS C:\Users\scott\Documents\budget-native> npx @better-auth/cli info --json { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Home", "release": "10.0.26200", "cpuCount": 16, "cpuModel": "AMD Ryzen 7 5800 8-Core Processor ", "totalMemory": "15.87 GB", "freeMemory": "1.72 GB" }, "node": { "version": "v24.9.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.4" }, "frameworks": [ { "name": "react", "version": "19.1.0" } ], "databases": null, "betterAuth": { "version": "^1.4.10", "config": null } } PS C:\Users\scott\Documents\budget-native> ``` ### Which area(s) are affected? (Select all that apply) Client ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { db } from "@/db"; import { account, session, user, verification } from "@/db/schema/auth"; import { nextCookies } from "better-auth/next-js"; import { buildResetPasswordEmail, buildVerifyEmail, buildWelcomeEmail, sendMail } from "@/lib/mail"; import { expo } from "@better-auth/expo"; const appUrl = (process.env.BETTER_AUTH_URL || "http://localhost:3000").replace(/\/$/, ""); export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema: { account, session, user, verification } }), emailAndPassword: { autoSignIn: false, enabled: true, requireEmailVerification: true, onPasswordReset: async ({ user }) => { console.log(`Password reset for ${user.email}`); }, sendResetPassword: async ({ user, url }) => { const email = await buildResetPasswordEmail(url); void sendMail({ to: user.email, subject: email.subject, text: email.text, html: email.html, }).catch((err) => console.error("send reset email failed", err)); }, }, emailVerification: { autoSignInAfterVerification: true, sendVerificationEmail: async ({ user, url }) => { const email = await buildVerifyEmail(url); void sendMail({ to: user.email, subject: email.subject, text: email.text, html: email.html, }).catch((err) => console.error("send verification email failed", err)); }, afterEmailVerification: async (user) => { const email = await buildWelcomeEmail(`${appUrl}/onboarding`); void sendMail({ to: user.email, subject: email.subject, text: email.text, html: email.html, }).catch((err) => console.error("send welcome email failed", err)); }, }, plugins: [ expo(), nextCookies() ], trustedOrigins: ['http//localhost:3000', "http://192.168.0.27:3000", "native://"] }); export type Session = typeof auth.$Infer.Session import { expoClient } from "@better-auth/expo/client"; import { createAuthClient } from "better-auth/react"; import * as SecureStore from "expo-secure-store"; export const authClient = createAuthClient({ baseURL: process.env.EXPO_PUBLIC_API_URL!, plugins: [ expoClient({ scheme: "native", storagePrefix: "native", storage: SecureStore, }), ], }); ``` ### Additional context This is reproducible locally Occurs on iOS only Android works consistently Happens even in a minimal Expo setup No custom native code Issue occurs immediately on import of @better-auth/expo/client This is a basic Expo app with Better Auth implemented exactly as shown in the official documentation. ![ios - iPhone](https://github.com/user-attachments/assets/5b3ede81-1770-442c-bb90-3b31127cc9a3)
GiteaMirror added the locked label 2026-04-13 07:05:58 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jan 9, 2026):

Hi @PinkyCodeMaster! I'm Dosu and I’m helping the better-auth team.

This is a known issue with Metro bundler on iOS: it tries to resolve a CommonJS file (client.cjs) for @better-auth/expo/client, but the package only ships an ES module (client.mjs) and does not export a .cjs file. This mismatch causes the iOS bundle to fail, while Android works because its Metro resolver is less strict or behaves differently in some setups. The package’s exports field only points to ./dist/client.mjs for the ./client subpath, so Metro’s attempt to load client.cjs fails outright (source).

There’s no official fix yet, but here are the most effective workarounds reported by the community:

1. Use babel-plugin-module-resolver to alias the import:

Add this to your babel.config.js:

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [
      [
        "module-resolver",
        {
          alias: {
            "@better-auth/expo/client": "./node_modules/@better-auth/expo/dist/client.mjs",
          },
        },
      ],
    ],
  };
};

This forces Metro to resolve the correct ES module file. Some users have reported needing to clear Metro’s cache after making this change (source).

2. Try adjusting Metro config (less reliable):

In metro.config.js, after enabling package exports, add:

const config = {
  resolver: {
    unstable_enablePackageExports: true,
    unstable_conditionNames: [
      "react-native",
      "import",
      "require",
      "browser",
      "types",
    ],
  },
};
module.exports = config;

This sometimes helps Metro resolve the correct file, but can cause the app to hang or get stuck on the splash screen in some environments (source).

Limitations:
These workarounds may not work in every environment, and some users have reported new errors or build hangs after applying them. If you hit further issues, try removing node_modules and your lockfile, then reinstalling dependencies.

Summary:
The root cause is a mismatch between Metro’s expectations and the package’s exports. The best-known workaround is to alias the import using babel-plugin-module-resolver. There is no official fix in the latest releases as of now.

If you need more details or run into other errors after trying these steps, let me know!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3727739125 --> @dosubot[bot] commented on GitHub (Jan 9, 2026): <!-- Greeting --> Hi @PinkyCodeMaster! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a known issue with Metro bundler on iOS: it tries to resolve a CommonJS file (`client.cjs`) for `@better-auth/expo/client`, but the package only ships an ES module (`client.mjs`) and does not export a `.cjs` file. This mismatch causes the iOS bundle to fail, while Android works because its Metro resolver is less strict or behaves differently in some setups. The package’s exports field only points to `./dist/client.mjs` for the `./client` subpath, so Metro’s attempt to load `client.cjs` fails outright ([source](https://github.com/better-auth/better-auth/blob/c31c92f3d24f3ccfb6776d4634e0681c6dee2db5/packages/expo/package.json#L4-L53)). There’s no official fix yet, but here are the most effective workarounds reported by the community: **1. Use `babel-plugin-module-resolver` to alias the import:** Add this to your `babel.config.js`: ```js module.exports = function (api) { api.cache(true); return { presets: ["babel-preset-expo"], plugins: [ [ "module-resolver", { alias: { "@better-auth/expo/client": "./node_modules/@better-auth/expo/dist/client.mjs", }, }, ], ], }; }; ``` This forces Metro to resolve the correct ES module file. Some users have reported needing to clear Metro’s cache after making this change ([source](https://github.com/better-auth/better-auth/issues/1715#issuecomment-2708456150)). **2. Try adjusting Metro config (less reliable):** In `metro.config.js`, after enabling package exports, add: ```js const config = { resolver: { unstable_enablePackageExports: true, unstable_conditionNames: [ "react-native", "import", "require", "browser", "types", ], }, }; module.exports = config; ``` This sometimes helps Metro resolve the correct file, but can cause the app to hang or get stuck on the splash screen in some environments ([source](https://github.com/better-auth/better-auth/issues/1715#issuecomment-2708456150)). **Limitations:** These workarounds may not work in every environment, and some users have reported new errors or build hangs after applying them. If you hit further issues, try removing `node_modules` and your lockfile, then reinstalling dependencies. **Summary:** The root cause is a mismatch between Metro’s expectations and the package’s exports. The best-known workaround is to alias the import using `babel-plugin-module-resolver`. There is no official fix in the latest releases as of now. If you need more details or run into other errors after trying these steps, let me know! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/531118a5-0b90-4330-9371-ab0d4428f84f?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/531118a5-0b90-4330-9371-ab0d4428f84f?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/531118a5-0b90-4330-9371-ab0d4428f84f?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/531118a5-0b90-4330-9371-ab0d4428f84f?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/531118a5-0b90-4330-9371-ab0d4428f84f?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/531118a5-0b90-4330-9371-ab0d4428f84f?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/531118a5-0b90-4330-9371-ab0d4428f84f?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20a%20known%20issue%20with%20Metro%20bundler%20on%20iOS%3A%20it%20tries%20to%20resolve%20a%20CommonJS%20file%20%28%60client.cjs%60%29%20for%20%60%40better-auth/expo/client%60%2C%20but%20the%20package%20only%20ships%20an%20ES%20module%20%28%60client.mjs%60%29%20and%20does%20not%20export%20a%20%60.cjs%60%20file.%20This%20mismatch%20causes%20the%20iOS%20bundle%20to%20fail%2C%20while%20Android%20works%20because%20its%20Metro%20resolver%20is%20less%20strict%20or%20behaves%20differently%20in%20some%20setups.%20The%20package%E2%80%99s%20exports%20field%20only%20points%20to%20%60./dist/client.mjs%60%20for%20the%20%60./client%60%20subpath%2C%20so%20Metro%E2%80%99s%20attempt%20to%20load%20%60client.cjs%60%20fails%20outright%C2%A0%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/blob/c31c92f3d24f3ccfb6776d4634e0681c6dee2db5/packages/expo/package.json%23L4-L53%29%29.%0A%0AThere%E2%80%99s%20no%20official%20fix%20yet%2C%20but%20here%20are%20the%20most%20effective%20workarounds%20reported%20by%20the%20community%3A%0A%0A%2A%2A1.%20Use%20%60babel-plugin-module-resolver%60%20to%20alias%20the%20import%3A%2A%2A%0A%0AAdd%20this%20to%20your%20%60babel.config.js%60%3A%0A%0A%60%60%60js%0Amodule.exports%20%3D%20function%20%28api%29%20%7B%0A%20%20api.cache%28true%29%3B%0A%20%20return%20%7B%0A%20%20%20%20presets%3A%20%5B%22babel-preset-expo%22%5D%2C%0A%20%20%20%20plugins%3A%20%5B%0A%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%22module-resolver%22%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20alias%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%40better-auth/expo/client%22%3A%20%22./node_modules/%40better-auth/expo/dist/client.mjs%22%2C%0A%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%5D%2C%0A%20%20%7D%3B%0A%7D%3B%0A%60%60%60%0AThis%20forces%20Metro%20to%20resolve%20the%20correct%20ES%20module%20file.%20Some%20users%20have%20reported%20needing%20to%20clear%20Metro%E2%80%99s%20cache%20after%20making%20this%20change%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/1715%23issuecomment-2708456150%29%29.%0A%0A%2A%2A2.%20Try%20adjusting%20Metro%20config%20%28less%20reliable%29%3A%2A%2A%0A%0AIn%20%60metro.config.js%60%2C%20after%20enabling%20package%20exports%2C%20add%3A%0A%0A%60%60%60js%0Aconst%20config%20%3D%20%7B%0A%20%20resolver%3A%20%7B%0A%20%20%20%20unstable_enablePackageExports%3A%20true%2C%0A%20%20%20%20unstable_conditionNames%3A%20%5B%0A%20%20%20%20%20%20%22react-native%22%2C%0A%20%20%20%20%20%20%22import%22%2C%0A%20%20%20%20%20%20%22require%22%2C%0A%20%20%20%20%20%20%22browser%22%2C%0A%20%20%20%20%20%20%22types%22%2C%0A%20%20%20%20%5D%2C%0A%20%20%7D%2C%0A%7D%3B%0Amodule.exports%20%3D%20config%3B%0A%60%60%60%0AThis%20sometimes%20helps%20Metro%20resolve%20the%20correct%20file%2C%20but%20can%20cause%20the%20app%20to%20hang%20or%20get%20stuck%20on%20the%20splash%20screen%20in%20some%20environments%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/1715%23issuecomment-2708456150%29%29.%0A%0A%2A%2ALimitations%3A%2A%2A%20%20%0AThese%20workarounds%20may%20not%20work%20in%20every%20environment%2C%20and%20some%20users%20have%20reported%20new%20errors%20or%20build%20hangs%20after%20applying%20them.%20If%20you%20hit%20further%20issues%2C%20try%20removing%20%60node_modules%60%20and%20your%20lockfile%2C%20then%20reinstalling%20dependencies.%0A%0A%2A%2ASummary%3A%2A%2A%20%20%0AThe%20root%20cause%20is%20a%20mismatch%20between%20Metro%E2%80%99s%20expectations%20and%20the%20package%E2%80%99s%20exports.%20The%20best-known%20workaround%20is%20to%20alias%20the%20import%20using%20%60babel-plugin-module-resolver%60.%20There%20is%20no%20official%20fix%20in%20the%20latest%20releases%20as%20of%20now.%0A%0AIf%20you%20need%20more%20details%20or%20run%20into%20other%20errors%20after%20trying%20these%20steps%2C%20let%20me%20know%21)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/7218)
Author
Owner

@bytaesu commented on GitHub (Jan 9, 2026):

Hi @PinkyCodeMaster,

I’ve always tested Expo plugin on an iOS simulator.
Could you check the details below and let me know if anything is unclear?

I'll improve that 🙂

https://www.better-auth.com/docs/integrations/expo#configure-metro-bundler

<!-- gh-comment-id:3727817927 --> @bytaesu commented on GitHub (Jan 9, 2026): Hi @PinkyCodeMaster, I’ve always tested Expo plugin on an iOS simulator. Could you check the details below and let me know if anything is unclear? I'll improve that 🙂 https://www.better-auth.com/docs/integrations/expo#configure-metro-bundler
Author
Owner

@PinkyCodeMaster commented on GitHub (Jan 9, 2026):

Hi @dosu, thanks for the detailed explanation — that aligns with what I’m seeing 👍

Just to clarify though, the suggested workaround is effectively what I’ve already had in place from the beginning, and the issue still occurs.

Current state (since day one)

My project already has both:

babel.config.js

module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [
[
"module-resolver",
{
alias: {
"better-auth/react":
"./node_modules/better-auth/dist/client/react/index.cjs",
"better-auth/client/plugins":
"./node_modules/better-auth/dist/client/plugins/index.cjs",
"@better-auth/expo/client":
"./node_modules/@better-auth/expo/dist/client.cjs",
},
},
],
],
};
};

metro.config.js

const { getDefaultConfig } = require("expo/metro-config");

const config = getDefaultConfig(__dirname);
config.resolver.unstable_enablePackageExports = true;

module.exports = config;

These have been present from the start, Metro cache cleared repeatedly, dev client rebuilt, etc.

Key issue

Even with the alias in place, iOS Metro still attempts to resolve:

@better-auth/expo/dist/client.cjs

and fails, while Android continues to work.

So in my case:

The alias does exist

Package exports are enabled

The error still happens only on iOS

This makes me think the problem may be deeper than just “missing alias”, possibly:

iOS Metro ignoring or bypassing Babel aliases for ESM-only packages

Or Metro attempting a CJS fallback path before alias resolution

I haven’t yet tried aliasing directly to client.mjs, but wanted to clarify that the documented workaround (alias + exports enabled) is already in place and not resolving the issue here.

Happy to test additional variations if you think there’s a specific combination that’s known to work reliably on iOS.

Thanks again for the context — really appreciate it.

<!-- gh-comment-id:3727829306 --> @PinkyCodeMaster commented on GitHub (Jan 9, 2026): Hi @dosu, thanks for the detailed explanation — that aligns with what I’m seeing 👍 Just to clarify though, the suggested workaround is effectively what I’ve already had in place from the beginning, and the issue still occurs. Current state (since day one) My project already has both: babel.config.js module.exports = function (api) { api.cache(true); return { presets: ["babel-preset-expo"], plugins: [ [ "module-resolver", { alias: { "better-auth/react": "./node_modules/better-auth/dist/client/react/index.cjs", "better-auth/client/plugins": "./node_modules/better-auth/dist/client/plugins/index.cjs", "@better-auth/expo/client": "./node_modules/@better-auth/expo/dist/client.cjs", }, }, ], ], }; }; metro.config.js const { getDefaultConfig } = require("expo/metro-config"); const config = getDefaultConfig(__dirname); config.resolver.unstable_enablePackageExports = true; module.exports = config; These have been present from the start, Metro cache cleared repeatedly, dev client rebuilt, etc. Key issue Even with the alias in place, iOS Metro still attempts to resolve: @better-auth/expo/dist/client.cjs and fails, while Android continues to work. So in my case: The alias does exist Package exports are enabled The error still happens only on iOS This makes me think the problem may be deeper than just “missing alias”, possibly: iOS Metro ignoring or bypassing Babel aliases for ESM-only packages Or Metro attempting a CJS fallback path before alias resolution I haven’t yet tried aliasing directly to client.mjs, but wanted to clarify that the documented workaround (alias + exports enabled) is already in place and not resolving the issue here. Happy to test additional variations if you think there’s a specific combination that’s known to work reliably on iOS. Thanks again for the context — really appreciate it.
Author
Owner

@dosubot[bot] commented on GitHub (Jan 9, 2026):

You're right—your current setup matches the documented workaround, but the iOS Metro resolver is still failing because the package only ships an ES module (client.mjs) and not a CommonJS file (client.cjs). This is a known issue with Metro's handling of ESM-only packages and package exports, especially on iOS, where it sometimes insists on resolving .cjs even when only .mjs is present.

The most promising next steps, based on community reports and maintainer suggestions, are:

  1. Change your Babel alias for @better-auth/expo/client to point directly to ./node_modules/@better-auth/expo/dist/client.mjs (not client.cjs).
    Example:

    alias: {
      "@better-auth/expo/client": "./node_modules/@better-auth/expo/dist/client.mjs",
      // ...other aliases
    }
    
  2. Update your metro.config.js to add the following immediately after enabling package exports:

    config.resolver.unstable_conditionNames = [
      "react-native",
      "import",
      "require",
      "browser",
      "types",
    ];
    

    This helps Metro resolve the correct file extension and export condition on iOS, as suggested by users who hit the same wall (source).

  3. Clear Metro's cache and rebuild the dev client after making these changes.

If you still see the same error, it's likely due to Metro's internal resolution logic on iOS, which sometimes bypasses Babel aliases or expects a CJS fallback that doesn't exist. There is no official fix yet, but these steps have helped some users get past the iOS-specific failure.

Let me know if aliasing to client.mjs and updating unstable_conditionNames changes the behavior on iOS. If not, sharing a minimal reproducible repo may help the maintainers investigate further.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3727858309 --> @dosubot[bot] commented on GitHub (Jan 9, 2026): <!-- Answer --> You're right—your current setup matches the documented workaround, but the iOS Metro resolver is still failing because the package only ships an ES module (`client.mjs`) and not a CommonJS file (`client.cjs`). This is a known issue with Metro's handling of ESM-only packages and package exports, especially on iOS, where it sometimes insists on resolving `.cjs` even when only `.mjs` is present. The most promising next steps, based on community reports and maintainer suggestions, are: 1. **Change your Babel alias for `@better-auth/expo/client` to point directly to `./node_modules/@better-auth/expo/dist/client.mjs`** (not `client.cjs`). Example: ```js alias: { "@better-auth/expo/client": "./node_modules/@better-auth/expo/dist/client.mjs", // ...other aliases } ``` 2. **Update your `metro.config.js`** to add the following immediately after enabling package exports: ```js config.resolver.unstable_conditionNames = [ "react-native", "import", "require", "browser", "types", ]; ``` This helps Metro resolve the correct file extension and export condition on iOS, as suggested by users who hit the same wall ([source](https://github.com/better-auth/better-auth/issues/1715#issuecomment-2740781920)). 3. **Clear Metro's cache and rebuild the dev client** after making these changes. If you still see the same error, it's likely due to Metro's internal resolution logic on iOS, which sometimes bypasses Babel aliases or expects a CJS fallback that doesn't exist. There is no official fix yet, but these steps have helped some users get past the iOS-specific failure. Let me know if aliasing to `client.mjs` and updating `unstable_conditionNames` changes the behavior on iOS. If not, sharing a minimal reproducible repo may help the maintainers investigate further. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/bd019d04-dd50-4c4e-8c73-4ac8bd9624d6?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/bd019d04-dd50-4c4e-8c73-4ac8bd9624d6?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/bd019d04-dd50-4c4e-8c73-4ac8bd9624d6?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/bd019d04-dd50-4c4e-8c73-4ac8bd9624d6?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/bd019d04-dd50-4c4e-8c73-4ac8bd9624d6?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/bd019d04-dd50-4c4e-8c73-4ac8bd9624d6?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/bd019d04-dd50-4c4e-8c73-4ac8bd9624d6?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%27re%20right%E2%80%94your%20current%20setup%20matches%20the%20documented%20workaround%2C%20but%20the%20iOS%20Metro%20resolver%20is%20still%20failing%20because%20the%20package%20only%20ships%20an%20ES%20module%20%28%60client.mjs%60%29%20and%20not%20a%20CommonJS%20file%20%28%60client.cjs%60%29.%20This%20is%20a%20known%20issue%20with%20Metro%27s%20handling%20of%20ESM-only%20packages%20and%20package%20exports%2C%20especially%20on%20iOS%2C%20where%20it%20sometimes%20insists%20on%20resolving%20%60.cjs%60%20even%20when%20only%20%60.mjs%60%20is%20present.%0A%0AThe%20most%20promising%20next%20steps%2C%20based%20on%20community%20reports%20and%20maintainer%20suggestions%2C%20are%3A%0A%0A1.%20%2A%2AChange%20your%20Babel%20alias%20for%20%60%40better-auth/expo/client%60%20to%20point%20directly%20to%20%60./node_modules/%40better-auth/expo/dist/client.mjs%60%2A%2A%20%28not%20%60client.cjs%60%29.%20%20%0A%20%20%20Example%3A%0A%20%20%20%60%60%60js%0A%20%20%20alias%3A%20%7B%0A%20%20%20%20%20%22%40better-auth/expo/client%22%3A%20%22./node_modules/%40better-auth/expo/dist/client.mjs%22%2C%0A%20%20%20%20%20//%20...other%20aliases%0A%20%20%20%7D%0A%20%20%20%60%60%60%0A%0A2.%20%2A%2AUpdate%20your%20%60metro.config.js%60%2A%2A%20to%20add%20the%20following%20immediately%20after%20enabling%20package%20exports%3A%0A%20%20%20%60%60%60js%0A%20%20%20config.resolver.unstable_conditionNames%20%3D%20%5B%0A%20%20%20%20%20%22react-native%22%2C%0A%20%20%20%20%20%22import%22%2C%0A%20%20%20%20%20%22require%22%2C%0A%20%20%20%20%20%22browser%22%2C%0A%20%20%20%20%20%22types%22%2C%0A%20%20%20%5D%3B%0A%20%20%20%60%60%60%0A%20%20%20This%20helps%20Metro%20resolve%20the%20correct%20file%20extension%20and%20export%20condition%20on%20iOS%2C%20as%20suggested%20by%20users%20who%20hit%20the%20same%20wall%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/1715%23issuecomment-2740781920%29%29.%0A%0A3.%20%2A%2AClear%20Metro%27s%20cache%20and%20rebuild%20the%20dev%20client%2A%2A%20after%20making%20these%20changes.%0A%0AIf%20you%20still%20see%20the%20same%20error%2C%20it%27s%20likely%20due%20to%20Metro%27s%20internal%20resolution%20logic%20on%20iOS%2C%20which%20sometimes%20bypasses%20Babel%20aliases%20or%20expects%20a%20CJS%20fallback%20that%20doesn%27t%20exist.%20There%20is%20no%20official%20fix%20yet%2C%20but%20these%20steps%20have%20helped%20some%20users%20get%20past%20the%20iOS-specific%20failure.%0A%0ALet%20me%20know%20if%20aliasing%20to%20%60client.mjs%60%20and%20updating%20%60unstable_conditionNames%60%20changes%20the%20behavior%20on%20iOS.%20If%20not%2C%20sharing%20a%20minimal%20reproducible%20repo%20may%20help%20the%20maintainers%20investigate%20further.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/7218)
Author
Owner

@PinkyCodeMaster commented on GitHub (Jan 9, 2026):

Hi @bytaesu, thanks for checking in 🙂

Yes — I’ve followed the Expo integration docs exactly (including the Metro config you linked).
Unfortunately the issue still occurs.

To clarify:

Metro is configured per the docs

Package exports are enabled

Android works correctly

iOS (simulator + device) still fails during bundling

Error remains:

Unable to resolve @better-auth/expo/dist/client.cjs

So the documentation itself is clear 👍 — the problem seems to be that even with the documented setup, Metro on iOS is still attempting to resolve a .cjs file that doesn’t exist.

Happy to test any changes you recommend, but at this point this appears to be a package-level iOS Metro resolution issue rather than a config/documentation issue.

Thanks for taking a look — appreciate it.

<!-- gh-comment-id:3728005103 --> @PinkyCodeMaster commented on GitHub (Jan 9, 2026): Hi @bytaesu, thanks for checking in 🙂 Yes — I’ve followed the Expo integration docs exactly (including the Metro config you linked). Unfortunately the issue still occurs. To clarify: Metro is configured per the docs Package exports are enabled Android works correctly iOS (simulator + device) still fails during bundling Error remains: Unable to resolve @better-auth/expo/dist/client.cjs So the documentation itself is clear 👍 — the problem seems to be that even with the documented setup, Metro on iOS is still attempting to resolve a .cjs file that doesn’t exist. Happy to test any changes you recommend, but at this point this appears to be a package-level iOS Metro resolution issue rather than a config/documentation issue. Thanks for taking a look — appreciate it.
Author
Owner

@bytaesu commented on GitHub (Jan 10, 2026):

Hi @PinkyCodeMaster,

We switched our setup to ESM only, but it seems the documentation wasn’t updated then. Sorry for the confusion 🙏

<!-- gh-comment-id:3733674283 --> @bytaesu commented on GitHub (Jan 10, 2026): Hi @PinkyCodeMaster, We switched our setup to ESM only, but it seems the documentation wasn’t updated then. Sorry for the confusion 🙏
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10761