---
title: "Sharing Kit"
description: "Ownable resources, share dialogs, visibility badges, and standard share actions for agent-native apps."
---

# Sharing Kit

The Sharing Kit makes user-authored resources private by default, then gives
users and agents the same controlled ways to share them: specific people, the
active organization, public links when allowed, and agent-readable resource
links.

<WireframeBlock id="doc-block-toolkit-sharing">
  <Screen
    surface="desktop"
    html={
      "<div style='min-height:540px;box-sizing:border-box;padding:26px;display:grid;grid-template-columns:1fr 320px;gap:18px'><main class='wf-card' style='display:flex;flex-direction:column;gap:14px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Q3 Forecast</h2><span class='wf-pill accent'>Org visible</span><div style='flex:1'></div><button><span data-icon='user'></span> Share</button></div><div class='wf-box' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Revenue by month</small><div style='display:flex;align-items:flex-end;gap:10px;height:120px'><div style='flex:1;height:50%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:75%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:60%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:92%;background:var(--wf-accent);border-radius:4px'></div></div></div><div style='display:flex;flex-direction:column;gap:8px'><div style='height:11px;width:70%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:90%;background:var(--wf-line);border-radius:4px'></div></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:12px'><strong>Share Q3 Forecast</strong><div style='display:flex;align-items:center;gap:8px'><div class='wf-box' style='flex:1;display:flex;align-items:center;gap:8px;padding:8px 10px'><span data-icon='mail'></span><span class='wf-muted'>Add people or teams</span></div><button class='primary'>Invite</button></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Steve Ray</strong><br/><small class='wf-muted'>steve@acme.com</small></div><small class='wf-muted'>Owner</small></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Maya Chen</strong><br/><small class='wf-muted'>maya@acme.com</small></div><span class='wf-pill'>Editor <span data-icon='chevronDown'></span></span></div><hr/><div style='display:flex;align-items:center;gap:8px'><span data-icon='user'></span><div style='flex:1'>Anyone at Acme</div><span class='wf-pill'>Can view <span data-icon='chevronDown'></span></span></div><div style='display:flex;align-items:center;gap:8px'><span data-icon='lock'></span><div style='flex:1'>Public link</div><span class='wf-muted'>Off</span></div></aside></div>"
    }
  />
</WireframeBlock>

## Pieces {#pieces}

| Piece                             | Purpose                                                                                |
| --------------------------------- | -------------------------------------------------------------------------------------- |
| `ownableColumns()`                | Adds owner, organization, visibility, and timestamps to the resource table.            |
| `createSharesTable()`             | Creates the per-resource grants table.                                                 |
| `registerShareableResource()`     | Registers the resource type so framework share actions know how to read and mutate it. |
| `<ShareButton>` / `<ShareDialog>` | Renders the standard share control and calls the share actions.                        |
| `<VisibilityBadge>`               | Shows private, shared, org, or public state in lists and headers.                      |
| `share-resource`                  | Grants a user or organization role.                                                    |
| `unshare-resource`                | Removes a grant.                                                                       |
| `list-resource-shares`            | Reads current visibility, grants, and policy.                                          |
| `set-resource-visibility`         | Changes private, org, or public visibility when policy allows it.                      |

## API {#api}

Make every shareable table ownable, register it at server startup, and guard
reads and writes with the same access helpers the UI and agent actions use.

```ts filename="server/db/schema.ts"
import { ownableColumns, createSharesTable } from "@agent-native/core/sharing";
import { registerShareableResource } from "@agent-native/core/sharing";

export const dashboards = table("dashboards", {
  id: text("id").primaryKey(),
  title: text("title").notNull(),
  ...ownableColumns(),
});

export const dashboardShares = createSharesTable(
  "dashboard_shares",
  dashboards,
);

registerShareableResource({
  type: "dashboard",
  displayName: "Dashboard",
  table: dashboards,
  sharesTable: dashboardShares,
});
```

