---
title: Template Schema Reference
description: Field-by-field reference for earl template HCL
icon: Code
---

Templates are `version = 1` HCL files with provider attributes and one or more `command` blocks.

## Root Fields

| Field              | Type       | Required | Description                                      |
| ------------------ | ---------- | -------- | ------------------------------------------------ |
| `version`          | `integer`  | Yes      | Must be `1`.                                     |
| `provider`         | `string`   | Yes      | Namespace for commands (e.g. `github`).          |
| `categories`       | `string[]` | No       | Provider-level categories.                       |
| `command "<name>"` | `block`    | Yes      | One or more command blocks keyed by command name. |

## Command Fields

| Field         | Type          | Required | Description                                 |
| ------------- | ------------- | -------- | ------------------------------------------- |
| `title`       | `string`      | Yes      | Human-readable title.                       |
| `summary`     | `string`      | Yes      | One-line summary.                           |
| `description` | `string`      | Yes      | Detailed markdown description.              |
| `categories`  | `string[]`    | No       | Command-level categories.                   |
| `annotations` | `object`      | Yes      | Mode and secret declarations.               |
| `param`       | `block`       | No       | Repeatable typed input parameters.          |
| `operation`   | `block`       | Yes      | Protocol-specific execution definition.     |
| `result`      | `block`       | Yes      | Output decoding, extraction, and rendering. |

## `annotations`

| Field     | Type              | Required | Description                                     |
| --------- | ----------------- | -------- | ----------------------------------------------- |
| `mode`    | `read` \| `write` | Yes      | Safety mode used by CLI/MCP confirmation flows. |
| `secrets` | `string[]`        | No       | Secret keys this command is allowed to access.  |

## `param`

```hcl
param "query" {
  type = "string"
  required = true
  description = "GitHub search query"
}
```

| Field         | Type      | Required | Description                                                             |
| ------------- | --------- | -------- | ----------------------------------------------------------------------- |
| `name`        | `string`  | Yes      | Parameter name (from the block label).                                  |
| `type`        | `string`  | Yes      | `string`, `integer`, `number`, `boolean`, `null`, `array`, or `object`. |
| `required`    | `boolean` | No       | Whether the argument must be provided. Default `false`.                 |
| `default`     | `any`     | No       | Default value when not provided.                                        |
| `description` | `string`  | No       | Human-readable description.                                             |

## `operation`

`operation.protocol` selects the operation shape: `http`, `graphql`, `grpc`, or `cli`.

### HTTP

```hcl
operation {
  protocol = "http"
  method = "GET"
  url = "https://api.example.com/items"
  path = "{{ args.path_suffix }}"           # optional, appended to url
  query = { q = "{{ args.query }}" }        # optional
  headers = { Accept = "application/json" } # optional
  cookies = { session = "{{ args.sid }}" }  # optional

  auth { ... }      # optional
  body { ... }      # optional
  transport { ... } # optional
}
```

### GraphQL

```hcl
operation {
  protocol = "graphql"
  method = "POST"                           # optional, defaults to POST
  url = "https://api.example.com/graphql"
  headers = { Accept = "application/json" } # optional

  auth { ... } # optional

  graphql {
    query = "query User($id: ID!) { user(id: $id) { login } }"
    operation_name = "User"                 # optional
    variables = {                           # optional
      id = "{{ args.user_id }}"
    }
  }

  transport { ... } # optional
}
```

### gRPC

```hcl
operation {
  protocol = "grpc"
  url = "http://127.0.0.1:50051"
  headers = { x-request-id = "{{ args.request_id }}" } # optional

  auth { ... } # optional

  grpc {
    service = "grpc.health.v1.Health" # fully-qualified service name
    method = "Check"                   # RPC method name
    body = { service = "" }            # optional, default {}
    # descriptor_set_file = "./descriptors/health.fds.bin" # optional
  }

  transport { ... } # optional
}
```

`descriptor_set_file` is optional. If omitted, `earl` uses server reflection.
`transport.proxy_profile` and `transport.tls.min_version` are currently unsupported for `grpc`.

### CLI

```hcl
operation {
  protocol = "cli"

  cli {
    command = "cargo"
    args = ["--version", "{{ args.flag }}"]  # optional
    env = { CARGO_TERM_COLOR = "always" }    # optional
    cwd = "{{ args.cwd }}"                   # optional
    stdin = "{{ args.input }}"               # optional
  }

  transport { ... } # optional
}
```

