---
title: Privacy and sharing
description: 'Every app is private by default: one owner, one dataset, and you decide who else gets in and what they can do.'
sidebar:
  label: Privacy and sharing
  order: 5
---

## What you can do

**Private by default.** A new app has one owner and one shared dataset. Sign in from your phone or another computer to open it securely from anywhere. Nobody else can open it until you invite them or make it public.

**Invite people.** Share the app with specific people and pick what each can do: edit it, use it, or just view it. Change or revoke someone's access at any time. Need more control? [Contact us](https://usecharming.com/contact-us).

**Make it Public, deliberately.** A separate, explicit step lets anyone with the URL open the live App Viewer with no sign-in. Public visitors can read but cannot change data. If you also want anyone who signs in to Charming, not just people you invited, to read and write the App's data, choose that wider general access level. Turn either off again at any time.

**Choose Template and Listed separately.** Template lets people who can already view the source App make an independent copy. Listed adds public discovery and copying without granting access to the live App or its data. Listed always includes Template. See [Templates](templates) for listing content and copy behavior.

## How access works

Every app sits at one of three **general access** levels, from narrowest to widest:

1. **Invited people only.** Nobody else can open the app.
2. **Everyone with the link can view.** No sign-in needed; visitors can look but not change data.
3. **Everyone can view, signed-in people can edit.** Level 2, plus: anyone who signs in (not just people you invited) can also read and write app data.

Invites are additive on top of whichever level you pick: an invited `viewer`, `end-user`, or `collaborator` keeps their access even at "Invited people only." An App set up before these three levels existed may sit outside all three, such as signed-in access without a Public link. The owner is asked to pick one of the three the next time they open **Sharing** settings.

```mermaid
flowchart TB
    A["Your app starts at<br/>Invited people only"]

    A -->|"Turn on public link"| P["Anyone can view,<br/>no sign-in"]
    P -->|"Also let signed-in<br/>people write"| S["Signed-in people can<br/>also read + write data"]
    A -->|"Invite someone"| R["Choose access level"]

    R -->|"viewer"| V["View data"]
    R -->|"end-user"| E["Save data"]
    R -->|"collaborator"| C["Edit code"]

    V -.and.-> E
    E -.and.-> C

    classDef start fill:#e8611c,color:#fff,stroke:#e8611c;
    classDef pick fill:#fff7f1,color:#26251f,stroke:#e8611c;
    classDef public fill:#fdfdfb,color:#a8440f,stroke:#e8611c,stroke-width:1.5px,stroke-dasharray:5 4;
    classDef look fill:#f9cfa2,color:#26251f,stroke:#e8611c,stroke-width:1px;
    classDef use fill:#f29d52,color:#26251f,stroke:#a8440f,stroke-width:1.5px;
    classDef build fill:#a8440f,color:#fff,stroke:#f2762c,stroke-width:2px,font-weight:bold;

    class A start;
    class R pick;
    class P public;
    class S use;
    class V look;
    class E use;
    class C build;
```

The owner or a team admin can invite people, change what they can do, or change the general access level. The Public link stays view-only on its own. Signed-in write access, an end-user invitation, or a collaborator invitation grants write access.

Public, Template, and Listed answer different questions:

| Setting      | What it grants                                                                  | What it does not grant                                                |
| ------------ | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| **Public**   | Anyone with the link can open the live hosted App Viewer as a read-only viewer. | Source editing, data writes, Template copying, or public discovery.   |
| **Template** | Existing readers of the source App can make an independent copy.                | Access for strangers, a public directory entry, or Public App access. |
| **Listed**   | Anyone can discover the Template and make a copy. Listed implies Template.      | Access to the live source App or its data.                            |

## Technical