Render the standard controls where users decide access:

```tsx
import {
  ShareButton,
  VisibilityBadge,
} from "@agent-native/toolkit/app/sharing";

<VisibilityBadge visibility={dashboard.visibility} />;
<ShareButton resourceType="dashboard" resourceId={dashboard.id} />;
```

## UX Standard {#ux-standard}

- Every resource header should show a visibility badge and a share button.
- Lists should make private vs shared state scannable.
- Public access must be an explicit policy choice, not an accidental URL leak.
- Agents use the same share actions as the UI and cannot bypass resource policy.

## UI components {#ui-components}

`@agent-native/toolkit/sharing` contains presentational sharing UI for badges,
share rows, role pickers, and base dialogs. It does not perform access checks
or mutations; core and app wrappers inject data and callbacks.

<WireframeBlock id="doc-block-toolkit-sharing-ui">
  <Screen
    surface="desktop"
    html={
      "<div style='min-height:480px;box-sizing:border-box;padding:26px;display:grid;grid-template-columns:1fr 300px;gap:16px'><main class='wf-card' style='display:flex;flex-direction:column;gap:14px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Roadmap</h2><span class='wf-pill accent'>Org visible</span><div style='flex:1'></div><button><span data-icon='user'></span> Share</button></div><small class='wf-muted'>Visibility badges</small><div style='display:flex;gap:6px'><span class='wf-pill'><span data-icon='lock'></span> Private</span><span class='wf-pill'>Shared</span><span class='wf-pill accent'>Org</span><span class='wf-pill'>Public</span></div><div style='display:flex;flex-direction:column;gap:8px'><div style='height:11px;width:88%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:72%;background:var(--wf-line);border-radius:4px'></div></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><strong>Share</strong><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Maya Chen</strong><br/><small class='wf-muted'>maya@acme.com</small></div><span class='wf-pill'>Editor <span data-icon='chevronDown'></span></span></div><div class='wf-card' style='display:flex;flex-direction:column;gap:2px;padding:6px'><div style='padding:6px 8px;border-radius:6px;background:var(--wf-accent-soft);display:flex;align-items:center;gap:8px'><span data-icon='check'></span> Editor</div><div style='padding:6px 8px'>Viewer</div><div style='padding:6px 8px'>Remove</div></div><div style='display:flex;align-items:center;gap:8px'><span data-icon='lock'></span><small class='wf-muted'>Public links disabled by policy</small></div></aside></div>"
    }
  />
</WireframeBlock>

`<ShareButton>`, `<ShareDialog>`, and `<VisibilityBadge>` are exported from
`@agent-native/toolkit/app/sharing`. They use Core client hooks, the shared
sharing actions, and the app's localized text. When building custom sharing UI,
import `useShareButtonController` and `useShareDialogController` from
`@agent-native/core/client/sharing`.

The presentational pieces in `@agent-native/toolkit/sharing` take their text
from the app. `VisibilityBadge` there accepts a `labels` object with `private`,
`org`, and `public` text. Any label you leave out falls back to the legacy
English default ("Private", "Org", "Public"), so existing callers keep their
text. `OwnershipFilter` requires labels for `all`, `mine`, and `shared`. The
`@agent-native/toolkit/app/sharing` versions always fill those labels from the
app's localized text, so use them unless you supply the copy yourself.

Moving a resource from Private to Organization in `<ShareButton>` or
`<ShareDialog>` opens a confirmation first. Nothing is saved until the user
confirms, and Cancel or Escape keeps the current access. The confirmation is UI
intent only. The share actions and their policy checks are unchanged for the
agent. For chat threads, both components refresh mounted chat lists after they
save.

