---
title: "Sharing on X"
description: "Web apps and games spread on X. A post works when three things happen: people see a big image, they tap and try it right away inside the X app, and they post again with their own result or score."
url: "/docs/guides/sharing/"
lang: en
index: "/llms.txt"
---
# Sharing on X

Web apps and games spread on X. A post works when three things happen: people **see** a big image, they **tap and try it**
right away inside the X app, and they **post again** with their own result or score. KUMODeck gives you the backend
pieces for that loop. Your app, your URL, your card — KUMODeck stays invisible to your users (called *players* in the API).

**Every tool is off by default.** Turn on only what you want in `kumo.config.json` → `share`
([reference](/docs/reference/config/index.md#share)). KUMODeck never changes your pages: the card tags go into your `<head>`
(you or your AI agent write them there, see [Card images and tags](#card-images-and-tags)).

## The fastest way: `kumodeck share on`

```sh
kumodeck share on          # in your project folder, after your first deploy
```

One command turns on what a post needs — `share.images` (the card title comes from your `index.html` `<title>`),
`share.links` and `share.tracking` — writes them into `kumo.config.json` and pushes it to **both** environments
(`--env production` for one). Texts and colors you already set are kept. Then put the card tags into your `<head>` once
(`kumodeck share tags` prints them, see below) and deploy. **Sign in with X stays off**; turn it on separately if you want it.
From your AI agent, the MCP tool `share_enable` does the same.

| Tool | Config | KUMODeck-hosted app | App on your own server |
|---|---|---|---|
| Card images (1200×600) | `share.images` | yes | yes (image URL on the API) |
| Card tags in your HTML | `share.tags` (card text and image) | you put them in `<head>` (`kumodeck share tags`) | you put them in `<head>` (`kumodeck share tags`) |
| Share / challenge links | `share.links` | yes | yes |
| Traffic per post | `share.tracking` | yes | yes |
| Sign in with X | `auth.providers.x` | yes | yes |
| X in-app browser helpers | `share.inAppBrowser` | yes | yes |

## Share a score or a challenge

```js
// call from a click handler (the post window must open during the click)
shareButton.onclick = () => kumo.share({ kind: 'challenge', score: best, showName: true });
```

`kumo.share()` creates a share id, adds `?ks=<id>` to the current page URL and opens X's post screen
(`https://x.com/intent/tweet`, no API key, no cost).
The `score` comes from your app, so the other side sees `verified: false` — a modified client could send any number.
If `share.links` is off, the post screen still opens, just without a share id.

On the receiving side:

```js
const challenge = await kumo.share.incoming();   // null unless the page was opened from a share link
if (challenge?.kind === 'challenge') showBeatThis(challenge.score, challenge.displayName);
```

## Card images and tags

X shows a large image when the page it links to has card tags (`twitter:card`, `twitter:image`, Open Graph). X's crawler
**does not run JavaScript**, so the tags must be in the HTML the server returns.

KUMODeck does not add anything to your pages. You (or your AI agent) put the tags in the `<head>` of your HTML:

```sh
kumodeck share tags        # prints the <meta> tags to put in <head> (--env production for the live app)
```

Copy them as printed, into the `<head>` of every page that people share (for a single-page app: `index.html`), replacing
any `og:` / `twitter:` tags already there, and deploy. Keep the image URLs as they are. Asking your agent is enough:
"Put KUMODeck's X card tags in the `<head>` of every page of this app. Get them with `kumodeck share tags` and do not change the image URLs."

- **The default card** (your title, colors and image) works everywhere, including a static site.
- **A card for each share** (the score, or challenge of a `?ks=` link) needs HTML made on each request:
  a [server-rendered app](/docs/guides/hosting/index.md#server-rendered-apps) or your own server reads
  `GET /v1/share/tags?ks=<id>` (publishable key) for that request and puts the returned tags in `<head>` — at least
  `<meta property="og:image" content="https://<your domain>/.card/<id>.png">`. When KUMODeck hosts a server-rendered app,
  the images at `/.card.png` and `/.card/<id>.png` on your domain are answered by KUMODeck before your app (custom domain
  too), but the tags are yours to write: KUMODeck never rewrites your app's HTML.
  **A static site shows the default card for every share link**; the link itself, the challenge in the page
  (`kumo.share.incoming()`) and the traffic per post still work.
- Images come from your app's own domain when KUMODeck hosts it (`/.card.png`, `/.card/<id>.png`). With a
  [custom domain](/docs/guides/hosting/index.md#custom-domains) (`play.mygame.com`), the image URLs and the share links use that
  domain — nothing in the post points to KUMODeck.

The rendered card uses your `share.images.title`, colors and optional background image. The built-in font is Latin only;
set `share.images.font` to a font file in your deployment to draw other scripts. Rendering is metered at cost like the rest
of hosting; each card is drawn once and then cached.

## Traffic per post

With `share.tracking`, the SDK reports when someone opens a share link and when they come back. Only totals are
stored per share id and day: visits, new users (first share that brought them), plays (sessions).

```sh
kumodeck share link "launch post"      # a tracked URL for your own post
kumodeck share stats --from 2026-10-01
```

The same numbers are available at `GET /v1/admin/share/stats` (and from your AI agent through MCP).

## Sign in with X

Register your own X app, paste its Client ID and Client Secret in the dashboard under **Sign-in methods** → X, and turn
on `auth.providers.x.enabled` (steps: [Your own X app](/docs/guides/auth/index.md#your-own-x-app)). X's consent screen shows your
app's name, and X bills your app directly for any API use; KUMODeck does not charge for it. Until an X app is saved,
starting Sign in with X fails with 409 `provider_not_configured`. KUMODeck asks only for `users.read tweet.read` (no
posting, no DMs) and never stores X tokens.

On some KUMODeck environments the dashboard also offers **Use without setup**, which uses KUMODeck's shared X app
instead of yours. X's consent screen then shows the shared app's neutral name, not your app's name.

Sign in with X is **free**: X does not charge for sign-in (reading the user's own profile, `/2/users/me`), so
KUMODeck counts these calls but prices them at $0. With your own X app, X would bill you directly anyway — and the same
X rule applies there.

The user's X name and avatar are **not shown to anyone** until the user opts in:

```js
await kumo.x.setProfileVisible(true);
// rows: your own list of players (each with a playerId), e.g. read from your Functions
const withX = await kumo.x.withProfiles(rows);   // row.xProfile = { username, name, avatarUrl } or null
```

## Inside X's in-app browser

Links tapped in the X app open in X's own browser, whose storage is separate from Safari / Chrome.

- `kumo.share.inAppBrowser()` returns `'x'` there. Sign-in defaults to a redirect instead of a popup.
- With `share.inAppBrowser`, `kumo.share.openInBrowser()` gives a link (valid 10 minutes, once) that continues as the
  same guest in the user's normal browser. Show it as a button or "copy link"; the link is the user's account, so
  warn them not to post it.
