Extensions.definePage and laid out with Grid, Card and Stack. For a worked example, see Building app pages.
File convention
Pages live undersrc/app/pages/. Each page is a folder holding a page.tsx that default-exports a definePage call:
src/app/pages/dashboard/page.tsx is served at /{workspaceSlug}/apps/{appSlug}/dashboard.
Discovery rules
- Page folders sit directly inside
src/app/pages/. Pages do not nest:src/app/pages/reports/weekly/page.tsxis a build error. - Folder names follow the same format as extension ids: lowercase kebab-case, starting with a letter, containing only lowercase letters, digits and single hyphens, at most 64 characters.
- Each folder contains exactly one definition file,
page.tsxorpage.ts. Other files in the folder are ignored by discovery; import them frompage.tsxlike any other module. - The
definePage(...)call must be the file’s default export directly.
extension.tsx. Everything else about app structure still applies.
Example
src/app/pages/dashboard/page.tsx
Sidebar
Once a workspace installs an app with at least one page, the app appears in the Apps section of the sidebar. Clicking the app row opens its first page, where pages are ordered alphabetically by slug. Apps with more than one page expand to list them. Apps without pages get no sidebar entry.Loading
Page can suspend. While it does, Attio shows the page skeleton with the real header already in place: the name comes from your page definition, so it renders before your code has booted. Add your own React.Suspense boundaries inside the page when one slow section shouldn’t hold up the rest.
Errors
IfPage throws, Attio renders an error state in place of that page. Nothing else about your app is affected: record actions, widgets, other pages and tabs keep working.
Build errors
The CLI checks pages on everyattio dev and attio build. The build fails when:
- A folder contains both
page.tsandpage.tsx. Keep exactly one. - A
page.tsxsits directly insrc/app/pages/rather than in a subfolder. - A folder name is not a valid slug (see the format above).
- A
page.tsxis nested more than one folder deep. - The file doesn’t default-export an
Extensions.definePage()call.