# Buttons

Telegram rich posts can carry buttons **inside the text**: in a line of a paragraph or as a row of buttons between paragraphs. Posts support exactly two button types:

- **link** - opens a URL (`url`);
- **copy** - copies a text to the reader's clipboard (`copy` / `copy_text`): promo codes, wallet addresses, commands, e-mails, phone numbers. Telegram cannot copy a URL by tapping `code`, so use a copy button for that too.

Other Telegram button types (callback, web app, login and so on) are not available in posts and are removed.

There are two ways to add buttons. Both are trusted as the author's will and pass the same cleaning and limits.

## 1. The `buttons` field (recommended)

Structured rows of buttons, no markup to get wrong. Works in every mode, and PostFrog's AI never drops them.

```json
"buttons": [
  [ { "text": "Read the original", "url": "https://example.com/news" } ],
  [ { "text": "Copy the promo code", "copy": "FROG20", "style": "success" } ]
],
"buttons_place": "end"
```

- Each inner array is one row, up to 8 buttons. Up to 40 buttons per post in total.
- A button has `text` and exactly one of `url` or `copy`. Optional `style`: `"primary"` (blue), `"success"` (green), `"danger"` (red). No style = the default look; use color only when there is a reason.
- `buttons_place`:
  - `"end"` (default) - rows at the end of the post text;
  - `"start"` - rows at the top of the post (Rich only; in Markdown they go under the post);
  - `"below_post"` - link buttons go into the keyboard under the post instead of the text. Copy buttons cannot sit under a post: they are added as a row at the end of the text (warning `copy_button_moved`).
- In Markdown mode all rows end up under the post as a keyboard.
- With `exact`, the rows are inserted into your text right away: `validate_post` returns the result in `normalized_text`, and `buttons_below_post` lists the links that go under the post.

## 2. Tags inside an `exact` text

Use tags when a button belongs **inside a sentence** or between specific paragraphs.

Inline, at the end of (or inside) a paragraph line:

```
Registration is open until Friday. <tg-button type="url" url="https://example.com/join">Sign up</tg-button>

Promo code FROG20 gives a discount. <tg-button type="copy_text" text="FROG20">Copy the promo code</tg-button>
```

A row, as a block of its own:

```
Last paragraph of the post.

<tg-button-row>
<tg-button type="url" url="https://t.me/example_channel">Subscribe</tg-button>
<tg-button type="url" url="https://example.com" style="primary">Website</tg-button>
</tg-button-row>
```

Rules for tags:

- Attributes: `type` (`url` or `copy_text`), `url` (for `url`), `text` (the value to copy, for `copy_text`), optional `style` (`primary`, `success`, `danger`). A row may have `align="left|center|right"`.
- A row: the opening `<tg-button-row>` line, **one button per line**, the closing `</tg-button-row>` line, a blank line before and after, **no blank lines inside**. More than 8 buttons in a row are split into several rows.
- **`&` in a URL inside a tag is written `&amp;`**: `url="https://example.com/?a=1&amp;b=2"`. (In the `buttons` JSON field write a normal `&`.)
- Broken or unknown button tags are removed or turned into text, with a `button_fixed` warning. Check with `validate_post`.

## Labels

- Plain text only, up to 40 visible characters. Short verbs work best: "Sign up", "Open the site", "Copy the code".
- In the `buttons` field a longer label is cut, with the warning `button_label_truncated`; a label over 200 characters is an error (HTTP 400 `invalid_request`, path `items.N.buttons.row.column.text`).
- Inside a `<tg-button>` tag a longer label is cut **without** a warning. Check the label in `normalized_text` of `validate_post`.
- No Markdown (`**`, `_`, `~`, `=`, `|`, backticks), no links, no HTML, no `@username` or `#hashtag` in a label: these are stripped. A plain Unicode emoji is fine.
- Keep `@channel` mentions in the post text and give the button the `https://t.me/channel` link.

## Values

- `url`: `https://` or `http://`, also `tg://` links. A bare domain (`example.com`) becomes `https://example.com`, `@name` becomes `https://t.me/name`. Up to 2,048 characters.
- `copy`: 1 to 256 characters, exactly what the reader gets in the clipboard.

## Good practice

- A button replaces a bare URL: do not repeat the same full address as text next to it.
- Be restrained: one row at the end plus at most two or three buttons inside lines is plenty for a normal post.
- Links that appear only in untrusted `material` are not turned into buttons by PostFrog's AI. If you want one, add it to `buttons` yourself.
- The author can move, recolor or remove any button later in the bot.