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

# Schema File Format

> How to write JSON schema files for the Vela CLI — file naming, field definitions, types, and validation rules.

## File naming

Each schema lives in its own `.json` file, named after the event:

```
vela/schemas/
  order.placed.json
  order.cancelled.json
  payment.failed.json
  user.signed-up.json
```

The filename is just for humans — the `eventName` field inside the file is what Vela uses to match incoming events. By convention, keep them in sync.

## File structure

```json theme={null}
{
  "eventName": "order.placed",
  "description": "Emitted when a customer completes checkout",
  "fields": [
    {
      "id": "f1",
      "name": "orderId",
      "type": "string",
      "required": true,
      "description": "Opaque order identifier",
      "validation": { "min": 1, "max": 128 }
    },
    {
      "id": "f2",
      "name": "amountCents",
      "type": "number",
      "required": true,
      "description": "Total order amount in cents",
      "validation": { "min": 0 }
    },
    {
      "id": "f3",
      "name": "currency",
      "type": "enum",
      "required": true,
      "enumValues": ["USD", "EUR", "GBP", "CAD"]
    },
    {
      "id": "f4",
      "name": "customerEmail",
      "type": "string",
      "required": false,
      "validation": { "pattern": "^[^@]+@[^@]+\\.[^@]+$" }
    },
    {
      "id": "f5",
      "name": "itemCount",
      "type": "number",
      "required": false,
      "defaultValue": 1
    }
  ],
  "metadataFields": [
    {
      "id": "m1",
      "name": "environment",
      "type": "string",
      "description": "e.g. production, staging"
    },
    {
      "id": "m2",
      "name": "traceId",
      "type": "string",
      "description": "Distributed trace identifier"
    }
  ]
}
```

## Top-level fields

| Field            | Required | Description                                                                                                   |
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `eventName`      | yes      | The event name that must match when ingesting. Dot-separated by convention: `order.placed`, `payment.failed`. |
| `description`    | no       | Human-readable description of what this event represents. Shown in the dashboard.                             |
| `fields`         | yes      | Array of field definitions. Minimum one field.                                                                |
| `metadataFields` | no       | Optional contextual fields (environment, trace ID, version). Not part of the core payload.                    |

## Field properties

| Property       | Required    | Description                                                                                                                            |
| -------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `id`           | yes         | Unique identifier for this field within the schema. Use a stable value — changing an `id` doesn't affect matching, but it's confusing. |
| `name`         | yes         | The key name in the event payload (e.g. `orderId`). Case-sensitive.                                                                    |
| `type`         | yes         | Data type — see the table below.                                                                                                       |
| `required`     | yes         | `true` → event is rejected if this field is missing. `false` → field is optional.                                                      |
| `description`  | no          | Human-readable description. Shown in the dashboard.                                                                                    |
| `defaultValue` | no          | Default value used when the field is absent and `required` is `false`.                                                                 |
| `enumValues`   | conditional | Required when `type` is `"enum"`. Array of allowed string values.                                                                      |
| `validation`   | no          | Additional validation constraints (see below).                                                                                         |

## Field types

| Type      | Description                                        | Validation options                                        |
| --------- | -------------------------------------------------- | --------------------------------------------------------- |
| `string`  | Text value                                         | `min` (min length), `max` (max length), `pattern` (regex) |
| `number`  | Integer or float                                   | `min`, `max`                                              |
| `boolean` | `true` or `false`                                  | —                                                         |
| `date`    | ISO-8601 date string (e.g. `2024-06-01T12:00:00Z`) | —                                                         |
| `enum`    | One value from a fixed set of strings              | `enumValues` array required                               |
| `object`  | Nested JSON object                                 | —                                                         |

### Validation examples

```json theme={null}
{ "type": "string", "validation": { "min": 1, "max": 64 } }
{ "type": "string", "validation": { "pattern": "^ord_[a-z0-9]+$" } }
{ "type": "number", "validation": { "min": 0, "max": 1000000 } }
{ "type": "enum",   "enumValues": ["card_declined", "insufficient_funds", "expired_card"] }
```

## Metadata fields

Metadata fields hold optional contextual data that isn't part of the core event payload — things like deployment environment, trace IDs, or client version.

They have the same `id`, `name`, `type`, and `description` properties as regular fields, but:

* They are always optional (no `required` property)
* They have no `defaultValue` or `validation`

```json theme={null}
{
  "metadataFields": [
    { "id": "m1", "name": "environment", "type": "string" },
    { "id": "m2", "name": "traceId",     "type": "string" },
    { "id": "m3", "name": "version",     "type": "string" }
  ]
}
```

When ingesting, pass metadata in the `metadata` key:

```typescript theme={null}
await ingest.ingest({
  event: 'order.placed',
  data: { orderId: 'ord_1', amountCents: 4999, currency: 'USD' },
  level: 'info',
  metadata: { environment: 'production', traceId: 'abc-123' },
});
```

## How diffing works

When you run `vela diff` or `vela push`, schemas are matched by `eventName`. For each schema:

* **No remote match** → schema will be **created**
* **Remote exists, no changes** → **skipped**
* **Remote exists, changes detected** → schema will be **updated**

Fields are compared by `name`, not `id`. You can change a field's `id` without triggering an update — only changes to `name`, `type`, `required`, `enumValues`, or `validation` count as changes.

<Note>
  When updating, the entire `fields` array replaces the remote version. Removing a field from the local file removes it from the schema. Make sure downstream consumers are updated before removing required fields.
</Note>
