Skip to main content

Overview

Apps are now organized by folder convention under src/app/ (see App structure). 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:
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 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. Legacy settings calls outside the settings page are left unchanged and listed in the plan. Update those to Settings.getSettings/Settings.setSettings 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

1

Update the attio package

Make sure you’re on the latest CLI:
2

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

Preview the plan

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

Apply the migration

The command prints the same plan and asks for confirmation before writing. Pass --yes to skip the prompt.
5

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.

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.

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.

Flags

--json cannot host an interactive prompt, so a --json invocation without --yes prints the plan and writes nothing.
Running bare attio migrate opens a picker listing the available migrations; folder-structure is one of them.