---
title: "Dashboard Architecture"
description: "The Mem0 self-hosted dashboard is built as a Next.js 15 single-page application that provides a comprehensive web interface for managing memories, system configuration, API keys, and administrative..."
last_updated: "2026-10-11T17:37:10.257685+00:00"
canonical_url: "https://www.doc0.dev/docs/faa36707-7c28-4f69-a18f-700ff61c704e/technical/server-dashboard/dashboard-architecture"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [server/dashboard/src/app/setup/page.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx)
- [server/dashboard/src/app/root/dashboard-client-layout.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard-client-layout.tsx)
- [server/dashboard/src/app/root/dashboard/memories/page.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/memories/page.tsx)
- [server/dashboard/src/app/root/dashboard/configuration/page.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/configuration/page.tsx)
- [server/dashboard/src/app/root/dashboard/api-keys/page.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/api-keys/page.tsx)
- [server/dashboard/src/lib/auth.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/lib/auth.tsx)
- [server/dashboard/src/app/root/dashboard/requests/page.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/requests/page.tsx)
- [server/dashboard/src/app/setup/layout.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/layout.tsx)
- [server/dashboard/src/app/root/dashboard/settings/page.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/settings/page.tsx)
- [server/dashboard/src/app/root/dashboard/entities/page.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/entities/page.tsx)
- [server/dashboard/src/store/store.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/store/store.ts)
- [server/dashboard/src/app/root/dashboard/components/main-nav.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/components/main-nav.tsx)
- [server/dashboard/src/app/root/dashboard/components/nav-wrapper.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/components/nav-wrapper.tsx)
- [server/dashboard/src/middleware.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts)
- [server/dashboard/src/app/auth/layout.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(auth)/layout.tsx)
- [server/dashboard/src/app/root/dashboard/layout.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/layout.tsx)
- [server/dashboard/src/hooks/use-auth.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-auth.ts)
- [server/dashboard/src/app/auth/login/login-form.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(auth)/login/login-form.tsx)
- [server/dashboard/src/app/root/clientLayout.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/clientLayout.tsx)
- [server/dashboard/src/store/rootReducer.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/store/rootReducer.ts)
- [server/dashboard/src/components/ui/form.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/components/ui/form.tsx)
- [server/dashboard/src/app/root/layout.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/layout.tsx)
- [server/dashboard/package.json](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/package.json)
- [server/dashboard/src/app/root/dashboard/analytics/page.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/analytics/page.tsx)
- [server/dashboard/src/utils/api.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts)
- [server/dashboard/src/hooks/use-api-query.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-api-query.ts)
- [server/dashboard/src/app/auth/login/page.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(auth)/login/page.tsx)
- [server/dashboard/src/app/api/auth/refresh/route.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/api/auth/refresh/route.ts)
- [server/dashboard/src/components/ui/accordion.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/components/ui/accordion.tsx)
- [server/dashboard/src/components/theme-provider.tsx](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/components/theme-provider.tsx)
</details>

## Overview

The Mem0 self-hosted dashboard is built as a Next.js 15 single-page application that provides a comprehensive web interface for managing memories, system configuration, API keys, and administrative operations. Powered by React 19, Redux Toolkit, and Tailwind CSS, the dashboard architecture implements a modular client layout alongside a secure Next.js middleware routing layer. It orchestrates state management, automated Axios token interception, and first-run initialization flows to seamlessly connect administrators with the underlying FastAPI backend.

