# Frontend Module Map & State Architecture

> React 18 / Jotai / Apollo Client architecture: how modules (object-record, settings, auth, workflow, command-menu, navigation) are structured, how global state atoms compose with GraphQL cache, and the Linaria/Lingui conventions. Covers the twenty-ui shared component library and the metadata-store that mirrors backend schema on the client.

- Repository: twentyhq/twenty
- GitHub: https://github.com/twentyhq/twenty
- Human wiki: https://grok-wiki.com/public/wiki/twentyhq-twenty-7ed82e5a21f6
- Complete Markdown: https://grok-wiki.com/public/wiki/twentyhq-twenty-7ed82e5a21f6/llms-full.txt

## Source Files

- `packages/twenty-front/src/modules/object-record`
- `packages/twenty-front/src/modules/settings`
- `packages/twenty-front/src/modules/auth`
- `packages/twenty-front/src/modules/metadata-store`
- `packages/twenty-front/src/modules/workflow`
- `packages/twenty-front/src/modules/command-menu`
- `packages/twenty-front/src/modules/navigation`
- `packages/twenty-ui/src`

---

> ⚠️ The agent returned an invalid wiki page. This page needs recovery.
>
> First failure: the page did not include the required "# Frontend Module Map & State Architecture" heading near the top
> Retry failure: the page did not include the required "# Frontend Module Map & State Architecture" heading near the top

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts](packages/twenty-front/src/modules/metadata-store/states/metadataStoreState.ts)
- [packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts](packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts)
- [packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts](packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts)
- [packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts](packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts)
- [packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomState.ts](packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomState.ts)
- [packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomComponentFamilyState.ts](packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomComponentFamilyState.ts)
- [packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap.ts](packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap.ts)
- [packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts](packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts)
- [packages/twenty-front/src/modules/object-record/hooks/useAggregateRecords.ts](packages/twenty-front/src/modules/object-record/hooks/useAggregateRecords.ts)
- [packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx](packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx)
- [packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useNavigationSection.ts](packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useNavigationSection.ts)
- [packages/twenty-front/src/modules/auth/sign-in-up/components/FooterNote.tsx](packages/twenty-front/src/modules/auth/sign-in-up/components/FooterNote.tsx)
- [packages/twenty-front/src/modules/workflow/types/Workflow.ts](packages/twenty-front/src/modules/workflow/types/Workflow.ts)
- [packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts](packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts)
</details>

# Frontend Module Map & State Architecture

The Twenty frontend (`packages/twenty-front`) is a single-page React 18 application designed for maximum extensibility and performance. It organizes core domain logic into distinct modules under `src/modules/`. This design encapsulates files related to specific features—including components, custom hooks, state atoms, types, and GraphQL assets—preventing cross-domain spaghetti code.

The state management architecture relies on a hybrid model that separates concern: **Apollo Client** acts as the authoritative engine for normalized remote database record caches, while **Jotai** handles local UI state, authentication details, and database schema representations. By utilizing Jotai custom wrappers, Twenty maintains client-side persistence and per-instance state isolation. This wiki outlines the module mapping, state architecture, and UI paradigms of the Twenty frontend.

---

## State & Data Flow Architecture

```mermaid
flowchart TB
    %% Subgraphs for visual layers
    subgraph UI ["twenty-front UI & Styling (twenty-ui)"]
        Components["React 18 Components"]
        LinariaStyled["Linaria zero-runtime CSS-in-JS<br/>(using themeCssVariables)"]
        LinguiMacro["Lingui i18n Macros<br/>(Trans, t template tag)"]
        Components --> LinariaStyled
        Components --> LinguiMacro
    end

    subgraph State ["Client-Side State Management (Jotai)"]
        subgraph Atoms ["Atom Types & Collections"]
            StateAtom["State (createAtomState)<br/>- local/session storage / cookies"]
            FamilyState["FamilyState (createAtomFamilyState)<br/>- e.g., metadataStoreState"]
            CompFamilyState["ComponentFamilyState (createAtomComponentFamilyState)<br/>- per-instance context isolation"]
        end
        Selectors["Derived Selectors (createAtomSelector)<br/>- e.g., objectMetadataItemsWithFieldsSelector"]
        Atoms --> Selectors
    end

    subgraph ServerData ["Server Data & Schema Sync (Apollo Client)"]
        ApolloFactory["ApolloFactory (Apollo Client Manager)"]
        MetadataStore["Metadata Store Module<br/>(Schema mirror + hash checking)"]
        ApolloFactory -->|FIND_MINIMAL_METADATA| MetadataStore
        MetadataStore -->|Writes current / draft| FamilyState
    end

    subgraph RecordCRUD ["Dynamic Record Operations (object-record)"]
        RecordHooks["Record Hooks (e.g., useAggregateRecords)"]
        GqlQueryBuilder["GQL Dynamic Document Builder<br/>(e.g., useAggregateRecordsQuery)"]
        
        Selectors -->|Reads Enriched Schema| RecordHooks
        RecordHooks --> GqlQueryBuilder
        GqlQueryBuilder -->|Executes Dynamic Query| ApolloFactory
    end

    %% Dependencies between layers
    Components -->|Reads State/Selectors| Selectors
    Components -->|Calls Record Hooks| RecordHooks
    ApolloFactory -->|Normalized Cache / CRUD| RecordHooks
```

