How the Logto Account Center Works: Architecture, API, and Front-End Integration
The Logto Account Center is a self-service hub that combines database-persisted configuration, cached middleware injection, management API endpoints, and Lit-based web components to let users manage their profiles, passwords, and linked identities.
The account center in the logto-io/logto repository provides end users with a centralized interface to manage account settings, including profile data, passwords, and social identities. Its implementation spans PostgreSQL schemas, cached query layers, Koa middleware, and framework-agnostic web components. This article breaks down how these layers interact to deliver a seamless self-service experience.
Database Schema and Configuration Storage
The account center configuration is stored in the account_centers table, defined in packages/schemas/tables/account_centers.sql. The corresponding seed file at packages/schemas/src/seeds/account-center.ts populates the default row.
The table stores critical fields including enabled, fields, customCss, profileFields, and an optional deleteAccountUrl. Only one record exists in practice—the row with id = 'default'—which serves as the global configuration for the entire tenant.
Query Layer and Caching Strategy
All database interactions flow through the AccountCenterQueries class in packages/core/src/queries/account-center.ts. This class extends SchemaQueries and implements aggressive caching via WellKnownCache (packages/core/src/caches/well-known.ts).
The query layer exposes two primary methods:
// packages/core/src/queries/account-center.ts
export class AccountCenterQueries extends SchemaQueries<...> {
public readonly findDefaultAccountCenter = this.wellKnownCache.memoize(
async () => this.findById('default'), // ← always fetch the row with id = 'default'
['account-center']
);
public readonly updateDefaultAccountCenter = this.wellKnownCache.mutate(
async (accountCenter) => this.updateById('default', accountCenter, 'replace'),
['account-center']
);
}
findDefaultAccountCenter retrieves the configuration with memoization, while updateDefaultAccountCenter mutates the cache atomically when settings change. This ensures that high-traffic routes read from memory rather than hitting PostgreSQL repeatedly.
Middleware Injection
Before any protected route accesses account center data, the koaAccountCenter middleware (packages/core/src/routes/account/middlewares/koa-account-center.ts) validates and injects the configuration into the Koa context.
// packages/core/src/routes/account/middlewares/koa-account-center.ts
export default function koaAccountCenter<StateT, ContextT, ResponseT>({ accountCenters }: Queries) {
return async (ctx, next) => {
const accountCenter = await findDefaultAccountCenter(); // ← query from the DB
assertThat(accountCenter.enabled, 'account_center.not_enabled');
ctx.accountCenter = accountCenter; // ← made available downstream
return next();
};
}
This middleware guarantees that downstream handlers can access ctx.accountCenter without additional database queries, and it enforces that the feature is enabled before processing requests.
Management API and Well-Known Endpoints
The management API exposes the account center under /account-center in packages/core/src/routes/account-center/index.ts. The router handles both reads and writes:
// packages/core/src/routes/account-center/index.ts
router.get('/account-center', koaGuard({ response: AccountCenters.guard }), async (ctx, next) => {
ctx.body = await findDefaultAccountCenter(); // read
return next();
});
router.patch('/account-center', koaGuard({ body: z.object({ … }), response: AccountCenters.guard }), async (ctx, next) => {
const { enabled, fields, webauthnRelatedOrigins, deleteAccountUrl, customCss, profileFields } = ctx.guard.body;
// normalise fields, deduplicate origins, etc.
const updated = await updateDefaultAccountCenter({ … });
ctx.body = updated; // write
return next();
});
For unauthenticated access, the configuration is also exposed via the well-known endpoint GET /.well-known/account-center in packages/core/src/routes/well-known/index.ts. This allows front-end applications to fetch UI settings before the user authenticates.
Front-End Integration
Fetching Configuration
Front-end applications retrieve settings through the helper in packages/account/src/apis/account-center.ts:
// packages/account/src/apis/account-center.ts
export const getAccountCenterSettings = async (): Promise<AccountCenter> =>
ky.get('/api/.well-known/account-center').json<AccountCenter>();
This function uses ky to fetch the well-known endpoint, making it available to the Lit components without requiring management API tokens.
The Account Center Component
The core UI element is the LogtoAccountCenter Lit component in packages/elements/src/account/elements/logto-account-center.ts. It consumes the logtoAccountContext from packages/elements/src/account/providers/logto-account-provider.ts to render profile sections dynamically.
// packages/elements/src/account/elements/logto-account-center.ts
@customElement('logto-account-center')
export class LogtoAccountCenter extends LitElement {
@consume({ context: logtoAccountContext, subscribe: true })
private readonly accountContext?: LogtoAccountContextType;
render() {
if (!this.accountContext) return html`<span>Unable to retrieve account context.</span>`;
const { username, primaryEmail, primaryPhone, hasPassword, identities } = this.accountContext.userProfile;
return html`
${username !== undefined && html`<logto-username></logto-username>`}
${primaryEmail !== undefined && html`<logto-user-email></logto-user-email>`}
${primaryPhone !== undefined && html`<logto-user-phone></logto-user-phone>`}
${hasPassword !== undefined && html`<logto-user-password></logto-user-password>`}
${identities !== undefined && Object.entries(identities).map(
([target]) => html`<logto-social-identity target=${target}></logto-social-identity>`
)}
`;
}
}
The component conditionally renders sub-components based on the available user profile data, ensuring only relevant sections (username, email, phone, password, social identities) appear in the UI.
Routing and Session Management
Since the account center operates under a dedicated base path (e.g., /account), the utility module packages/account/src/utils/account-center-route.ts handles redirect logic and state preservation.
Key functions include:
- accountCenterBasePath – Defines the base URL segment (e.g.,
/account). - handleAccountCenterRoute() – Extracts
redirect,show_success, andui_localesquery parameters, storing them insessionStorage(packages/account/src/utils/session-storage.ts) for retrieval after authentication redirects.
This ensures users return to their intended account page after signing in, with their preferred locale intact.
End-to-End Flow
When a user navigates to the account center:
- The Lit component mounts and requests the configuration via
getAccountCenterSettings(). - The well-known endpoint returns the cached configuration from the database.
- Middleware (
koaAccountCenter) validates that the center is enabled for any authenticated API calls. - The component renders profile sections based on the configuration and current user data.
- When settings change, the front-end calls
PATCH /account-center, triggeringupdateDefaultAccountCenterto persist changes and invalidate the cache.
Code Examples
Enabling a Custom Field
To enable the account center and add a custom field from your application:
import ky from 'ky';
// Enable the account‑center and add the “profile picture” field
await ky.patch('/api/account-center', {
json: {
enabled: true,
fields: {
// field name → control definition (see @logto/schemas for shape)
profilePicture: { displayName: 'Profile Picture', isOptional: true }
}
}
}).json();
Handling Post-Update Redirects
After a successful profile update, restore the user's original destination:
import { getPendingReturn, clearPendingReturn } from './account-center-route.js';
export const onProfileSaved = async () => {
const returnUrl = getPendingReturn();
clearPendingReturn(); // one‑time use
if (returnUrl) {
window.location.assign(returnUrl);
}
};
Integrating the Component
Use the account center component in a React or Vue application:
// Inside a Vite‑based Experience page
import '@logto/elements/account'; // registers <logto-account-center>
export default function AccountPage() {
return (
<section>
<h1>My Account</h1>
<logto-account-center></logto-account-center>
</section>
);
}
Summary
- Database Layer: Configuration resides in the
account_centerstable with a single default row, seeded viapackages/schemas/src/seeds/account-center.ts. - Caching:
AccountCenterQueriesleveragesWellKnownCacheto memoize reads and atomically mutate writes, minimizing database load. - Middleware:
koaAccountCenterinjects the configuration into the Koa context and enforces the enabled state. - API Surface: Management routes at
/account-centersupport CRUD operations, while/.well-known/account-centerprovides public read access. - Front-End: A Lit-based component in
packages/elements/src/account/elements/logto-account-center.tsrenders the UI, consuming context fromlogtoAccountProviderand using utilities inpackages/account/src/utils/account-center-route.tsfor redirect handling.
Frequently Asked Questions
How is the account center configuration cached?
The AccountCenterQueries class in packages/core/src/queries/account-center.ts uses WellKnownCache to memoize the findDefaultAccountCenter method. When configuration updates via updateDefaultAccountCenter, the cache mutates atomically, ensuring subsequent reads return fresh data without querying PostgreSQL.
What happens if the account center is disabled?
The koaAccountCenter middleware in packages/core/src/routes/account/middlewares/koa-account-center.ts asserts that accountCenter.enabled is true before attaching the configuration to the context. If disabled, it throws an account_center.not_enabled error, preventing downstream handlers from processing account-related requests.
How do front-end applications access account center settings without authentication?
Front-end code calls the well-known endpoint GET /.well-known/account-center defined in packages/core/src/routes/well-known/index.ts. This endpoint returns the account center configuration without requiring a management API token, allowing the UI to render appropriate fields before the user authenticates.
Can I customize which fields appear in the account center?
Yes. Administrators can PATCH /api/account-center with a fields object defining which profile sections to display. The LogtoAccountCenter component in packages/elements/src/account/elements/logto-account-center.ts dynamically renders only the components corresponding to the enabled fields and available user data, such as logto-user-email or logto-social-identity.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →