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

# Record tab

> Add a tab to record pages in Attio

Add a record tab by creating an extension folder under `src/app/extensions/` whose `extension.tsx` default-exports a `defineExtension` call with `type: "record-tab"`. See [App structure](./overview) for the discovery rules.

Use it for content that doesn't fit a [widget](./record-widget): tables, timelines, a dashboard about the record. For UI that isn't about one record, use a [page](../pages/overview).

<Note>
  Unlike widgets, record tabs don't need to be added by hand. Once a workspace installs your app, the
  tab appears on every record page it targets, after Attio's own tabs.
</Note>

## Example

The tab component itself can live in a sibling file inside the extension folder:

```tsx src/app/extensions/enrichment/extension.tsx theme={"system"}
import React from "react"
import {Extensions} from "attio/client"

import {EnrichmentTab} from "./enrichment-tab"

export default Extensions.defineExtension({
  type: "record-tab",
  id: "enrichment",
  label: "Enrichment",
  objects: "companies", // only show on company records
  Tab: ({recordId}) => <EnrichmentTab recordId={recordId} />,
})
```

```tsx src/app/extensions/enrichment/enrichment-tab.tsx theme={"system"}
import React from "react"
import {Grid, Card, DescriptionList, EmptyState, Button} from "attio/client"

import {useEnrichmentQuery} from "./use-enrichment-query"
import {runEnrichment} from "./run-enrichment.server"

export const EnrichmentTab = ({recordId}: {recordId: string}) => {
  const enrichment = useEnrichmentQuery(recordId) // may suspend!

  if (enrichment === null) {
    return (
      <EmptyState
        title="Not enriched yet"
        description="Pull firmographic data for this company from your enrichment provider."
      >
        <Button label="Enrich now" variant="primary" onClick={() => runEnrichment(recordId)} />
      </EmptyState>
    )
  }

  return (
    <Grid maxColumns={2}>
      <Card title="Firmographics">
        <DescriptionList>
          <DescriptionList.Item label="Industry">{enrichment.industry}</DescriptionList.Item>
          <DescriptionList.Item label="Headcount">{enrichment.headcount}</DescriptionList.Item>
        </DescriptionList>
      </Card>
      <Card title="Funding">
        <DescriptionList>
          <DescriptionList.Item label="Last round">{enrichment.lastRound}</DescriptionList.Item>
          <DescriptionList.Item label="Total raised">{enrichment.totalRaised}</DescriptionList.Item>
        </DescriptionList>
      </Card>
    </Grid>
  )
}
```

## Arguments

<ParamField path="type" type="&#x22;record-tab&#x22;" required>
  Identifies this extension as a record tab.
</ParamField>

<ParamField path="id" type="string" required>
  The unique identifier for this tab. It is the last segment of the tab's URL and the value you pass
  as `tab` when [navigating](../navigation/overview) to it. Never shown to the user.

  It must not be the slug of a built-in tab. Using one fails the build with
  `RECORD_TAB_ID_IS_NATIVE_TAB_SLUG`. The reserved slugs are `overview`, `activity`, `calls`,
  `notes`, `tasks`, `emails`, `files`, `team`, `company`, `companies`, `associated_people` and
  `associated_users`.
</ParamField>

<ParamField path="label" type="string" required>
  The label shown in the record page tab bar.
</ParamField>

<ParamField path="Tab" type="(props: { recordId: string, object: ObjectSlug }) => React.JSX.Element" required>
  A React component that renders the contents of the tab.

  It can suspend while loading data. Return an [`EmptyState`](../components/empty-state) as the
  only child when there is nothing to show yet.
</ParamField>

<ParamField path="objects" type="ObjectSlug | Array<ObjectSlug>">
  A single slug or an array of slugs selecting which objects the tab appears on.

  Defaults to `undefined`, which shows the tab on ***all*** objects.

  The built-in slugs are

  * [`"companies"`](../../docs/standard-objects/standard-objects-companies)
  * [`"people"`](../../docs/standard-objects/standard-objects-people)
  * [`"deals"`](../../docs/standard-objects/standard-objects-deals)
  * [`"users"`](../../docs/standard-objects/standard-objects-users)
  * [`"workspaces"`](../../docs/standard-objects/standard-objects-workspaces)
</ParamField>

## Where the tab appears

After install, the tab shows on every record page matching `objects`, placed after Attio's built-in tabs. Workspace admins can remove it, add it back and reorder it through "Configure page" on the record page, the same editor they use for the built-in tabs. You don't need to document any of that for your users: it works like every other tab.

## Deep links

Every record tab has a stable URL:

```text theme={"system"}
https://app.attio.com/{workspaceSlug}/{objectSlug}/{recordId}/{appSlug}/{tabId}
```

Loading that URL cold lands directly on your tab. To build one from inside your app, use [`Destinations.record`](../navigation/overview) with the `tab` option rather than assembling the path yourself.

## Lifecycle

Tabs follow your app's versions. Publishing a version that no longer defines a tab removes it from every workspace on update, including any position an admin gave it. If a later version brings the tab back, it is created fresh at the end of the tab bar. Placement is not preserved across removal, so don't ship a version without a tab you intend to keep.
