# How Logto Manages User Data: Architecture and Implementation Guide

> Discover how Logto manages user data with its layered architecture. Learn about schema definitions, Zod validation, and centralized user lifecycle management for type-safe operations. Get the implementation guide.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: architecture
- Published: 2026-07-06

---

**Logto manages user data through a layered architecture that separates schema definitions, database queries, business logic libraries, and HTTP routes, ensuring type-safe operations with Zod validation and centralized user lifecycle management.**

The `logto-io/logto` repository implements a robust user management system that organizes data handling into distinct layers. This architecture ensures strict type safety, prevents data leakage, and provides a clear separation between database operations, business logic, and API exposure.

## Layered Architecture Overview

Logto organizes user data management into four primary layers:

- **Schema Layer**: Declares the shape of user records using Zod types and guards in [`packages/schemas/src/types/user.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/user.ts)
- **Database Layer**: Low-level CRUD helpers generated at runtime for the `users` table and related entities
- **Library Layer**: Business logic façade in [`packages/core/src/libraries/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/user.ts) that orchestrates creation, validation, password handling, role assignment, MFA, and SSO provisioning
- **Route Layer**: REST endpoints in [`packages/core/src/routes-me/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes-me/user.ts) and [`packages/core/src/routes/user-assets.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/user-assets.ts) that expose library functions to clients

## Schema Definitions and Type Safety

### User Schema Structure

The foundation of Logto's user data management resides in [`packages/schemas/src/types/user.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/user.ts), which defines the **User** type using Zod validation. The schema explicitly enumerates safe fields for public API exposure through the `userInfoSelectFields` array:

```typescript
export const userInfoSelectFields = Object.freeze([
  'id',
  'username',
  'primaryEmail',
  'primaryPhone',
  'name',
  'avatar',
  'customData',
  'identities',
  'lastSignInAt',
  'createdAt',
  'updatedAt',
  'profile',
  'applicationId',
  'isSuspended',
] satisfies Array<keyof User>);

```

The `userInfoGuard` created from this list guarantees that every API response conforms to the declared shape. This prevents accidental leakage of internal columns like password hashes or encryption metadata.

## Core Business Logic Library

### The createUserLibrary Factory

All high-level user operations live in the **user library** instantiated by `createUserLibrary(tenantId, queries)` in [`packages/core/src/libraries/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/user.ts). The library receives a `Queries` object that abstracts the underlying PostgreSQL tables (users, roles, organizations, SSO identities).

Key functions include:

- **`insertUser`**: Generates a short-id, inserts the row, assigns default and custom roles, and emits a developer event for admin-tenant creations
- **`checkIdentifierCollision`**: Validates uniqueness of email, phone, username, and SSO identities before creation or updates
- **`verifyUserPassword`**: Validates supplied passwords against stored hashes, automatically upgrading to Argon2i when needed
- **`signOutUser`**: Revokes all token and session instances for the supplied user ID
- **`provisionOrganizations`**: Automatically adds users to JIT-derived organizations based on email domain, SSO connector, or explicit organization lists after sign-up

The library also encapsulates **MFA handling** via `addUserMfaVerification` and **SSO identity retrieval** through `findUserSsoIdentities`.

## HTTP Routes and API Exposure

The library layer connects to HTTP endpoints that serve the admin console and experience SPA:

- **`GET /api/me/user`**: Returns the authenticated user profile using `userInfoGuard` to filter fields. Implemented in [`packages/core/src/routes-me/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes-me/user.ts)
- **`GET /api/user-assets/:userId`**: Streams avatar or binary assets stored in the `user_assets` table. Implemented in [`packages/core/src/routes/user-assets.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/user-assets.ts)

Both routes rely on the library's `findUserById` and `updateUserById` helpers, ensuring consistent validation and business rules across all entry points.

## Data Flow Example: User Sign-Up

The following flow illustrates how user data moves through the architecture during creation:

1. **API Request**: The admin console sends `POST /api/users` with a `CreateUser` payload
2. **Validation**: The route handler calls `userLibrary.checkIdentifierCollision` to guarantee uniqueness of identifiers
3. **Password Security**: `encryptUserPassword` from [`packages/core/src/utils/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/utils/user.ts) creates an Argon2i hash, storing `passwordEncrypted` and `passwordEncryptionMethod`
4. **Persistence**: `userLibrary.insertUser` writes the new row, assigns roles, and returns the persisted `User` object
5. **Organization Provisioning**: `provisionOrganizations` executes, adding the user to auto-join organizations based on domain or connector rules
6. **Response Filtering**: The route filters results through `userInfoGuard` before sending safe fields to the client