[Account Home](https://charm.ing/) lists the Apps you own and the ones shared with you. To change or revoke access without an agent, open the App's **App settings**, then choose **Sharing**. Use the separate **Template** section for Template state, listing content, and the starter prompt.

### Copy this prompt for your agent

```text
Set up sharing for my Charming app. Read
https://charm.ing/docs/capabilities/privacy-and-sharing.md first.
Suggest who it makes sense to share it with and what each person
should be able to do, then ask me to reply with email addresses.
```

### How an agent performs this job

Use [`share_app`](/docs/technical-reference/mcp/tools/share_app) to invite someone, [`list_app_shares`](/docs/technical-reference/mcp/tools/list_app_shares) to inspect access, and [`unshare_app`](/docs/technical-reference/mcp/tools/unshare_app) to revoke it. The tool pages define inputs and errors.

Use [`set_public`](/docs/technical-reference/mcp/tools/set_public) or [`unset_public`](/docs/technical-reference/mcp/tools/unset_public) to change Public access. These tools only change the live App Viewer link. They do not change Template or Listed. An anonymous App must be [claimed](/docs/technical-reference/authentication#claim-an-anonymous-app) first.

### The contract

Access is enforced server-side: every route declares whether it's read-only, and the platform denies a role that can't attempt an op regardless of what the app's code does. Roles stack in one direction only. `collaborator` can do everything `end-user` can, which can do everything `viewer` can:

- **Owner.** Full control: edit source, run any op, share, unshare, delete, make Public, edit Template settings, and transfer.
- **`collaborator`.** Open, edit source, run the App, and edit Template settings, short of sharing, deleting, or transferring it.
- **`end-user`.** Open the app and run it, including writes to its data, but can't see or change the app's source.
- **`viewer`.** Open and run read-only operations only. A well-built app hides or disables its write actions for a viewer instead of letting them fail.
- **Public (no login).** Same access as `viewer`, and only after the owner or a team admin turns it on. Letting signed-in visitors write without an invite needs the widest general access level: see [How access works](#how-access-works).

### Team Apps and ownership transfer

Create a team with `POST /api/v1/teams`, then manage its members in **Account settings → Teams**. An MCP agent can create a team-owned App by passing `team_id` to [`create_app`](/docs/technical-reference/mcp/tools/create_app). Only a team owner or admin can create an App there. Omitting `team_id` creates a personal App.

Team owners and admins set defaults under **Team settings → Apps**, or with `GET` and `PATCH /api/v1/teams/{teamId}/app-defaults`. The defaults choose one general access level and whether Template starts enabled. With no saved defaults, new team Apps start at **Invited people only** with Template off. Template-on never means Listed.

Charming snapshots those defaults when it creates an App in the team or transfers a personal App into it. Changing the team defaults does not alter existing team Apps, and updating an existing App does not reapply them. A transfer into a team can therefore change general access, turn Template on or off, and unpublish Listed when the destination default turns Template off. A transfer from a team to your personal account keeps the App's current access and Template state.

Move an App with `POST /api/v1/apps/{appId}/transfer`. A transfer changes its owner and friendly URL; the old live App URL redirects to the new one. Its storage, secrets, source, and saved Template listing draft move with it. Existing direct App members, pending invitations, and active single-use links stay by default. If the transfer sets `keepExistingAppMembers: false`, Charming deletes direct grants and pending invitations and revokes active single-use links. Team membership access follows the owner: destination team members gain their team role, while a move to your personal account leaves only you with owner-level management unless a direct grant remains.

### Building a role-aware UI

Use `window.charming.viewer.can(op)` to hide write actions from read-only viewers, after checking `window.charming.user`. An anonymous visitor on an app open to signed-in editors also reads `can(op) === false`, and hiding the button there is what stops them ever reaching the sign-in. Hide the action only for a caller who is signed in and still refused, or where `charming.login.available` is `false`; otherwise leave it live and route the click through `withUser` below. The server still enforces access. See the [design prompt](/docs/prompts/design-an-app#access) for the build rules and the [Browser runtime API](/docs/technical-reference/browser-runtime#access-and-identity) for `viewer`, `user`, `login`, and `withUser`.

### Signing a visitor in mid-app

An app open to signed-in visitors can start the sign-in itself with `window.charming.login()`, and the caller it returns is live: `charming.user`, `charming.viewer.role`, and gated calls all reflect the new session with no reload.

Wrap the gated action in `window.charming.withUser(action)` rather than asking someone to sign in first. It runs the action, signs them in only if the action turns out to need it, and runs it again with the payload the closure captured, so a visitor's half-finished work is saved rather than thrown away. Wrap the call, not the whole click handler: `withUser` re-runs the closure verbatim, so anything else inside it happens twice.

```js
try {
  const saved = await charming.withUser(() => api.saveScore({ score }));
  if (saved === null) status.textContent = 'Not saved. Sign in when you are ready.';
} catch (err) {
  if (err.name !== 'CharmingLoginError' || err.reason !== 'popup-blocked') throw err;
  signInButton.hidden = false; // its own click handler calls charming.login()
}
```

`withUser` resolves `null` only when the visitor closes the sign-in window, so have the wrapped op return something other than `null` — otherwise a save that worked reports itself as a cancel.

`withUser` retries around two error kinds. `sign_in_required` (401) is the server's answer to a gated call from a visitor with no account. `forbidden` is the runtime's own viewer gate, which refuses a write before any request leaves the page. An anonymous visitor hits `forbidden` first, so branch on both if you handle this by hand rather than through `withUser`.

A browser can refuse the sign-in popup instead of opening it — `login()` then rejects with a `CharmingLoginError` carrying `reason: 'popup-blocked'`, not the `null` a cancel resolves with. `withUser`'s own retry is the likely place to hit this: its `login()` call happens after the wrapped action has already failed once, so the popup no longer opens inside the click that started it.

Inside a chat-host embed, `charming.login.available` is `false`: `withUser` opens no popup and rethrows the refusal the action raised. Check that flag before you offer the gated action at all, and render a link to `charming.login.canonicalUrl` in its place. See [Gate an action behind sign-in](../guides/gate-an-action-behind-sign-in) for the full pattern, including how to recover from a blocked popup.

### Limits, access, and deletion

- **Sharing requires a claimed app.** An anonymous app has no owner yet, so `share_app` and `set_public` fail until you claim it.
- **Ownership can't be shared away.** Sharing, revoking, deleting, transferring, and making an app public or private stay owner-only regardless of anyone else's role.
- **Public is read-only on the main URL.** Anonymous visitors can read the App's shared data but cannot run mutating operations. A [confirmed custom domain](custom-domains) uses a separate per-visitor storage policy. Turning Public off does not delete existing App data.
- **Revoking access is immediate for new requests.** An already-open page can keep working on its short-lived credentials for a short window after you revoke.

## Related

- [Gate an action behind sign-in](../guides/gate-an-action-behind-sign-in): the full mid-app sign-in pattern, including a blocked popup
- [Templates](templates): let existing App readers copy, or publish a Listed Template for anyone
- [Custom domains](custom-domains): give public visitors their own private data instead of one shared pool
- [Data storage](data-storage)
- [Connect to any AI agent](connect-any-agent): roles and access apply no matter which agent connects to the app
- [Browser runtime API](../technical-reference/browser-runtime)
- [How Charming works](../concepts/how-charming-works)
- [Docs home](..)
- [llms-full.txt](https://charm.ing/docs/llms-full.txt)

Found a bug or need a feature? [Tell us](/docs/capabilities/feedback) with `submit_feedback` or `POST /app/{id}/feedback`. Your feedback shapes what we build next.
