> ## Documentation Index
> Fetch the complete documentation index at: https://docs.attio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# App structure

> How an app's extensions, settings, and handlers are laid out on disk

An Attio app is organized by folder convention under `src/app/`. Extensions, workspace settings, events, webhooks, and workflow blocks each live in their corresponding folders, and the CLI discovers and registers everything automatically based on the file structure.

```text theme={"system"}
src/
├── app/
│   ├── extensions/
│   │   └── send-invoice/
│   │       ├── extension.tsx        # default-exports defineExtension(...)
│   │       └── invoice-dialog.tsx   # helper used by this extension
│   ├── settings/
│   │   ├── schema.ts                # default-exports Settings.defineWorkspaceSchema(...)
│   │   └── page.tsx                 # default-exports Settings.defineWorkspacePage(...)
│   ├── events/
│   │   └── connection-added.event.ts
│   ├── webhooks/
│   │   └── prospect-updated.webhook.ts
│   └── blocks/                      # workflow blocks
│       └── my-step/
└── get-invoice-data.server.ts       # server functions can live anywhere in src/
```

## Extensions

An **extension** is a single addition your app makes to the Attio UI. Every extension lives in its own folder under `src/app/extensions/`, which must contain a file named exactly `extension.tsx` (or `extension.ts`) that directly default-exports a [`defineExtension`](./record-action) call:

```tsx src/app/extensions/show-weather-forecast/extension.tsx theme={"system"}
import {Extensions} from "attio/client"

export default Extensions.defineExtension({
  type: "record-action",
  id: "show-weather-forecast",
  label: "Show weather forecast",
  icon: "Sun",
  objects: ["people", "companies"],
  onTrigger: async ({recordId, object}) => {
    // Run code here
  },
})
```

The `type` field identifies the kind of extension and narrows the remaining configuration to the matching shape:

| `type`                                                                             | What it adds                                              |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------- |
| [`record-action`](./record-action)                                                 | An action on record pages and in the CMD+K palette        |
| [`bulk-record-action`](./bulk-record-action)                                       | An action that runs over records selected in a list       |
| [`record-widget`](./record-widget)                                                 | A React widget rendered on a record page                  |
| [`object-action`](./object-action)                                                 | An action that can be taken on an object                  |
| [`call-recording-insight-text-action`](./call-recording-insight-text-action)       | An action on text selected in a call recording insight    |
| [`call-recording-summary-text-action`](./call-recording-summary-text-action)       | An action on text selected in a call recording summary    |
| [`call-recording-transcript-text-action`](./call-recording-transcript-text-action) | An action on text selected in a call recording transcript |

### Discovery rules

* Extension folders can be nested at any depth under `src/app/extensions/`, but each folder contains at most one definition file, `extension.tsx` or `extension.ts`.
* Extensions are identified by the `id` property in the `defineExtension` call, not by the folder name. Ids must be unique across the app.
* `id` and `type` must be written as string literals, and the `defineExtension(...)` call must be the file's default export directly. Wrapping `defineExtension` in another function call, or assigning `id` and `type` to variables, is not permitted.
* Ids are lowercase kebab-case: they start with a letter, contain only lowercase letters, digits, and single hyphens, and are at most 64 characters.
* Sibling files in an extension folder are ignored by discovery; import them from `extension.tsx` like any other module.

## Settings

Workspace settings live in `src/app/settings/`: a schema file default-exporting [`Settings.defineWorkspaceSchema`](../settings/define-workspace-schema) and a page file default-exporting [`Settings.defineWorkspacePage`](../settings/define-workspace-page). By convention these are `schema.ts` and `page.tsx`. See the [Settings overview](../settings/overview).

## Events and webhooks

[Event handlers](../server/events/events) live in `src/app/events/` with an `.event.ts` suffix, and [webhook handlers](../server/webhooks/webhook-handlers) live in `src/app/webhooks/` with a `.webhook.ts` suffix.

## Server functions and workflow blocks

[Server functions](../server/server-functions) keep their `.server.ts` suffix and can live anywhere in `src/`. [Workflow blocks](../workflow-blocks/overview) live in their own folders under `src/app/blocks/`. See the workflow block [file structure](../workflows/file-structure).

## Using a legacy `app.ts`

Apps created before the folder-based layout register their entry points in a central [`app.ts`](../entry-points/app-ts) file. `app.ts` files are still supported but marked as deprecated: existing apps keep working, while new apps use the folder-based convention. You can use both the folder-based convention and the `app.ts` manifest at the same time while you migrate:

* Once `src/app/` exists, `app.ts` is optional.
* If the same extension `id` is registered in both places, the folder-based definition wins.
* Settings are the exception: defining settings both in `src/app/settings/` and via the legacy API (`app.settings.ts` or `app.ts` `settings.workspace`) is a build error. Migrate settings in one step.

To convert an existing app automatically, use [`attio migrate folder-structure`](../guides/folder-structure-migration).
