[GH-ISSUE #9189] Plugin type inference fails in monorepo with composite tsconfig — fix: add @better-auth/core as direct devDependency #28628

Open
opened 2026-04-17 20:03:26 -05:00 by GiteaMirror · 0 comments
Owner

Originally created by @khanhduyvt0101 on GitHub (Apr 14, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/9189

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

In a Bun monorepo with TypeScript project references (composite: true), adminClient with custom ac/roles and inferAdditionalFields both fail type inference — authClient.admin.* methods are missing, and session.user.role / session.session.impersonatedBy don't exist on the inferred types.

Monorepo structure

packages/
├── api/        # Backend (Hono)
├── app/        # Frontend SPA (React Router v7)
└── util/       # Shared utilities

Root tsconfig.json

{
  "references": [
    { "path": "packages/util/tsconfig.json" },
    { "path": "packages/api/tsconfig.json" },
    { "path": "packages/app/tsconfig.json" }
  ],
  "extends": "@tsconfig/bun/tsconfig.json",
  "compilerOptions": {
    "outDir": ".tsc",
    "composite": true,
    "emitDeclarationOnly": true,
    "noEmit": false,
    "allowImportingTsExtensions": false,
    "types": ["bun", "node"]
  },
  "include": ["scripts"]
}

App packages/app/tsconfig.json

{
  "references": [{ "path": "../util/tsconfig.json" }],
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": ".tsc",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "paths": { "~/*": ["./*"] }
  },
  "include": ["hooks", "components", "lib", "app"]
}

Auth client (packages/app/lib/auth-client.ts)

import { adminClient, inferAdditionalFields } from "better-auth/client/plugins";
import { createAccessControl } from "better-auth/plugins/access";
import { defaultStatements } from "better-auth/plugins/admin/access";
import { createAuthClient } from "better-auth/react";

const statement = {
  ...defaultStatements,
  blog: ["create", "read", "update", "delete", "publish"],
} as const;

const ac = createAccessControl(statement);

const adminRole = ac.newRole({
  user: ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"],
  session: ["list", "revoke", "delete"],
  blog: ["create", "read", "update", "delete", "publish"],
});

const writerRole = ac.newRole({
  blog: ["create", "read", "update", "delete", "publish"],
});

const userRole = ac.newRole({
  blog: [],
  user: [],
  session: [],
});

const _authClient = createAuthClient({
  baseURL: "http://localhost:3000",
  basePath: "/api/auth",
  plugins: [
    adminClient({
      ac,
      roles: {
        admin: adminRole,
        writer: writerRole,
        user: userRole,
      },
    }),
    inferAdditionalFields({
      user: {
        role: { type: "string", required: false },
      },
      session: {
        impersonatedBy: { type: "string", required: false },
      },
    }),
  ],
});

export const authClient: typeof _authClient = _authClient;

packages/app/package.json (relevant deps)

{
  "dependencies": {
    "better-auth": "^1.6.3"
  }
}

Current vs. Expected behavior

Expected: Full plugin type inference — authClient.admin.* methods exist, session.user.role and session.session.impersonatedBy are typed.

Actual: Three categories of type errors:

1. TS2883 — Non-portable type reference

error TS2883: The inferred type of '_authClient' cannot be named without a reference
to 'RawError' from '.bun/@better-auth+core@1.6.3+.../node_modules/@better-auth/core/utils/error-codes'.
This is likely not portable. A type annotation is necessary.

2. TS2322 — adminClient not assignable to BetterAuthClientPlugin

error TS2322: Type '{ id: "admin-client"; ... $InferServerPlugin: ... }' is not
assignable to type 'BetterAuthClientPlugin'.

3. TS2339 — Plugin-provided fields missing

error TS2339: Property 'admin' does not exist on type '{ refreshToken: ... }'.
error TS2339: Property 'role' does not exist on type 'StripEmptyObjects<{ id: string; ... }>'.
error TS2339: Property 'impersonatedBy' does not exist on type 'StripEmptyObjects<{ id: string; ... }>'.

Root Cause

In a Bun monorepo, @better-auth/core (a dependency of better-auth) is resolved through multiple different cache paths:

.bun/@better-auth+core@1.6.3+1d86c9dfb0dda137/...
.bun/@better-auth+core@1.6.3+d0d9bec75502bd58/...

TypeScript treats types from different module resolution paths as structurally distinct, even when they're identical. This causes:

  1. TS2883: TypeScript can't find a portable path for RawError from @better-auth/core
  2. TS2322: The BetterAuthClientPlugin interface resolved from one path doesn't match the adminClient() return type resolved through another path
  3. TS2339: Since the plugin array type check fails, InferActions and InferAdditionalFromClient can't extract the plugin types

Fix / Workaround

Add @better-auth/core as a direct devDependency:

{
  "devDependencies": {
    "@better-auth/core": "^1.6.3"
  }
}

This gives TypeScript a single, portable resolution path for all @better-auth/core types. All three error categories are resolved immediately — full plugin inference works, no type assertions or @ts-expect-error needed.

Suggested improvement

This workaround should ideally not be necessary. Possible fixes:

  1. Document this requirement for monorepo/composite tsconfig users
  2. Re-export the types (RawError, BetterAuthClientPlugin, etc.) directly from the better-auth package so they don't need to be resolved through @better-auth/core
  3. Consider listing @better-auth/core as a peerDependency of better-auth so package managers hoist it to a resolvable location

What version of Better Auth are you using?

1.6.3

System info

bun: 1.3.11
node: v24.10.0
typescript: 6.0.2
platform: darwin arm64 (macOS)

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

Types, Client

  • #8855 — adminClient with custom ac/roles produces type incompatible with BetterAuthPlugin
  • #8623 — TS2742: inferred type cannot be named without reference to path-to-object.d.mts
  • #6642 — TypeScript Type Inference Issue with adminClient Plugin
  • #5666 — Systemic type issues across better-* packages ("cannot be named without a reference")
Originally created by @khanhduyvt0101 on GitHub (Apr 14, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/9189 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce In a **Bun monorepo** with TypeScript project references (`composite: true`), `adminClient` with custom `ac`/`roles` and `inferAdditionalFields` both fail type inference — `authClient.admin.*` methods are missing, and `session.user.role` / `session.session.impersonatedBy` don't exist on the inferred types. #### Monorepo structure ``` packages/ ├── api/ # Backend (Hono) ├── app/ # Frontend SPA (React Router v7) └── util/ # Shared utilities ``` #### Root `tsconfig.json` ```jsonc { "references": [ { "path": "packages/util/tsconfig.json" }, { "path": "packages/api/tsconfig.json" }, { "path": "packages/app/tsconfig.json" } ], "extends": "@tsconfig/bun/tsconfig.json", "compilerOptions": { "outDir": ".tsc", "composite": true, "emitDeclarationOnly": true, "noEmit": false, "allowImportingTsExtensions": false, "types": ["bun", "node"] }, "include": ["scripts"] } ``` #### App `packages/app/tsconfig.json` ```jsonc { "references": [{ "path": "../util/tsconfig.json" }], "extends": "../../tsconfig.json", "compilerOptions": { "outDir": ".tsc", "lib": ["DOM", "DOM.Iterable", "ESNext"], "paths": { "~/*": ["./*"] } }, "include": ["hooks", "components", "lib", "app"] } ``` #### Auth client (`packages/app/lib/auth-client.ts`) ```typescript import { adminClient, inferAdditionalFields } from "better-auth/client/plugins"; import { createAccessControl } from "better-auth/plugins/access"; import { defaultStatements } from "better-auth/plugins/admin/access"; import { createAuthClient } from "better-auth/react"; const statement = { ...defaultStatements, blog: ["create", "read", "update", "delete", "publish"], } as const; const ac = createAccessControl(statement); const adminRole = ac.newRole({ user: ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"], session: ["list", "revoke", "delete"], blog: ["create", "read", "update", "delete", "publish"], }); const writerRole = ac.newRole({ blog: ["create", "read", "update", "delete", "publish"], }); const userRole = ac.newRole({ blog: [], user: [], session: [], }); const _authClient = createAuthClient({ baseURL: "http://localhost:3000", basePath: "/api/auth", plugins: [ adminClient({ ac, roles: { admin: adminRole, writer: writerRole, user: userRole, }, }), inferAdditionalFields({ user: { role: { type: "string", required: false }, }, session: { impersonatedBy: { type: "string", required: false }, }, }), ], }); export const authClient: typeof _authClient = _authClient; ``` #### `packages/app/package.json` (relevant deps) ```json { "dependencies": { "better-auth": "^1.6.3" } } ``` ### Current vs. Expected behavior **Expected:** Full plugin type inference — `authClient.admin.*` methods exist, `session.user.role` and `session.session.impersonatedBy` are typed. **Actual:** Three categories of type errors: #### 1. TS2883 — Non-portable type reference ``` error TS2883: The inferred type of '_authClient' cannot be named without a reference to 'RawError' from '.bun/@better-auth+core@1.6.3+.../node_modules/@better-auth/core/utils/error-codes'. This is likely not portable. A type annotation is necessary. ``` #### 2. TS2322 — adminClient not assignable to BetterAuthClientPlugin ``` error TS2322: Type '{ id: "admin-client"; ... $InferServerPlugin: ... }' is not assignable to type 'BetterAuthClientPlugin'. ``` #### 3. TS2339 — Plugin-provided fields missing ``` error TS2339: Property 'admin' does not exist on type '{ refreshToken: ... }'. error TS2339: Property 'role' does not exist on type 'StripEmptyObjects<{ id: string; ... }>'. error TS2339: Property 'impersonatedBy' does not exist on type 'StripEmptyObjects<{ id: string; ... }>'. ``` ### Root Cause In a Bun monorepo, `@better-auth/core` (a dependency of `better-auth`) is resolved through **multiple different cache paths**: ``` .bun/@better-auth+core@1.6.3+1d86c9dfb0dda137/... .bun/@better-auth+core@1.6.3+d0d9bec75502bd58/... ``` TypeScript treats types from different module resolution paths as **structurally distinct**, even when they're identical. This causes: 1. **TS2883**: TypeScript can't find a portable path for `RawError` from `@better-auth/core` 2. **TS2322**: The `BetterAuthClientPlugin` interface resolved from one path doesn't match the `adminClient()` return type resolved through another path 3. **TS2339**: Since the plugin array type check fails, `InferActions` and `InferAdditionalFromClient` can't extract the plugin types ### Fix / Workaround **Add `@better-auth/core` as a direct `devDependency`:** ```json { "devDependencies": { "@better-auth/core": "^1.6.3" } } ``` This gives TypeScript a **single, portable resolution path** for all `@better-auth/core` types. All three error categories are resolved immediately — full plugin inference works, no type assertions or `@ts-expect-error` needed. ### Suggested improvement This workaround should ideally not be necessary. Possible fixes: 1. **Document this requirement** for monorepo/composite tsconfig users 2. **Re-export the types** (`RawError`, `BetterAuthClientPlugin`, etc.) directly from the `better-auth` package so they don't need to be resolved through `@better-auth/core` 3. **Consider listing `@better-auth/core` as a `peerDependency`** of `better-auth` so package managers hoist it to a resolvable location ### What version of Better Auth are you using? 1.6.3 ### System info ``` bun: 1.3.11 node: v24.10.0 typescript: 6.0.2 platform: darwin arm64 (macOS) ``` ### Which area(s) are affected? (Select all that apply) Types, Client ### Related issues - #8855 — adminClient with custom ac/roles produces type incompatible with BetterAuthPlugin - #8623 — TS2742: inferred type cannot be named without reference to path-to-object.d.mts - #6642 — TypeScript Type Inference Issue with adminClient Plugin - #5666 — Systemic type issues across better-* packages ("cannot be named without a reference")
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28628