---

## Module Directory Map

The core domain logic of `twenty-front` is organized as decoupled modules. Each module owns its specific features:

| Module | Core Responsibility | Key Files / Subdirectories |
| :--- | :--- | :--- |
| **`auth`** | Session management, OAuth logins, cookies, bypass mechanisms, and auth tokens. | `states/tokenPairState.ts`<br/>`sign-in-up/` |
| **`metadata-store`** | Mirrors the backend schema locally to support dynamic UI layout generation. | `states/metadataStoreState.ts`<br/>`hooks/useLoadMinimalMetadata.ts` |
| **`object-metadata`** | Resolves dynamic object configurations and calculates access control/permissions. | `states/objectMetadataItemsWithFieldsSelector.ts`<br/>`hooks/useObjectMetadataItem.ts` |
| **`object-record`** | Dynamic record operations, filters, sorting, offsets, and paginated aggregates. | `hooks/useAggregateRecords.ts`<br/>`hooks/useUpdateOneRecord.ts` |
| **`command-menu`** | The main navigation command menu launcher, action registrations, and filters. | `states/contexts/CommandMenuComponentInstanceContext.ts` |
| **`command-menu-item`**| Context-aware rendering of specific command items depending on the view. | `contexts/CommandMenuContextProviderContent.tsx` |
| **`navigation`** | Control drawer layout status, history stacks, and contextual breadcrumbs. | `states/lastVisitedViewPerObjectMetadataItemState.ts` |
| **`workflow`** | UI builder for automation steps and trigger event configurations. | `types/Workflow.ts` |
| **`ui`** | Generic helper modules, Jotai wrapper creators, and component instances context managers. | `utilities/state/jotai/utils/createAtomState.ts` |

---

## Jotai Custom State Wrappers & Isolation

To simplify state definitions and add persistent properties, Twenty wraps Jotai in custom state builders:
1. **`State` (`createAtomState`)**: Standard Jotai atom configured to support optional memory-only storage, session storage, cookies, or `localStorage` backups.
2. **`FamilyState` (`createAtomFamilyState`)**: Creates a key-value family lookup map of atoms (used extensively for metadata items).
3. **`ComponentFamilyState` (`createAtomComponentFamilyState`)**: Maps components to a specific instance ID so that multiple component instances (e.g., side panels, search drawers, table aggregations) do not share or override each other's state values.

### Scoping State via Component Instances
To achieve isolation, Twenty uses a global context mapping model. An `instanceId` is passed down using a standard React context created with `createComponentInstanceContext`. The component registers its status in a global namespace.

```typescript
// packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap.ts
class ComponentInstanceContextMap {
  constructor() {
    if (!isDefined(window.componentComponentStateContextMap)) {
      window.componentComponentStateContextMap = new Map();
    }
  }

  public get(key: string): ComponentInstanceStateContext<any> | undefined {
    return window.componentComponentStateContextMap.get(key);
  }

  public set(key: string, context: ComponentInstanceStateContext<any>) {
    window.componentComponentStateContextMap.set(key, context);
  }
}
```
*Sources: [packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap.ts:4-22]()*

Component family states use this map to cache and query scoped instances:
```typescript
// packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomComponentFamilyState.ts
export const createAtomComponentFamilyState = <ValueType, FamilyKey>({
  key,
  defaultValue,
  componentInstanceContext,
}: {
  key: string;
  defaultValue: ValueType;
  componentInstanceContext: ComponentInstanceStateContext<any> | null;
}) => {
  if (isDefined(componentInstanceContext)) {
    globalComponentInstanceContextMap.set(key, componentInstanceContext);
  }

  const atomCache = new Map<string, any>();

  const familyFunction = ({ instanceId, familyKey }) => {
    const familyKeyStr = typeof familyKey === 'string' ? familyKey : JSON.stringify(familyKey);
    const cacheKey = `${instanceId}__${familyKeyStr}`;
    const existing = atomCache.get(cacheKey);

    if (existing !== undefined) return existing;

    const baseAtom = atom(defaultValue);
    baseAtom.debugLabel = `${key}__${cacheKey}`;
    atomCache.set(cacheKey, baseAtom);
    return baseAtom;
  };

  return { type: 'ComponentFamilyState', key, atomFamily: familyFunction };
};
```
*Sources: [packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createAtomComponentFamilyState.ts:11-56]()*