Sources: [server/dashboard/package.json:1-84](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/package.json#L1-L84), [server/dashboard/src/middleware.ts:1-63](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L1-L63)

## Application Layout and Provider Hierarchy

### Root Layout Structure and Font Integration

The dashboard architecture organizes layouts across distinct Next.js route groups including root, authentication, and setup. The root client layout defined in `DashboardClientLayout` injects typography variables and provider trees into the DOM. It loads five font configurations (`Inter`, `InterDisplay`, `Roboto`, `Fustat`, and `DMMono`) from local font utilities and applies their CSS variable declarations alongside base font styles directly onto the document body.

```tsx
export function DashboardClientLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body
        className={cn(
          Inter.className,
          InterDisplay.variable,
          Roboto.variable,
          Fustat.variable,
          DMMono.variable,
        )}
        suppressHydrationWarning
      >
```
Sources: [server/dashboard/src/app/root/dashboard-client-layout.tsx:22-38](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard-client-layout.tsx#L22-L38)

Similarly, the setup and authentication route layouts establish parallel HTML document roots that load the identical font configuration sets and enforce suppress hydration warnings to prevent server-client attribute mismatches.

```tsx
return (
  <html
    lang="en"
    className={cn(
      Fustat.variable,
      InterDisplay.variable,
      Inter.variable,
      Roboto.variable,
      DMMono.variable,
    )}
    suppressHydrationWarning
  >
    <body className="font-fustat" suppressHydrationWarning>
```
Sources: [server/dashboard/src/app/setup/layout.tsx:17-29](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/layout.tsx#L17-L29), [server/dashboard/src/app/auth/layout.tsx:18-30](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(auth)/layout.tsx#L18-L30)

### Theme Injection and Provider Composition

Provider hierarchy wraps application route trees in a specific nested order to ensure state stores, authentication contexts, theme properties, and tooltips are available to all child components. In the root client layout, the Redux store provider sits at the outermost boundary, followed in sequence by the authentication provider, next-themes provider, and the custom client layout.

```tsx
<Provider store={store}>
  <AuthProvider>
    <ThemeProvider
      attribute="class"
      defaultTheme="light"
      enableSystem
      disableTransitionOnChange
    >
      <ClientLayout>{children}</ClientLayout>
      <Toaster />
    </ThemeProvider>
  </AuthProvider>
</Provider>
```
Sources: [server/dashboard/src/app/root/dashboard-client-layout.tsx:39-51](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard-client-layout.tsx#L39-L51)

Theme injection is handled via `ThemeProvider`, a wrapper around `next-themes` that accepts attributes, default settings, and transition rules. Across route layouts, theme configuration values differ based on authentication or installation state requirements:

| Route Group / Layout | Default Theme | Enable System | Disable Transition On Change | Attribute |
| :--- | :--- | :--- | :--- | :--- |
| **Root Dashboard** (`dashboard-client-layout.tsx`) | `light` | `true` | `true` | `class` |
| **Setup Wizard** (`setup/layout.tsx`) | `light` | `false` | `true` | `class` |
| **Authentication** (`auth/layout.tsx`) | `system` | `true` | `true` | `class` |

Sources: [server/dashboard/src/app/root/dashboard-client-layout.tsx:41-46](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard-client-layout.tsx#L41-L46), [server/dashboard/src/app/setup/layout.tsx:31-35](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/layout.tsx#L31-L35), [server/dashboard/src/app/auth/layout.tsx:32-37](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(auth)/layout.tsx#L32-L37)

> [!NOTE]
> The setup wizard layout intentionally disables system theme detection (`enableSystem={false}`), forcing a default light theme during initial installation, whereas authentication and dashboard layouts support dynamic system preference matching.

Sources: [server/dashboard/src/app/setup/layout.tsx:31-35](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/layout.tsx#L31-L35), [server/dashboard/src/app/auth/layout.tsx:32-37](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(auth)/layout.tsx#L32-L37)

### Client Layout and Sidebar Geometry

The inner `ClientLayout` component enforces session validation logic. It evaluates the current pathname against public prefixes (`/login` and `/setup`) and monitors authentication state. If a user is unauthenticated on a protected path, it renders an `AuthLoadingState` containing a theme-aware logo and linear progress indicator while executing a router replacement to `/login`.

```tsx
const isPublicPage =
  pathname.startsWith("/login") || pathname.startsWith("/setup");

useEffect(() => {
  if (!isPublicPage && !isLoading && !user) {
    router.replace("/login");
  }
}, [isLoading, isPublicPage, router, user]);

if (isLoading && !isPublicPage) {
  return <AuthLoadingState />;
}

if (!isPublicPage && !user) {
  return <AuthLoadingState />;
}
```
Sources: [server/dashboard/src/app/root/clientLayout.tsx:31-46](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/clientLayout.tsx#L31-L46)

Within the dashboard sub-layout, structural dimensions for navigation and content containers are governed by fixed constants exported from the client layout module.

```tsx
export const SIDEBAR_WIDTH = 180;
export const COLLAPSED_SIDEBAR_WIDTH = 64;
export const COLLAPSED_SIDEBAR_PADDING = 16;
export const COLLAPSED_SIDEBAR_WIDTH_WITHOUT_PADDING =
  COLLAPSED_SIDEBAR_WIDTH - COLLAPSED_SIDEBAR_PADDING;
```
Sources: [server/dashboard/src/app/root/clientLayout.tsx:10-14](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/clientLayout.tsx#L10-L14)

The dashboard view layout reads sidebar collapse state from the Redux store (`state.layout.isSidebarCollapsed`) to dynamically calculate horizontal offsets and content area width styles for the main scrollable viewport container.

```tsx
const isSidebarCollapsed = useSelector(
  (state: RootState) => state.layout.isSidebarCollapsed,
);

return (
  <>
    <NavWrapper />
    <div
      className="mt-[48px] rounded-tl-lg relative h-[calc(100vh-56px)] bg-surface-default-primary border border-memBorder-primary overflow-hidden transition-all duration-300 ease-in-out font-fustat"
      style={{
        left: `${isSidebarCollapsed ? COLLAPSED_SIDEBAR_WIDTH : SIDEBAR_WIDTH}px`,
        width: `calc(100vw - ${isSidebarCollapsed ? COLLAPSED_SIDEBAR_WIDTH + 8 : SIDEBAR_WIDTH + 8}px)`,
      }}
```
Sources: [server/dashboard/src/app/root/dashboard/layout.tsx:15-27](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/layout.tsx#L15-L27)

## Client-Side Authentication Session Management

### AuthProvider Context and Session Lifecycle

Client-side session handling centers around `AuthProvider`, a client component (`"use client"`) that manages user profile state, loading indicators, and authentication actions. The `AuthContext` supplies consumer hooks with user records, role checking via `isAdmin`, and actions for `login`, `register`, `logout`, and `refreshUser`.

```tsx
export interface AuthUser {
  id: string;
  name: string;
  email: string;
  role: string;
  created_at: string;
}

interface AuthContextValue {
  user: AuthUser | null;
  isLoading: boolean;
  isAdmin: boolean;
  login: (email: string, password: string) => Promise<void>;
  register: (name: string, email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  refreshUser: () => Promise<void>;
}

export const AuthContext = createContext<AuthContextValue>({
  user: null,
  isLoading: true,
  isAdmin: false,
  login: async () => {},
  register: async () => {},
  logout: async () => {},
  refreshUser: async () => {},
});
```
Sources: [server/dashboard/src/lib/auth.tsx:13-39](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/lib/auth.tsx#L13-L39)

When the component mounts, an effect hook invokes `refreshSession()` to exchange an HTTP-only refresh cookie for an access token. If successful, it calls `loadUser()` to fetch user details from `AUTH_ENDPOINTS.ME`. In case of failure or network rejection, `user` resets to `null`, and `isLoading` sets to `false`.

```tsx
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<AuthUser | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  const loadUser = useCallback(async () => {
    const res = await api.get<AuthUser>(AUTH_ENDPOINTS.ME);
    setUser(res.data);
  }, []);

  useEffect(() => {
    let active = true;
    (async () => {
      try {
        const ok = await refreshSession();
        if (ok && active) await loadUser();
      } catch {
        if (active) setUser(null);
      } finally {
        if (active) setIsLoading(false);
      }
    })();
    return () => {
      active = false;
    };
  }, [loadUser]);
```
Sources: [server/dashboard/src/lib/auth.tsx:64-88](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/lib/auth.tsx#L13-L88)

> [!WARNING]
> Component unmounting sets `active = false` to prevent asynchronous state updates on unmounted component instances during initial session validation.

Sources: [server/dashboard/src/lib/auth.tsx:73-87](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/lib/auth.tsx#L73-L87)

### API Token Handlers and Cookie Routes

Client authentication helpers communicate with Next.js API route handlers to read, write, and purge session tokens. The backend refresh route manages secure cookies named `mem0_refresh_token` using parameters configured in `COOKIE_OPTIONS`.

```typescript
const COOKIE_NAME = "mem0_refresh_token";

function shouldUseSecureCookie() {
  const dashboardUrl = process.env.DASHBOARD_URL;
  if (!dashboardUrl) {
    return process.env.NODE_ENV === "production";
  }

  try {
    return new URL(dashboardUrl).protocol === "https:";
  } catch {
    return process.env.NODE_ENV === "production";
  }
}

const COOKIE_OPTIONS = {
  httpOnly: true,
  secure: shouldUseSecureCookie(),
  sameSite: "lax" as const,
  path: "/",
  maxAge: 30 * 24 * 60 * 60, // 30 days
};
```
Sources: [server/dashboard/src/app/api/auth/refresh/route.ts:6-27](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/api/auth/refresh/route.ts#L6-L27)

| Route Method | Action | Request Payload | Response | Error Code |
| :--- | :--- | :--- | :--- | :--- |
| **POST** | Validates existing refresh token and issues new tokens | None (reads cookie) | `{ access_token }` | `401` |
| **PUT** | Stores refresh token in HTTP-only cookie | `{ refresh_token }` | `{ ok: true }` | `400` |
| **DELETE** | Clears refresh token cookie | None | `{ ok: true }` | — |

Sources: [server/dashboard/src/app/api/auth/refresh/route.ts:29-75](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/api/auth/refresh/route.ts#L29-L75)

Authentication hooks like `useAuth` provide clean access to the context throughout component trees by wrapping `useContext(AuthContext)`.

```typescript
export function useAuth() {
  return useContext(AuthContext);
}
```
Sources: [server/dashboard/src/hooks/use-auth.ts:6-8](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-auth.ts#L6-L8)

## Route Protection and Edge Middleware

### Middleware Execution and Path Interception

The dashboard relies on Next.js edge middleware running prior to route resolution to inspect incoming requests and enforce authentication policies. The middleware matches requests against paths excluding static assets and static files as defined by the matcher configuration.

```typescript
export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|fonts|images|icons).*)"],
};
```
Sources: [server/dashboard/src/middleware.ts:65-67](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L65-L67)

Incoming path evaluation begins by checking against a static array of public prefixes. If a request pathname starts with any entry in `PUBLIC_PATHS`, the middleware bypasses all checks and allows execution to proceed.

```typescript
const PUBLIC_PATHS = [
  "/_next",
  "/api/auth",
  "/api/health",
  "/fonts",
  "/favicon",
];

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) {
    return NextResponse.next();
  }
```
Sources: [server/dashboard/src/middleware.ts:5-18](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L5-L18)

| Public Path Prefix | Purpose |
| :--- | :--- |
| `/_next` | Next.js internal static chunks and assets |
| `/api/auth` | Authentication endpoints and token refresh routes |
| `/api/health` | System health check inspection route |
| `/fonts` | Application font files |
| `/favicon` | Browser icon assets |

Sources: [server/dashboard/src/middleware.ts:5-11](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L5-L11)

### Setup Status Verification and Routing

When a request targets root paths (`/`, `/login`, or `/setup`), the middleware queries the backend server setup status endpoint to determine whether initial deployment registration is required.

```typescript
if (pathname === "/" || pathname === "/login" || pathname === "/setup") {
  try {
    const res = await fetch(
      `${getServerApiUrl()}${AUTH_ENDPOINTS.SETUP_STATUS}`,
    );
```
Sources: [server/dashboard/src/middleware.ts:22-26](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L22-L26)

> [!WARNING]
> If the backend API is unreachable during setup status checks, the network request throws an error which is caught locally, causing the middleware to gracefully fall through to standard navigation rules without blocking requests.

Sources: [server/dashboard/src/middleware.ts:37-39](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L37-L39)

Depending on the boolean flag `needsSetup` returned from the API, the middleware enforces routing rules:

```typescript
if (res.ok) {
  const { needsSetup } = await res.json();

  if (needsSetup && pathname !== "/setup") {
    return NextResponse.redirect(new URL("/setup", request.url));
  }
  if (!needsSetup && pathname === "/setup") {
    return NextResponse.redirect(new URL("/login", request.url));
  }
}
```
Sources: [server/dashboard/src/middleware.ts:27-36](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L27-L36)

### Token Verification and Protected Navigation

For routes outside the public or setup pathways, the middleware checks for the presence of the HTTP-only refresh token cookie (`mem0_refresh_token`).

```typescript
const hasRefreshToken = request.cookies.has("mem0_refresh_token");
```
Sources: [server/dashboard/src/middleware.ts:20-20](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L20-L20)

If a user visits the application root (`/`), redirection behavior branches based on whether the refresh token cookie exists. If present, the user is forwarded to `/dashboard/requests`; otherwise, they are routed to `/login`. Similarly, base dashboard paths (`/dashboard` or `/dashboard/`) automatically rewrite to `/dashboard/requests`.

```typescript
if (pathname === "/") {
  return NextResponse.redirect(
    new URL(hasRefreshToken ? "/dashboard/requests" : "/login", request.url),
  );
}

if (pathname === "/dashboard" || pathname === "/dashboard/") {
  return NextResponse.redirect(new URL("/dashboard/requests", request.url));
}
```
Sources: [server/dashboard/src/middleware.ts:46-54](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L46-L54)

When a protected route is accessed without the `mem0_refresh_token` cookie, the middleware constructs a redirect response targeting `/login` and appends the original destination to a `next` query parameter so the client can resume its path after authentication.

```typescript
if (!hasRefreshToken) {
  const loginUrl = new URL("/login", request.url);
  loginUrl.searchParams.set("next", pathname);
  return NextResponse.redirect(loginUrl);
}
```
Sources: [server/dashboard/src/middleware.ts:56-60](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/middleware.ts#L56-L60)

## Axios Client and Token Interception

### Centralized API Client Setup and Bearer Token Injection

The dashboard instantiates a centralized Axios instance configured with a base URL sourced from `process.env.NEXT_PUBLIC_API_URL`. To authenticate outgoing requests, a request interceptor inspects the in-memory token cache and injects a Bearer authorization header if a token exists.

```typescript
let cachedToken: string | null = null;
const LOGIN_PATH = "/login";

export const setAccessToken = (token: string | null) => {
  cachedToken = token;
};

export const getAccessToken = (): string | null => {
  return cachedToken;
};

```
Sources: [server/dashboard/src/utils/api.ts:3-13](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts#L3-L13)

```typescript
const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
});

api.interceptors.request.use(
  async (config) => {
    if (cachedToken) {
      config.headers = config.headers ?? {};
      config.headers.Authorization = `Bearer ${cachedToken}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  },
);
```
Sources: [server/dashboard/src/utils/api.ts:42-57](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts#L42-L57)

> [!NOTE]
> Tokens are held exclusively in runtime memory via `cachedToken` and manipulated using `setAccessToken` and `getAccessToken` helpers, preventing persistent storage vulnerabilities on the client side.

Sources: [server/dashboard/src/utils/api.ts:3-12](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts#L3-L12)

### Response Interception and Token Refresh Flow

When an API request encounters a 401 Unauthorized response, the response interceptor triggers an automated token recovery sequence. It first clears the stored token via `handleTokenError` and issues a POST request to `/api/auth/refresh` with credentials included.

```typescript
const handleTokenError = () => {
  cachedToken = null;
};
```
Sources: [server/dashboard/src/utils/api.ts:14-16](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts#L14-L16)

```typescript
const refreshAccessToken = async () => {
  const refreshResponse = await fetch("/api/auth/refresh", {
    method: "POST",
    credentials: "include",
  });

  if (!refreshResponse.ok) {
    return null;
  }

  const data = await refreshResponse.json();
  setAccessToken(data.access_token);
  return data.access_token as string;
};
```
Sources: [server/dashboard/src/utils/api.ts:24-37](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts#L24-L37)

If the refresh endpoint returns a valid new access token, the client updates its cache, attaches the new Bearer token to the original request configuration, and retries the failed call. If the refresh attempt fails or throws an exception, the client clears the token cache and executes a window redirect to `/login`.

```typescript
api.interceptors.response.use(
  (response) => response,
  async (error: AxiosError<{ error?: string }>) => {
    if (error.response?.status === 401) {
      handleTokenError();

      try {
        const nextToken = await refreshAccessToken();
        if (nextToken && error.config) {
          error.config.headers = error.config.headers ?? {};
          error.config.headers.Authorization = `Bearer ${nextToken}`;
          return api.request(error.config);
        }
      } catch {}

      handleTokenError();
      redirectToLogin();
    }
```
Sources: [server/dashboard/src/utils/api.ts:59-76](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts#L59-L76)

```typescript
const redirectToLogin = () => {
  if (typeof window !== "undefined") {
    window.location.href = LOGIN_PATH;
  }
};
```
Sources: [server/dashboard/src/utils/api.ts:18-22](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts#L18-L22)

### Streaming Request Handling

Alongside the standard Axios instance, the API module exposes a custom `postStream` helper designed to handle server-sent event or streaming payload requests via the native `fetch` API.

```typescript
const postStream = async (url: string, data: unknown): Promise<Response> => {
  const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${url}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: cachedToken ? `Bearer ${cachedToken}` : "",
    },
    body: JSON.stringify(data),
  });
```
Sources: [server/dashboard/src/utils/api.ts:86-94](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts#L86-L94)

The `postStream` function evaluates response statuses explicitly, throwing an unauthorized error and triggering `redirectToLogin` if a 401 status code occurs, or parsing error payloads when requests fail.

```typescript
  if (response.status === 401) {
    handleTokenError();
    redirectToLogin();
    throw new Error("Unauthorized");
  }

  if (!response.ok) {
    const errorData = await response.json().catch(() => ({}));
    throw new Error(errorData.error || "Request failed");
  }

  return response;
};
```
Sources: [server/dashboard/src/utils/api.ts:96-108](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/utils/api.ts#L96-L108)

## Data Fetching Hook Lifecycle

### The `useApiQuery` Execution Model

The `useApiQuery` custom hook manages asynchronous data fetching, loading flags, error strings, and manual refetch capabilities for dashboard components like ApiKeysPage and ConfigurationPage. It accepts a generic `fetcher` function alongside a `UseApiQueryOptions` configuration object specifying `enabled`, `errorToast`, and `initialData`.

```typescript
interface UseApiQueryOptions<T> {
  enabled?: boolean;
  errorToast?: string;
  initialData?: T;
}

interface UseApiQueryResult<T> {
  data: T | undefined;
  isLoading: boolean;
  error: string;
  refetch: () => Promise<void>;
}

export function useApiQuery<T>(
  fetcher: () => Promise<T>,
  options: UseApiQueryOptions<T> = {},
): UseApiQueryResult<T> {
```
Sources: [server/dashboard/src/hooks/use-api-query.ts:5-21](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-api-query.ts#L5-L21)

State management tracks resolved items via `data`, active request status via `isLoading`, and failure messages via `error`. A React `useRef` tracks the latest `fetcher` reference so callbacks execute the most up-to-date query logic without capturing stale closures.

```typescript
const [data, setData] = useState<T | undefined>(initialData);
const [isLoading, setIsLoading] = useState(enabled);
const [error, setError] = useState("");

const fetcherRef = useRef(fetcher);
fetcherRef.current = fetcher;

```
Sources: [server/dashboard/src/hooks/use-api-query.ts:24-30](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-api-query.ts#L24-L30)

### Call-Chain Execution Walkthrough

1. `ApiKeysPage` or `ConfigurationPage` invokes `useApiQuery` during component initialization, passing a custom async fetcher and options.
2. `useApiQuery` defines the `run` callback via `useCallback`, which marks `isLoading` as true and clears any existing `error` string.
3. `run` executes `fetcherRef.current()` to retrieve network data, storing the resolved value in `data` upon success.
4. If an exception occurs, `getErrorMessage` processes the error alongside any provided `errorToast` label, populates `error`, and invokes `toast()` if an error toast option was specified.
5. The `finally` block sets `isLoading` to false, and a `useEffect` hook triggers `run` automatically on mount if `enabled` is true.

```typescript
const run = useCallback(async () => {
  setIsLoading(true);
  setError("");
  try {
    setData(await fetcherRef.current());
  } catch (err) {
    const message = getErrorMessage(err, errorToast || "Request failed");
    setError(message);
    if (errorToast) {
      toast({
        title: errorToast,
        description: message,
        variant: "destructive",
      });
    }
  } finally {
    setIsLoading(false);
  }
}, [errorToast]);

useEffect(() => {
  if (enabled) void run();
}, [enabled, run]);
```
Sources: [server/dashboard/src/hooks/use-api-query.ts:31-53](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-api-query.ts#L31-L53)

```mermaid
sequenceDiagram
    participant Page as ApiKeysPage / ConfigurationPage
    participant Hook as useApiQuery
    participant Run as run()
    participant Fetcher as fetcherRef.current()

    Page->>Hook: Invoke useApiQuery(fetcher, options)
    Hook->>Run: useEffect / refetch triggers run()
    Run->>Fetcher: Execute fetcherRef.current()
    Fetcher-->>Run: Resolve data or throw error
    Run->>Hook: Update data, isLoading, error state & toast
```
Sources: [server/dashboard/src/hooks/use-api-query.ts:31-55](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-api-query.ts#L31-L55), [server/dashboard/src/app/root/dashboard/api-keys/page.tsx:41-47](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/api-keys/page.tsx#L41-L47), [server/dashboard/src/app/root/dashboard/configuration/page.tsx:41-47](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/configuration/page.tsx#L41-L47)

### Options and Return Signature Reference

| Property / Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `enabled` | `boolean` | `true` | Controls whether the query executes automatically on initial component mount. |
| `errorToast` | `string` | `undefined` | Title string passed to the centralized notification toast if the request fails. |
| `initialData` | `T` | `undefined` | Fallback data value assigned before the initial fetch completes. |
| `data` | `T \| undefined` | `initialData` | The successfully resolved payload returned by the fetcher function. |
| `isLoading` | `boolean` | `enabled` | Boolean flag tracking whether an asynchronous fetch operation is currently in flight. |
| `error` | `string` | `""` | Formatted error message string populated when a fetch exception occurs. |
| `refetch` | `() => Promise<void>` | — | Manual trigger function to re-run the asynchronous fetcher on demand. |

```typescript
interface UseApiQueryOptions<T> {
  enabled?: boolean;
  errorToast?: string;
  initialData?: T;
}

interface UseApiQueryResult<T> {
  data: T | undefined;
  isLoading: boolean;
  error: string;
  refetch: () => Promise<void>;
}

export function useApiQuery<T>(
  fetcher: () => Promise<T>,
  options: UseApiQueryOptions<T> = {},
): UseApiQueryResult<T> {
```
Sources: [server/dashboard/src/hooks/use-api-query.ts:5-21](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-api-query.ts#L5-L21)

```typescript
return { data, isLoading, error, refetch: run };
```
Sources: [server/dashboard/src/hooks/use-api-query.ts:55-55](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-api-query.ts#L55-L55)

> [!NOTE]
> If `errorToast` is omitted from `UseApiQueryOptions`, failure messages update the local `error` state variable without dispatching an intrusive toast notification to the user interface.

Sources: [server/dashboard/src/hooks/use-api-query.ts:37-45](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/hooks/use-api-query.ts#L37-L45)

## Redux Toolkit State and Navigation

### Redux Store Configuration and Root Reducer

The application state container is established using Redux Toolkit's `configureStore` helper, importing a consolidated `rootReducer` that merges all feature slices. Currently, the store manages a single slice under the `layout` key, and exports standard TypeScript definitions for `RootState` and `AppDispatch`.

```typescript
import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./rootReducer";

const store = configureStore({
  reducer: rootReducer,
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
```
Sources: [server/dashboard/src/store/store.ts:1-10](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/store/store.ts#L1-L10)

```typescript
import { combineReducers } from "redux";
import { layoutReducer } from "./reducers/layoutReducer";

const rootReducer = combineReducers({
  layout: layoutReducer,
});

export default rootReducer;
```
Sources: [server/dashboard/src/store/rootReducer.ts:1-8](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/store/rootReducer.ts#L1-L8)

| Export Name | Return Type / Definition | Purpose |
| :--- | :--- | :--- |
| `store` | Return type of `configureStore` | The central Redux store instance wrapping `rootReducer`. |
| `RootState` | `ReturnType<typeof store.getState>` | Global state type used by selectors across components. |
| `AppDispatch` | `typeof store.dispatch` | Dispatch type for triggering slice actions. |

Sources: [server/dashboard/src/store/store.ts:8-10](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/store/store.ts#L8-L10)

> [!NOTE]
> `rootReducer` uses Redux's `combineReducers` function, making it easy to append new feature slices alongside `layout` as the dashboard architecture expands.

Sources: [server/dashboard/src/store/rootReducer.ts:1-6](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/store/rootReducer.ts#L1-L6)

### Collapsible Sidebar Navigation and Layout State

The sidebar navigation layout relies on the `isSidebarCollapsed` boolean flag retrieved from `state.layout.isSidebarCollapsed` via Redux's `useSelector`. Depending on this state, components like `MainNav` and `NavWrapper` dynamically adapt their width, hide or display text labels, and toggle collapsible trigger groups.

```tsx
const pathname = usePathname();
const isSidebarCollapsed = useSelector(
  (state: RootState) => state.layout.isSidebarCollapsed,
);
const [isCloudOpen, setIsCloudOpen] = React.useState(true);

```
Sources: [server/dashboard/src/app/root/dashboard/components/main-nav.tsx:43-48](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/components/main-nav.tsx#L43-L48)

The navigation structure is divided into three primary sections: **Activity**, **Cloud Features** (wrapped in a local `Collapsible` component controlled by `isCloudOpen`), and **Account**. When the sidebar is collapsed, group labels are hidden, and menu items center their icons with tooltip text enabled.

```tsx
<div className="flex flex-col gap-0">
  {!isSidebarCollapsed && (
    <SidebarGroupLabel className="mb-0">
      ACTIVITY
    </SidebarGroupLabel>
  )}
  {[
    {
      title: "Requests",
      url: "/dashboard/requests",
      icon: Activity,
      active: pathname === "/dashboard/requests",
    },
    {
      title: "Memories",
      url: "/dashboard/memories",
      icon: GalleryVerticalEnd,
      active: pathname === "/dashboard/memories",
    },
    {
      title: "Entities",
      url: "/dashboard/entities",
      icon: Users,
      active: pathname === "/dashboard/entities",
    },
  ].map((item) => (
    <SidebarMenuItem key={item.title}>
      <SidebarMenuButton
        asChild
        collapsed={isSidebarCollapsed}
        active={item.active}
        tooltip={isSidebarCollapsed ? item.title : undefined}
      >
        <Link
          href={item.url}
          className={cn(
            "flex items-center w-full",
            isSidebarCollapsed
              ? "justify-center mx-auto"
              : "gap-1.5",
          )}
        >
          <item.icon className="size-4 shrink-0" />
          {!isSidebarCollapsed && <span>{item.title}</span>}
        </Link>
      </SidebarMenuButton>
    </SidebarMenuItem>
  ))}
</div>

{isSidebarCollapsed && (
  <div className="h-[1px] w-full bg-memBorder-primary my-2" />
)}

<Collapsible
  open={isCloudOpen}
  onOpenChange={setIsCloudOpen}
  className="flex flex-col gap-0"
>
  {!isSidebarCollapsed && (
    <CollapsibleTrigger asChild>
      <SidebarGroupLabel className="cursor-pointer mb-0">
        CLOUD FEATURES
        <ChevronDown
          className={cn(
            "size-3 transition-transform duration-200",
            isCloudOpen ? "" : "-rotate-90",
          )}
        />
      </SidebarGroupLabel>
    </CollapsibleTrigger>
  )}
  <CollapsibleContent className="flex flex-col gap-0">
    {[
      {
        title: "Categories",
        url: "/dashboard/categories",
        icon: Tags,
      },
      {
```
Sources: [server/dashboard/src/app/root/dashboard/components/main-nav.tsx:59-242](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/components/main-nav.tsx#L59-L242)

```tsx
export default function NavWrapper() {
  const dispatch = useDispatch();
  const isSidebarCollapsed = useSelector(
    (state: RootState) => state.layout.isSidebarCollapsed,
  );
  const { user, logout } = useAuth();

  const instanceName = process.env.NEXT_PUBLIC_INSTANCE_NAME || "Mem0";

  const handleToggle = useCallback(() => {
    dispatch(toggleSidebar());
  }, [dispatch]);

```
Sources: [server/dashboard/src/app/root/dashboard/components/nav-wrapper.tsx:31-43](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/components/nav-wrapper.tsx#L31-L43)

> [!WARNING]
> Toggling the sidebar state updates both the fixed container width and the adjacent content offset calculations using constants like `COLLAPSED_SIDEBAR_WIDTH` and `SIDEBAR_WIDTH`.

Sources: [server/dashboard/src/app/root/dashboard/components/nav-wrapper.tsx:46-51](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/components/nav-wrapper.tsx#L46-L51), [server/dashboard/src/app/root/dashboard/components/nav-wrapper.tsx:136-141](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/(root)/dashboard/components/nav-wrapper.tsx#L136-L141)

## First-Run Setup Wizard Flow

### Multi-Step Wizard and Account Registration

The first-run setup wizard is orchestrated by `SetupPage`, which guides uninitialized server instances through a sequential five-step flow. The wizard tracks the active step index via local state and references an array of step titles defining the initialization lifecycle.

```tsx
const STEP_TITLES = [
  "Create your admin account",
  "Review provider configuration",
  "Your API key",
  "Tell us your use case",
  "Test your setup",
];
```
Sources: [server/dashboard/src/app/setup/page.tsx:45-51](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L45-L51)

| Step Index | Title | Primary Action / Handler |
| :--- | :--- | :--- |
| `0` | Create your admin account | `handleStep1` validates email format, confirms password matching, enforces a minimum length of 8 characters, and invokes `register(name, email, password)`. |
| `1` | Review provider configuration | `handleStep2` checks if configuration fields are dirty, builds provider payloads, and posts updates to `MEMORY_ENDPOINTS.CONFIGURE`. |
| `2` | Your API key | `handleStep3` requests a new API key from `API_KEY_ENDPOINTS.BASE` using the specified key label. |
| `3` | Tell us your use case | Captures user use case selection from preset options and custom instructions. |
| `4` | Test your setup | Executes a validation test message against the configured backend. |

Sources: [server/dashboard/src/app/setup/page.tsx:45-51](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L45-L51), [server/dashboard/src/app/setup/page.tsx:157-186](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L157-L186), [server/dashboard/src/app/setup/page.tsx:188-235](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L188-L235), [server/dashboard/src/app/setup/page.tsx:237-246](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L237-L246)

> [!WARNING]
> Step 1 registration enforces strict client-side validation: `email` must satisfy `isValidEmail`, `password` must equal `confirmPassword`, and `password.length` must be at least 8 characters before submitting credentials to the authentication provider.

Sources: [server/dashboard/src/app/setup/page.tsx:160-173](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L160-L173)

### Provider Configuration and Credentials

When the wizard advances to step index `1`, `useEffect` triggers an asynchronous configuration loader. This routine fetches the effective server configuration and bundled providers concurrently using `MEMORY_ENDPOINTS.CONFIGURE` and `MEMORY_ENDPOINTS.CONFIGURE_PROVIDERS`.

```tsx
const loadConfig = async () => {
  try {
    const [configRes, providersRes] = await Promise.all([
      api.get(MEMORY_ENDPOINTS.CONFIGURE),
      api.get<BundledProviders>(MEMORY_ENDPOINTS.CONFIGURE_PROVIDERS),
    ]);
```
Sources: [server/dashboard/src/app/setup/page.tsx:112-117](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L112-L117)

The configuration loader extracts provider identifiers and model names for both Language Model (`llm`) and `embedder` subsystems, storing both current state values and initial baselines to detect subsequent modifications.

```tsx
const llmProv = config?.llm?.provider || "";
const llmMod = config?.llm?.config?.model || "";
const embProv = config?.embedder?.provider || "";
const embMod = config?.embedder?.config?.model || "";

setLlmProvider(llmProv);
setLlmModel(llmMod);
setEmbedderProvider(embProv);
setEmbedderModel(embMod);
setInitialLlmProvider(llmProv);
setInitialLlmModel(llmMod);
setInitialEmbedderProvider(embProv);
setInitialEmbedderModel(embMod);
setServerHasLlmKey(!!config?.llm?.config?.api_key);
setProviders(providersRes.data);
```
Sources: [server/dashboard/src/app/setup/page.tsx:124-138](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L124-L138)

```tsx
const USE_CASE_PRESETS = [
  "Personal assistant",
  "Coding agent",
  "Customer support",
  "Research",
  "Therapy / journaling",
];
```
Sources: [server/dashboard/src/app/setup/page.tsx:55-61](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L55-L61)

> [!TIP]
> If no changes are detected when submitting step 2 (`dirty` evaluates to `false`), the wizard bypasses the API save request and immediately advances to step 2.

Sources: [server/dashboard/src/app/setup/page.tsx:192-202](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/server/dashboard/src/app/setup/page.tsx#L192-L202)

## Related

- [Dashboard Views](https://www.doc0.dev/docs/faa36707-7c28-4f69-a18f-700ff61c704e/technical/server-dashboard/dashboard-views)
- [FastAPI Application](https://www.doc0.dev/docs/faa36707-7c28-4f69-a18f-700ff61c704e/technical/server-dashboard/fastapi-application)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/faa36707-7c28-4f69-a18f-700ff61c704e/llms.txt) for all pages in this wiki.
