> ## 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.

# Migration: Folder structure

> Convert an existing app to the folder-based layout with attio migrate folder-structure

## Overview

Apps are now organized by folder convention under `src/app/` (see [App structure](../extensions/overview)). Apps created before this layout register their entry points in a central `app.ts` file. That still works, but it is deprecated, and the CLI can convert an existing app automatically:

```bash theme={"system"}
attio migrate folder-structure
```

The command is plan-first: it analyzes your app, prints exactly what it will do, and only writes after you confirm. If any part of the app cannot be migrated provably safely, the command prints the issues and exits without changing any files. Each blocker names the problem, the files involved, and the symbols concerned. Potential blockers include:

* a dirty git working tree
* files that fail to parse
* registrations that cannot be resolved statically, or duplicate ids
* entry points imported via namespace imports or star re-exports
* a settings `Page` that is wrapped, computed, or declared inside `app.ts` itself
* destination files that already exist

Fix what the blocker names and run the command again.

## What it migrates

* **Extensions.** Every entry point registered in `app.ts` (all seven surfaces) moves to `src/app/extensions/<id>/extension.tsx`, each default-exporting a [`defineExtension`](../extensions/overview) call. Files that imported a moved entry point are rewritten to match.
* **Workspace settings.** `src/app.settings.ts` and the registered workspace `Page` move to `src/app/settings/schema.ts` and `src/app/settings/page.tsx` as one unit, and the settings calls inside the page are rewritten to the schema-bound [`Settings` APIs](../settings/overview). Legacy settings calls *outside* the settings page are left unchanged and listed in the plan. Update those to [`Settings.getSettings`](../settings/get-settings)/[`Settings.setSettings`](../settings/set-settings) by hand.
* **Events and webhooks.** `src/events/**` and `src/webhooks/**` move to `src/app/events/**` and `src/app/webhooks/**`, preserving handler names and fixing relative imports.
* **Workflow blocks.** `src/blocks/**` moves to `src/app/blocks/**`, preserving block folders as they are.
* **Cleanup.** `app.ts` is deleted only when nothing meaningful remains in it after the migration; otherwise it stays.

The migrated files are formatted with your app's own Prettier configuration.

**It never touches** server functions (`.server.ts` files can live anywhere in `src/`).

## Migration steps

<Steps>
  <Step title="Update the attio package">
    Make sure you're on the latest CLI:

    ```bash theme={"system"}
    npm install attio@latest
    ```
  </Step>

  <Step title="Start from a clean git tree">
    The command refuses to run with uncommitted changes, so the migration is always one clean,
    revertable commit. Commit or stash your work first.
  </Step>

  <Step title="Preview the plan">
    ```bash theme={"system"}
    attio migrate folder-structure --dry-run
    ```

    This analyzes the app and prints the migration plan without writing anything: which
    extensions move where, what happens to settings, event and webhook handler moves, import
    path updates, and the full file-by-file change list.
  </Step>

  <Step title="Apply the migration">
    ```bash theme={"system"}
    attio migrate folder-structure
    ```

    The command prints the same plan and asks for confirmation before writing. Pass `--yes` to
    skip the prompt.
  </Step>

  <Step title="Review and commit">
    Review the diff, check that `attio build` (or your dev server) is happy, and commit. If
    anything went wrong during apply, all changes are rolled back automatically; the tree is
    never left half-migrated.
  </Step>
</Steps>

## Migrating by hand

You don't have to use the command: the legacy and folder-based conventions can coexist while you move entry points over one at a time (if the same extension id exists in both, the folder-based one wins). The exception is settings: defining settings both ways is a build error, so settings must move in one step. See [Using a legacy app.ts](../extensions/overview#using-a-legacy-app-ts).

### Handing the migration to an AI agent

If the migrator refuses your app and the blockers aren't practical to fix, or you can't run the command at all, the migration is mechanical enough to hand to a coding agent. The prompt below encodes the same moves and safety rules the migrator follows. Paste it as-is, then review the diff as you would any migration.

