TypeScript

Faire Auth is designed to be type-safe. Both the client and server are built with TypeScript, with end-to-end type inference from your server configuration through to client method calls.

TypeScript Config

Strict Mode

Faire Auth is designed to work with TypeScript's strict mode. We recommend enabling strict mode in your TypeScript config file:

tsconfig.json
{
  "compilerOptions": {
    "strict": true
  }
}

if you can't set strict to true, you can enable strictNullChecks:

tsconfig.json
{
  "compilerOptions": {
    "strictNullChecks": true,
  }
}

If you're running into issues with TypeScript inference exceeding maximum length the compiler will serialize, then please make sure you're following the instructions above, as well as ensuring that both declaration and composite are not enabled.

$Infer — The Type Inference Namespace

Faire Auth provides a $Infer namespace on the server auth instance for deriving types. Better Auth had $Infer.Session — Faire Auth expands this with $Infer.App() and $Infer.Api().

$Infer.Session

Infers the session type including plugin-extended fields:

auth.ts
import { faireAuth, defineOptions } from "faire-auth"

const cfg = defineOptions({
    baseURL: "http://localhost:3000",
    user: {
        additionalFields: {
            role: { type: "string", input: false },
        },
    },
})

const auth = faireAuth(cfg)

// Includes user.role from additionalFields + any plugin fields
type Session = typeof auth.$Infer.Session

$Infer.App()

Returns the full typed Hono app type with all route schemas inlined. This is the primary type you pass to createAuthClient to get typed client routes:

auth.ts
const { $Infer } = faireAuth(cfg)

// The typed Hono app — pass this to createAuthClient
export const App = $Infer.App(cfg)

App encodes every route's path, method, input schema, and response schema. When you pass <typeof App> to createAuthClient, the client automatically knows every available route and its types.

$Infer.Api()

Returns the server-side API object keyed by operationId:

auth.ts
export const Api = $Infer.Api(App)

// Api.signInEmail, Api.getSession, etc. — all typed

This is useful when you want to reference the API type without importing the auth instance itself.

Client Type Inference

The client derives its types entirely from the App type. This is how plugin routes, additional fields, and DTO transforms flow through to the client:

auth-client.ts
import { createAuthClient } from "faire-auth/client"
import type { App } from "./auth"

const authClient = createAuthClient<typeof App>()({}) 

// All routes are typed — including plugin routes
await authClient.signIn.email.$post({ json: { email: "", password: "" } })

// Session type reflects additional fields and DTO transforms
type Session = typeof authClient.$Infer.Session

inferAdditionalFields from faire-auth/client/plugins is deprecated and no longer exported. Use the App type generic on createAuthClient instead — it automatically infers all additional fields, plugin routes, and DTO transforms.

InferClient

If you need the full client type (routes + actions + hooks + $store) as a standalone type, use InferClient:

import type { InferClient } from "faire-auth/api"
import type { App } from "./auth"

type MyClient = InferClient<typeof App, { fetchOptions: { throw: true } }>

Route Visibility

Not all routes are exposed to the client. Faire Auth uses three route-level flags to control visibility:

FlagEffect
SERVER_ONLY: trueRoute is excluded from the Hono app entirely. Only available via auth.api.*() on the server. Not in OpenAPI spec.
isAction: falseRoute is registered but hidden from the client API and OpenAPI spec. Used for utility routes like /ok and /error.
client: falseRoute is registered and in OpenAPI spec, but excluded from the client type. Used for callback routes (OAuth redirects).

When you create a plugin route, these flags are set on the route config:

createRoute({
    operationId: "myInternalRoute",
    method: "post",
    path: "/my-plugin/internal",
    SERVER_ONLY: true, // only callable via auth.api.myInternalRoute()
    // ...
})

Routes without these flags are fully public — registered in the Hono app, included in OpenAPI spec, and typed on the client.

Additional Fields

Faire Auth allows you to add additional fields to the user and session objects. All additional fields are properly inferred and available on the server and client side.

import { faireAuth, defineOptions } from "faire-auth"

const cfg = defineOptions({
    baseURL: "http://localhost:3000",
    user: {
       additionalFields: {
          role: {
              type: "string",
              input: false
            }
        }
    }
})

const auth = faireAuth(cfg)
export const App = auth.$Infer.App(cfg)

type Session = typeof auth.$Infer.Session
// Session.user.role is typed as string

The input property

The input property in an additional field configuration determines whether the field should be included in the user input. This property defaults to true, meaning the field will be part of the user input during operations like registration.

To prevent a field from being part of the user input, you must explicitly set input: false:

additionalFields: {
    role: {
        type: "string",
        input: false
    }
}

When input is set to false, the field will be excluded from user input, preventing users from passing a value for it.

By default, additional fields are included in the user input, which can lead to security vulnerabilities if not handled carefully. For fields that should not be set by the user, like a role, it is crucial to set input: false in the configuration.

DTO Type Threading

When you define DTO transforms, the transformed types flow through to the client automatically:

const cfg = defineOptions({
    baseURL: "http://localhost:3000",
    dto: {
        user: (user) => ({
            id: user.id,
            name: user.name,
            // email, createdAt, updatedAt are stripped
        }),
    },
})

const auth = faireAuth(cfg)
export const App = auth.$Infer.App(cfg)
auth-client.ts
import type { App } from "./auth"

const authClient = createAuthClient<typeof App>()({})

// The session user type only has id and name — not email, createdAt, etc.
const { data } = await authClient.getSession.$get({ query: {} })
// data.user.id ✓
// data.user.name ✓
// data.user.email ✗ — stripped by DTO

This is implemented at the Zod schema level — buildSchemas() appends .transform() to response schemas that have a matching DTO key, and ProcessRouteConfig threads the transformed output type through the OpenAPI schema used by the client.

On this page