Custom surfaces get the same confirmation through
`useShareButtonController().visibilityTransition` (`request`, `confirm`,
`cancel`) and render it with `ShareVisibilityConfirmDialog`. `request` returns a
promise that resolves once the change is saved. A direct change rejects with
the save's error. A held change whose confirmation fails keeps the error and
the retry in the dialog and its promise stays pending until a retry succeeds,
or rejects with `ShareVisibilityNotSavedError` when it is cancelled or
invalidated, so a held change is never reported as saved. The
older `handleVisibilityChange` is a deprecated alias for `request` and never
skips the confirmation. Requests from a closed surface are ignored, and a
pending change is dropped when the resource changes.

### Toolkit vs Core {#toolkit-vs-core}

Toolkit sharing UI renders badges, rows, base dialogs, and role pickers when
data and callbacks are injected. Core sharing code owns SQL, permissions,
request context, org membership lookups, share tables, resource registration,
access filters, visibility changes, invites, roles, and the private access
tokens that back agent-readable public surfaces.

## Shared chat history {#shared-chat-history}

`@agent-native/toolkit/chat-history` provides the presentational rail. For an app sidebar backed by Core chat ownership and lifecycle behavior, use the focused adapter at `@agent-native/toolkit/app/chat/ownership-history`:

```tsx
import {
  ownDraftRows,
  OwnershipChatHistoryRail,
  useOwnershipChatHistory,
} from "@agent-native/toolkit/app/chat/ownership-history";
import { useChatThreads } from "@agent-native/core/client/agent-chat";

const { threads, isNewThread, pinThread, renameThread, archiveThread } =
  useChatThreads(undefined, CHAT_STORAGE_KEY, null, { autoCreate: false });

const history = useOwnershipChatHistory({
  apiUrl,
  scope: { type: "workspace", id: workspaceId },
  activeId: activeThreadId,
  pinned: true,
  localMineRows: ownDraftRows({ threads, isNewThread }),
  mutations: {
    pin: pinThread,
    rename: renameThread,
    archive: archiveThread,
  },
});

<OwnershipChatHistoryRail
  {...history}
  activeId={activeThreadId}
  onNewChat={startChat}
  onSelect={selectThread}
  showNewChat={false}
/>;
```

`useOwnershipChatHistory()` requests owned and shared rows from Core before paging. It keeps owner and access basis on shared rows. When pinned grouping is enabled with `pinned: true`, it gathers the viewer's pinned chats, owned or shared, into a Pinned section. Started drafts are not inferred automatically. If the app has local lifecycle data, pass `localMineRows: ownDraftRows({ threads, isNewThread })` to include started drafts proven to belong to the current user. The rail previews five rows in each section, supports Show all and independent paging, and keeps loaded rows visible when a section request fails. Retry preserves local changes until a successful authoritative refresh can reconcile them.

Shared rows are read-only: rename and archive stay with the owner, and the adapter refuses them. A shared row's menu offers the viewer's own pin, marked Only you, and Hide from my list. Hide removes the row at once and shows a toast with Undo. The adapter calls Core's `hideChatThread()` itself, so apps pass no hide mutation, and it reports a failed hide in its own toast instead of through `onMutationFailed`.

The adapter supplies localized section labels. `railLabels` may override `newChat`, `showMore`, or `showLess` individually. `onNewChat` and `onSelect` are required. Pass `activeId` to both the hook and rail. Mutation callbacks return `Promise<boolean>` so failed writes can roll back without navigating away from the active chat.

For custom layouts, use `chatThreadToHistoryItem()` to convert a Core row and `ownDraftRows()` to derive eligible started drafts. `ChatHistoryRail` remains available when an app already owns its data and lifecycle logic. See [Sharing & Privacy](/docs/sharing#shared-chats) for ownership filters and access fields.

## What's next

- [**Sharing & Privacy**](/docs/sharing) — the framework-level access model
  these components render.
- [**Security**](/docs/security) — how access filters and scoped reads keep
  shared data safe.
- [**Audit Log**](/docs/audit-log) — how visibility and grant changes get
  recorded.
- [**Collaboration Kit**](/docs/toolkit-collaboration) — the same Toolkit/Core
  split applied to realtime editing and presence.