---

## Metadata Store Lifecycle & Schema Mirroring

Twenty uses a client-side "metadata store" to avoid querying structural databases continuously. This cache contains every custom object metadata, roles, indexes, and layout settings defined in the workspace.

### Schema Syncing
During initialization, the `useLoadMinimalMetadata` hook executes a fast `FIND_MINIMAL_METADATA` query containing an array of collection hashes:

```typescript
// packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts
const { objectMetadataItems, views, collectionHashes } = result.data.minimalMetadata;
const staleEntityKeys: MetadataEntityKey[] = [];

if (isDefined(collectionHashes)) {
  for (const { collectionName, hash } of collectionHashes) {
    const entityKey = mapAllMetadataNameToEntityKey(collectionName);
    if (!isDefined(entityKey)) continue;

    const entry = store.get(metadataStoreState.atomFamily(entityKey));
    if (entry.currentCollectionHash !== hash) {
      staleEntityKeys.push(entityKey);
    }

    store.set(metadataStoreState.atomFamily(entityKey), (prev) => ({
      ...prev,
      draftCollectionHash: hash,
    }));
  }
}
```
*Sources: [packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts:30-58]()*

The client compares these hashes with its local `currentCollectionHash`. If the hash doesn't match, the collection is designated as stale, prompting a lazy sync request for that specific entity type.

### Derived Permission Filtering
To guarantee that users do not interact with columns or objects they are not allowed to access, derived Jotai selectors filter this cached schema dynamically. For instance, the `objectMetadataItemsWithFieldsSelector` resolves current workspace permissions:

```typescript
// packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts
export const objectMetadataItemsWithFieldsSelector = createAtomSelector<EnrichedObjectMetadataItem[]>({
  key: 'objectMetadataItemsWithFieldsSelector',
  get: ({ get }) => {
    const flatObjects = get(flatObjectMetadataItemsSelector);
    const allFlatFields = get(fieldMetadataItemsSelector);
    const allFlatIndexes = get(indexMetadataItemsSelector);
    const currentUserWorkspace = get(currentUserWorkspaceState);

    // Grouping by object ID and applying workspace member permission settings
    return flatObjects.map((flatObject) => {
      const fields = fieldsByObjectId.get(flatObject.id) ?? [];
      const objectPermissions = getObjectPermissionsFromMapByObjectMetadataId({
        objectPermissionsByObjectMetadataId,
        objectMetadataId: flatObject.id,
      });

      const nonReadableFieldMetadataIds = getNonReadableFieldMetadataIdsFromObjectPermissions({ objectPermissions });
      const nonUpdatableFieldMetadataIds = getNonUpdatableFieldMetadataIdsFromObjectPermissions({ objectPermissions });

      return {
        ...flatObject,
        fields,
        readableFields: fields.filter((field) => !nonReadableFieldMetadataIds.includes(field.id)),
        updatableFields: fields.filter((field) => !nonUpdatableFieldMetadataIds.includes(field.id)),
      };
    });
  }
});
```
*Sources: [packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts:13-93]()*

---

## Dynamic CRUD Generation & Apollo Strategy

Dynamic database actions (like viewing a custom CRM object table) are orchestrated dynamically on the fly by combining metadata selectors and custom Apollo queries.

1. The React hook (e.g., `useAggregateRecords`) queries the schema through `useObjectMetadataItem`.
2. It fetches the dynamic GraphQL query structure based on cached properties using helper builders like `useAggregateRecordsQuery`.
3. It validates user access configurations (e.g., `canReadObjectRecords`).
4. Finally, it triggers Apollo Client to fetch and store the response normalized cache inside the application.

