> ## Documentation Index
> Fetch the complete documentation index at: https://docs.buttons.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# button.json

> The on-disk spec for a button.

Every button has a `button.json` spec in its button folder. It is the source of truth for runtime, arguments, HTTP settings, metadata, concurrency, and packaging fields.

```text theme={null}
~/.buttons/buttons/<name>/
  button.json
  main.sh | main.py | main.js
  AGENTS.md
  pressed/
```

Project-local buttons use the same shape under `.buttons/buttons/<name>/`. App buttons (created with `--app`) live under `apps/<name>/` instead — see [App buttons](#app-buttons). The agent prompt file is `AGENTS.md`; an older `AGENT.md` is still read as a fallback but never written.

## Example

```json theme={null}
{
  "schema_version": 1,
  "name": "release",
  "description": "Ship a tagged release",
  "runtime": "shell",
  "args": [
    { "name": "env", "type": "enum", "required": true, "values": ["staging", "prod"] },
    { "name": "version", "type": "string", "required": true }
  ],
  "timeout_seconds": 300,
  "output_schema": {
    "type": "object",
    "properties": {
      "url": { "type": "string" }
    }
  },
  "queue": {
    "name": "releases",
    "concurrency": 1
  }
}
```

Real files always carry `runtime`, `env`, `timeout_seconds`, and `mcp_enabled` even when empty or default; the example omits the zero-value ones for brevity.

## Core fields

| Field | Meaning |
| - | - |
| `schema_version` | On-disk format counter. Currently `1`. Distinct from the packaging `version`. |
| `name` | Kebab-case button name. |
| `description` | One-line description surfaced by `buttons list`, `summary`, and the board. |
| `kind` | `""`/`"button"` for a regular button (pressed); `"app"` for an app button under `apps/` (see [App buttons](#app-buttons)). |
| `runtime` | `shell`, `bash`, `python`, or `node`. HTTP buttons (created with `--url`) carry an empty runtime and are identified by `url`; prompt-only buttons (created with `--prompt`) use `prompt`. |
| `args` | Typed press-time arguments. |
| `env` | Static environment variables injected into every press. |
| `timeout_seconds` | Default timeout for this button. |
| `mcp_enabled` | When true, the button is exposed as a tool by `buttons mcp`. |
| `created_at`, `updated_at` | UTC timestamps. |

## Code buttons

Code buttons use `runtime: shell`, `bash`, `python`, or `node`. The script lives next to `button.json` as `main.sh`, `main.py`, or `main.js`. Both shell runtimes use `main.sh`; `shell` executes it with `/bin/sh`, while `bash` resolves and executes Bash from `PATH`.

Arguments arrive as environment variables:

```text theme={null}
BUTTONS_ARG_<NAME>
```

Battery secrets arrive as `BUTTONS_BAT_<KEY>`.

## HTTP buttons

HTTP buttons are created with `--url`. They have no `main.*` file and carry an empty `runtime`; the `url` field identifies them.

| Field | Meaning |
| - | - |
| `url` | URL template created with `--url`. `{{arg}}` substitutes path/query/fragment only — never scheme or host. |
| `method` | HTTP method. Defaults to `GET`. |
| `headers` | Header templates. |
| `body` | Request body template. |
| `max_response_bytes` | Response body cap. Defaults to 10 MB for HTTP buttons. |
| `allow_private_networks` | Allows loopback/RFC1918/link-local targets when true. |
| `allowed_host` | Scheme/host lock derived from `url` at create time. Template args cannot redirect to a new host. A literal `"*"` opts out (requires `BUTTONS_ALLOW_ANY_HOST=1` at press). |

## Prompt buttons

Prompt-only buttons use `runtime: prompt` and do not have a `main.*` file. Their instruction lives in `AGENTS.md` and is returned in the `prompt` field when pressed.

Code and HTTP buttons can also carry `AGENTS.md` instructions; in that case the command runs first and the prompt is attached to the result.

## App buttons

App buttons set `kind: "app"` and live under `apps/<name>/` instead of `buttons/<name>/`. They are created by cloning a git URL or copying a local path:

```bash theme={null}
buttons create my-app --app https://github.com/owner/repo
```

They have no `main.*` file. Instead they carry a `serve` block describing how the app builds and runs:

| Field | Meaning |
| - | - |
| `type` | `"static"` (build, then serve the `output` dir) or `"dynamic"` (run a server on `$PORT`). |
| `build` | Build command, e.g. `"pnpm install && pnpm build"`. |
| `run` | Dynamic only — the long-running server command. |
| `output` | Static only — the built directory to serve, e.g. `"dist"`. |
| `port` | Dynamic only — local port. |

`CreateApp` defaults `serve` to `{ "type": "static", "output": "dist" }`.

## Arguments

Args are declared at create time:

```bash theme={null}
buttons create release \
  --arg env:enum:required:staging|prod \
  --arg version:string:required
```

Supported arg types are `string`, `int`, `bool`, and `enum`.

## Output schema

`output_schema` is optional JSON Schema for the button's JSON stdout. Drawers use it to type-check refs and auto-connect compatible fields.

```json theme={null}
{
  "output_schema": {
    "type": "object",
    "properties": {
      "summary": { "type": "string" }
    }
  }
}
```

When a button prints valid JSON to stdout, a drawer exposes it as `${step_id.output.<field>}`.

## Queues

`queue` limits concurrent presses.

```json theme={null}
{
  "queue": {
    "name": "openai",
    "concurrency": 3,
    "key": "${inputs.user_id}"
  }
}
```

Buttons sharing a queue name share the same slot pool. A `key` scopes that pool per press, so one queue can enforce a per-user or per-account limit.

## Packaging fields

Buttons published to or installed from a registry can include package metadata. The root dependency manifest is `.buttons/buttons.json`; the exact resolved versions and hashes live in `.buttons/buttons-lock.json`. `button.json` remains the runtime/package spec for one button.

| Field | Meaning |
| - | - |
| `tags` | Groups/categories exposed in registry discovery. |
| `version` | Button content version. New buttons start at `1`; registry publish auto-bumps to the next number when the current immutable version already exists. |
| `requires` | Transitive package dependencies, keyed by scoped name and valued as `"latest"` or an exact version. |
| `requires_batteries` | Battery keys the button needs. Values are never shipped. |

Example package fields:

```json theme={null}
{
  "version": "1",
  "requires": {
    "@your-desk/base": "latest",
    "@your-desk/deploy-lib": "2.0.1"
  },
  "requires_batteries": ["OPENAI_API_KEY"]
}
```

If `@your-desk/hello` is later published as `2`, `buttons status` can report it by reading `.buttons/buttons.json` and `.buttons/buttons-lock.json`. `buttons update` moves it only when the manifest requested `"latest"`.

## Triggers

A button can carry a `triggers` array so it fires automatically on cron, file-watch, or webhook events. Triggers run under `buttons serve` and are managed with `buttons trigger add|list|rm`.

## Board metadata

`pinned: true` surfaces a button as a large card at the top of `buttons board`.

## Related

* [Code buttons](/buttons/code)
* [API buttons](/buttons/http-api)
* [Arguments](/concepts/arguments)
* [Registry](/concepts/registry)
* [History](/concepts/history)
* [Workflows](/buttons/workflows)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.