<Accordion title="Copyable migration prompt">
  ```text theme={"system"}
  Migrate this Attio app from the legacy `app.ts` registration layout to the folder-based layout,
  by hand. The automatic migrator (`attio migrate folder-structure`) refused this app. Read its
  blockers first: each names the file and symbol that needs attention. Fix what it flagged, and
  make the equivalent moves manually.

  Start from a clean, committed working tree so every change is reviewable with `git diff`.

  Target layout

  - Every extension registered in `app.ts` (`record.actions`, `record.bulkActions`,
    `record.widgets`, `object.actions`, `callRecording.{insight,summary,transcript}.textActions`)
    becomes its own folder, `src/app/extensions/<id>/extension.tsx`, default-exporting the same
    configuration wrapped in the factory:

      import {Extensions} from "attio/client"

      export default Extensions.defineExtension({
          type: "record-action", // or: bulk-record-action | record-widget | object-action |
                                 // call-recording-{insight,summary,transcript}-text-action
          // ...the original configuration object, unchanged
      })

    Sibling files in the folder are private helpers; only `extension.tsx` is discovered. The
    folder name is cosmetic; the id in the call is what matters.

  - Legacy settings (`src/app.settings.ts` plus the Page under `settings.workspace`) migrate as
    one unit, never partially:
    - `src/app/settings/schema.ts`: `export default Settings.defineWorkspaceSchema({...})`
      using `Settings.Schema.string()/number()/boolean()`, with `import {Settings} from "attio"`.
    - `src/app/settings/page.tsx`: `export default Settings.defineWorkspacePage(schema, () => ...)`,
      a zero-argument callback, with `import schema from "./schema"` and
      `import {Settings} from "attio/client"`.
    - Inside the page component only, rewrite the calls:
      `useWorkspaceSettingsForm()` -> `Settings.useForm(schema)`
      `useWorkspaceSettings()` -> `Settings.useSettings(schema)`
      `getWorkspaceSettings()` -> `Settings.getSettings(schema)`
      `getWorkspaceSetting(key)` -> `Settings.getSettings(schema, key)`
      `setWorkspaceSetting(key, value)` -> `Settings.setSettings(schema, key, value)`

  - `src/events/**` moves to `src/app/events/**` and `src/webhooks/**` to `src/app/webhooks/**`,
    contents unchanged, relative imports fixed. Keep the same filenames.

  - `src/blocks/**` moves to `src/app/blocks/**`, each block folder as-is, relative imports
    fixed. Keep the same folder names.

  Rules that keep the migration safe

  1. Never change an extension id, a block id, or an event/webhook filename; these are dispatch
     identity in production.
  2. Move code, do not rewrite it: function bodies, JSX, and server helpers stay logically
     identical.
  3. Moving an extension turns its named export into a default export. Update every importer:
     named imports become default imports; re-exports become `export {default as name} from ...`.
  4. Settings calls outside the settings page (shared hooks, helpers, server functions) stay on
     the legacy API. Do not do a global find-and-replace.
  5. Workflow block folders move but their contents are never edited.
  6. Remove each migrated registration, and any imports it leaves unused, from `app.ts`. Delete
     `app.ts` only when nothing meaningful remains AND you created files under `src/app/`.

  Verify

  - `attio build` passes and TypeScript is clean.
  - `attio migrate folder-structure --dry-run` now reports nothing left to migrate.
  - `git diff` shows file moves plus the factory wrappers and import updates, and nothing else.
    Anything the migration did not need to touch should be byte-identical.
  - Run `attio dev` and confirm every extension, settings page, block, and handler still shows
    up and works.
  ```
</Accordion>

## Flags

| Flag        | Effect                                                           |
| ----------- | ---------------------------------------------------------------- |
| `--dry-run` | Analyze and print the migration plan without writing             |
| `--yes`     | Apply a valid plan without confirmation                          |
| `--json`    | Print the migration plan as JSON (the machine-readable contract) |

`--json` cannot host an interactive prompt, so a `--json` invocation without `--yes` prints the plan and writes nothing.

<Note>
  Running bare `attio migrate` opens a picker listing the available migrations;
  `folder-structure` is one of them.
</Note>