All steps remain type-checked and validated by Zod guards defined in the schema file, maintaining a stable contract for external callers.

## Code Examples

### Creating a User via the Library

```typescript
import { createUserLibrary } from '#src/libraries/user.js';
import type { Queries } from '#src/tenants/Queries.js';

const userLib = createUserLibrary('my-tenant-id', queries);

await userLib.checkIdentifierCollision({
  primaryEmail: 'alice@example.com',
  username: 'alice',
});

const [newUser] = await userLib.insertUser({
  username: 'alice',
  primaryEmail: 'alice@example.com',
  passwordEncrypted: '',
  passwordEncryptionMethod: '',
  name: 'Alice',
});

console.log('Created user id:', newUser.id);

```

### Verifying Password During Authentication

```typescript
import { createUserLibrary } from '#src/libraries/user.js';
import { queries } from '#src/tenants/queries.js';

const userLib = createUserLibrary('my-tenant-id', queries);
const user = await queries.users.findUserById('user-id');

try {
  const verifiedUser = await userLib.verifyUserPassword(user, 'plainPassword');
  console.log('Login successful for', verifiedUser.id);
} catch (err) {
  console.error('Invalid credentials');
}

```

### Retrieving Current User Profile

```typescript
import fetch from 'node-fetch';

const response = await fetch('http://localhost:3001/api/me/user', {
  headers: { Authorization: `Bearer ${accessToken}` },
});

const profile = await response.json(); // conforms to UserInfo
console.log(profile.username, profile.primaryEmail);

```

## Summary

- Logto implements a **four-layer architecture** (schema, database, library, routes) to manage user data with strict separation of concerns
- **Zod guards** in [`packages/schemas/src/types/user.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/user.ts) enforce type safety and prevent sensitive field leakage through `userInfoSelectFields`
- The **`createUserLibrary`** factory in [`packages/core/src/libraries/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/user.ts) centralizes all business logic including collision detection, password verification, and organization provisioning
- **HTTP routes** in [`packages/core/src/routes-me/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes-me/user.ts) expose only filtered user data, while [`packages/core/src/routes/user-assets.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/user-assets.ts) handles binary content
- All user creation flows enforce **Argon2i password hashing** and automatic **JIT organization assignment** before returning sanitized responses

## Frequently Asked Questions

### How does Logto prevent sensitive user data from leaking in API responses?

Logto uses the `userInfoSelectFields` array and `userInfoGuard` Zod schema in [`packages/schemas/src/types/user.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/user.ts) to explicitly whitelist only public-safe fields. When routes prepare responses, they filter user objects through these guards, ensuring internal columns like `passwordEncrypted` and `passwordEncryptionMethod` never reach clients.

### What happens when a user signs up with an email domain that matches an organization?

The `provisionOrganizations` function in [`packages/core/src/libraries/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/user.ts) automatically evaluates the user's email domain, SSO connector, or explicit organization list immediately after `insertUser` completes. If matching JIT (Just-In-Time) provisioning rules exist, the function adds the user to the appropriate organizations before the API returns the created user object.

### Where is user password hashing implemented in Logto?

Password hashing logic resides in [`packages/core/src/utils/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/utils/user.ts) through the `encryptUserPassword` function, which uses Argon2i by default. During verification, `verifyUserPassword` in [`packages/core/src/libraries/user.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/user.ts) compares supplied passwords against stored hashes and automatically migrates older hash formats to Argon2i when detected.

### Can I extend the user schema with custom fields?

Yes, the `customData` field in the User schema accepts JSON objects for application-specific extensions. The core schema remains stable, but `customData` provides a flexible storage mechanism without requiring database migrations. Access this data through the standard library functions in `createUserLibrary` or the `/api/me/user` endpoint.