```typescript
// packages/twenty-front/src/modules/object-record/hooks/useAggregateRecords.ts
export const useAggregateRecords = <T extends AggregateRecordsData>({
  objectNameSingular,
  filter,
  recordGqlFieldsAggregate,
  skip,
}) => {
  const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
  const apolloCoreClient = useApolloCoreClient();
  const { aggregateQuery, gqlFieldToFieldMap } = useAggregateRecordsQuery({
    objectNameSingular,
    recordGqlFieldsAggregate,
  });

  const objectPermissions = useObjectPermissionsForObject(objectMetadataItem.id);
  const hasReadPermission = objectPermissions.canReadObjectRecords;

  const { data, loading, error } = useQuery<RecordGqlOperationFindManyResult>(
    aggregateQuery,
    {
      skip: skip || !isDefined(objectMetadataItem) || !hasReadPermission,
      variables: { filter },
      client: apolloCoreClient,
    },
  );

  // Formats data map after completion...
  return { objectMetadataItem, data: formattedData, loading, error };
};
```
*Sources: [packages/twenty-front/src/modules/object-record/hooks/useAggregateRecords.ts:20-80]()*

---

## Command Menu Filter & Context Evaluation

The command bar renders dynamic actions (like opening settings, running logic functions, or trigger editing actions) contextually. Using `CommandMenuContextProviderContent`, the module evaluates custom JSON expressions to hide or show commands on the fly:

```typescript
// packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx
const filteredCommandMenuItems = useMemo(() => {
  const currentObjectMetadataItemId = commandMenuContextApi.objectMetadataItem.id;
  const hasSelectedRecords = commandMenuContextApi.numberOfSelectedRecords > 0;
  
  const commandMenuItemsToDisplay = isInPreviewMode
    ? (commandMenuItemsDraft ?? commandMenuItems)
    : commandMenuItems;

  return commandMenuItemsToDisplay
    .filter(doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId))
    .filter(doesCommandMenuItemMatchPageType(commandMenuContextApi.pageType))
    .filter(doesCommandMenuItemMatchSelectionState(hasSelectedRecords))
    .filter(doesCommandMenuItemMatchPageLayoutId(currentPageLayoutId))
    .filter((item) =>
      evaluateConditionalAvailabilityExpression(
        item.conditionalAvailabilityExpression,
        commandMenuContextApi,
      ),
    )
    .sort((firstItem, secondItem) => firstItem.position - secondItem.position);
}, [commandMenuContextApi, commandMenuItems, commandMenuItemsDraft, currentPageLayoutId, isInPreviewMode]);
```
*Sources: [packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx:36-67]()*

---

## Styling (Linaria) & Translation (Lingui) Conventions

### Theme Constants via Zero-Runtime CSS Custom Properties
Twenty avoids utility-first styling classes like Tailwind CSS, adopting **Linaria** to create zero-runtime CSS-in-JS styled elements. Instead of inlining hex colors, styles reference `themeCssVariables` imported from `twenty-ui/theme-constants`. Under the hood, this compiles to standard CSS custom properties (`--font-color-tertiary`, etc.). Changing themes dynamically is done by reassigning root CSS classes (e.g. from `.theme-light` variables to `.theme-dark`).

### Dynamic Translation via Lingui Macros
To support localization (i18n), Twenty utilizes `@lingui/react/macro` tags (like `<Trans>`) rather than hardcoding static text nodes. This ensures all user-visible strings are automatically extractable for translations during the build pipeline.

```tsx
// packages/twenty-front/src/modules/auth/sign-in-up/components/FooterNote.tsx
import { styled } from '@linaria/react';
import { Trans } from '@lingui/react/macro';
import { themeCssVariables } from 'twenty-ui/theme-constants';

const StyledCopyContainer = styled.div`
  align-items: center;
  color: ${themeCssVariables.font.color.tertiary};
  font-size: ${themeCssVariables.font.size.sm};
  max-width: 280px;
  text-align: center;
`;

export const FooterNote = () => {
  return (
    <StyledCopyContainer>
      <Trans>By using Twenty, you agree to the</Trans>{' '}
      <a href="https://twenty.com/legal/terms" target="_blank" rel="noopener noreferrer">
        <Trans>Terms of Service</Trans>
      </a>
    </StyledCopyContainer>
  );
};
```
*Sources: [packages/twenty-front/src/modules/auth/sign-in-up/components/FooterNote.tsx:1-83]()*

---

## Summary

The Twenty frontend leverages React 18, Jotai, and Apollo Client to build a highly responsive, custom-schema-driven application. By caching the workspace database schema in a hash-checked local `metadataStoreState` atom, the client can resolve permissions, construct dynamic GraphQL requests, and customize layout views without blocking network round-trips. Furthermore, Twenty isolates parallel component states using custom `ComponentFamilyState` wrappers, designs performant zero-runtime styled boundaries through Linaria, and localizes all user interfaces via Lingui macros.