The `command` must be listed in [`network.allow_cli_commands`](/docs/configuration#cli-command-allowlist).

## `auth`

| Kind             | Fields                                                   |
| ---------------- | -------------------------------------------------------- |
| `none`           | (none)                                                   |
| `api_key`        | `location` (`header`/`query`/`cookie`), `name`, `secret` |
| `bearer`         | `secret`                                                 |
| `basic`          | `username`, `password_secret`                            |
| `oauth2_profile` | `profile` (must match a config profile name)             |

For `grpc`, metadata is header-based. `api_key.location` must be `header`.

```hcl
# Bearer token
auth {
  kind = "bearer"
  secret = "github.token"
}

# API key in a header
auth {
  kind = "api_key"
  location = "header"
  name = "X-API-Key"
  secret = "service.api_key"
}

# OAuth2 profile
auth {
  kind = "oauth2_profile"
  profile = "my_provider"
}
```

## `body`

| Kind               | Fields                             | Description                     |
| ------------------ | ---------------------------------- | ------------------------------- |
| `none`             | (none)                             | No request body.                |
| `json`             | `value`                            | JSON object or value.           |
| `form_urlencoded`  | `fields`                           | URL-encoded form fields.        |
| `multipart`        | `parts`                            | Multipart form data.            |
| `raw_text`         | `value`, `content_type` (optional) | Raw text body.                  |
| `raw_bytes_base64` | `value`, `content_type` (optional) | Base64-encoded binary body.     |
| `file_stream`      | `path`, `content_type` (optional)  | Stream a file as the body.      |

### Multipart Parts

Each entry in `parts` supports:

| Field          | Type     | Required | Description                        |
| -------------- | -------- | -------- | ---------------------------------- |
| `name`         | `string` | Yes      | Form field name.                   |
| `value`        | `string` | No       | Text value (mutually exclusive with `bytes_base64` and `file_path`). |
| `bytes_base64` | `string` | No       | Base64-encoded binary content.     |
| `file_path`    | `string` | No       | Path to a file to upload.          |
| `content_type` | `string` | No       | MIME type for this part.           |
| `filename`     | `string` | No       | Filename sent in the part header.  |

## `transport`

Optional transport configuration for timeouts, retries, redirects, TLS, compression, and proxying.

```hcl
transport {
  timeout_ms = 30000
  max_response_bytes = 10485760
  compression = true

  redirects {
    follow = true
    max_hops = 5
  }

  retry {
    max_attempts = 3
    backoff_ms = 250
    retry_on_status = [429, 502, 503]
  }

  tls {
    min_version = "1.2"
  }

  proxy_profile = "corp"
}
```

| Field                | Type      | Default | Description                                        |
| -------------------- | --------- | ------- | -------------------------------------------------- |
| `timeout_ms`         | `u64`     | —       | Request timeout in milliseconds.                   |
| `max_response_bytes` | `u64`     | —       | Maximum response body size.                        |
| `compression`        | `boolean` | —       | Enable response decompression.                     |
| `proxy_profile`      | `string`  | —       | Named proxy from config `network.proxy_profiles`.  |

### `transport.redirects`

| Field      | Type      | Default | Description                    |
| ---------- | --------- | ------- | ------------------------------ |
| `follow`   | `boolean` | `true`  | Whether to follow redirects.   |
| `max_hops` | `usize`   | `5`     | Maximum redirect hops.         |

### `transport.retry`

| Field             | Type     | Default | Description                                |
| ----------------- | -------- | ------- | ------------------------------------------ |
| `max_attempts`    | `usize`  | `0`     | Maximum retry attempts.                    |
| `backoff_ms`      | `u64`    | `250`   | Backoff between retries in milliseconds.   |
| `retry_on_status` | `u16[]`  | `[]`    | Status codes that trigger a retry (`HTTP` for `http`/`graphql`, gRPC code numbers for `grpc`). |

### `transport.tls`

| Field         | Type     | Default | Description                      |
| ------------- | -------- | ------- | -------------------------------- |
| `min_version` | `string` | —       | Minimum TLS version (e.g. `1.2`).|

## `result`

| Field          | Type                                                      | Required | Description                                      |
| -------------- | --------------------------------------------------------- | -------- | ------------------------------------------------ |
| `decode`       | `auto` \| `json` \| `text` \| `html` \| `xml` \| `binary` | No       | Decode strategy. Default `auto`.                 |
| `extract`      | `object`                                                  | No       | Optional extraction rule (one of the shapes below). |
| `output`       | `string`                                                  | Yes      | Jinja2 output template.                          |
| `result_alias` | `string`                                                  | No       | Additional variable name for `result` in output. |

### Extract Shapes

```hcl
extract { json_pointer = "/items/0" }
```

```hcl
extract { regex = "id=([a-z0-9-]+)" }
```

```hcl
extract { css_selector = "h1" }
```

```hcl
extract { xpath = "//item/text()" }
```

## Template Variables

Available in `operation` fields (URL, headers, query, body, etc.):

| Variable     | Description                            |
| ------------ | -------------------------------------- |
| `args.*`     | Arguments passed to `earl call`.       |
| `secrets.*`  | Plaintext secret values (never leaked to output). |

Available in `result.output`:

| Variable       | Description                                |
| -------------- | ------------------------------------------ |
| `args`         | Original arguments.                        |
| `result`       | Decoded (and optionally extracted) response.|
| *(alias name)* | Same as `result`, if `result_alias` is set.|

<Callout type="warn">
  `secrets.*` is only available in operation rendering — never in `result.output`. This prevents secrets from leaking through command output.
</Callout>
