- A Dashboard page under your app’s own URL, laid out with
Grid,CardandStack. - An
EmptyStatefor workspaces with no data yet. - A Health record tab on deal pages.
- Links between the page, the tab and records with
AttioLinkandnavigate.
1. Scaffold a page
A page is a folder undersrc/app/pages/ with a page.tsx that default-exports Extensions.definePage. The folder name is the URL slug.
src/app/pages/dashboard/page.tsx
attio dev, install the app in your development workspace, and the app appears in the Apps section of the sidebar. Click it and you’re on /{workspaceSlug}/apps/{appSlug}/dashboard.
2. Lay out the dashboard
A dashboard is a grid of cards.Grid handles the columns and collapses them as the window narrows. Card gives each metric a bordered box. Stack arranges what’s inside.
src/app/pages/dashboard/page.tsx
minColumnWidth="small" because each holds a single value; the default "medium" would give them more room than they need. And the table card sits outside the grid, as a sibling, because there are no column spans: anything full-width goes next to the grid, not in it.
Page suspends while useDealHealth loads and Attio shows the page skeleton in the meantime. You don’t need a loading state of your own.
3. Handle the empty case
A fresh install has no deals to report on. Return anEmptyState as the only child of the page and it fills the surface with a centred title, description and actions. Anywhere else in the tree it doesn’t render, so it has to be the whole page.
src/app/pages/dashboard/page.tsx
4. Add a record tab
The dashboard shows health across all deals. For one deal, a record tab is the right surface: it appears on every deal page after install, gets the full width below the tab bar, and admins can reorder it with “Configure page” like any built-in tab.src/app/extensions/health/extension.tsx
src/app/extensions/health/health-tab.tsx
Grid and Card work here as on the page: the grid collapses on the width of its container, so it lays out correctly whether the record page is wide or the sidebar is open.
The tab’s URL is /{workspaceSlug}/deals/{recordId}/{appSlug}/health. Pick id carefully: it’s part of that URL, and it can’t be one of Attio’s built-in tab slugs (overview, activity, notes and the rest; the reference lists them all).
5. Link the surfaces together
The dashboard’s at-risk table should link to each deal, and ideally straight to its Health tab.AttioLink renders a real link, so cmd-click and copy link work.
src/app/pages/dashboard/at-risk-deals.tsx
tab: "health" is your record tab id. The same option takes built-in slugs too ("activity", "notes"). Both appPage slugs and tab autocomplete against the pages and tabs the CLI found in your app, with no setup; see typed slugs.
Going the other way, a link from the tab back to the dashboard is one line:
AttioLink shows plain text and navigate logs to the console. A record that no longer exists lands on Attio’s normal “not found” page, same as a stale bookmark.
What you’ve built
src/app/pages/dashboard/page.tsx, a page in the sidebar at/apps/{appSlug}/dashboard, laid out withGrid,CardandStack, with anEmptyStatefor new installs.src/app/extensions/health/extension.tsx, a record tab on deal pages.- Links between them with
AttioLink, and anavigatecall to settings.
- Add a second page and the app row in the sidebar expands to list both. See Pages.
- Use
variant: "centered"ondefinePagefor reading-heavy pages. - Fetch real data with
useQueryand server functions.