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

# Working with Mapping Files

> How mapping validation works for a Sync pipeline, and how to work with mapping files from the command line.

A mapping file is a YAML document that tells a Sync pipeline how to transform Auth0 events into FGA tuples. For the full mapping syntax (rules, tuple templates, tuple filters, iterators, and the expression language), see [Mapping Language](https://docs.fga.dev/relationship-sync/mapping-language.md). For complete, end-to-end mappings built around specific scenarios, see [Use-Cases](https://docs.fga.dev/relationship-sync/use-cases/b2b-organizations.md). This page covers how validation works for a Sync pipeline and how to work with mapping files from the command line.

## Validating a mapping file

Validate a mapping directly in the FGA Dashboard, from the Sync pipeline's **Mappings** tab: paste the YAML into the **Mappings** field and click **Add Mappings** (or **Update Mappings**, if you're changing an existing one). Validation checks, in order:

* **YAML syntax** — malformed YAML is rejected with a line/column pointer before anything else runs.
* **Structure** — required fields, valid `action` values, and the other structural rules described in [Mapping Language](https://docs.fga.dev/relationship-sync/mapping-language.md).
* **Consistency with your selected FGA model** — every relation a tuple template writes to must actually exist on that model.

All errors across all rules are reported at once, not just the first one. See [Troubleshooting: Mapping validation errors](https://docs.fga.dev/relationship-sync/troubleshooting.md#mapping-validation-errors) for the full breakdown by category.

You can also validate a mapping locally before uploading it, using the OpenFGA CLI's `fga mapping` command group:

```bash theme={null}
fga mapping validate mapping.yaml --model-file model.fga
```

This catches the same three error categories as the dashboard. The CLI can do more than the dashboard, including running a mapping's embedded tests; see below.

## Using the CLI

Beyond validation, the same command group lets you develop and test mapping files locally, entirely offline, with no store reads or writes, before they run against live input. `fga mapping test` is the only way to actually execute a mapping's embedded `tests:` cases; the dashboard checks syntax, structure, and model consistency when you add or update a mapping, but never runs them.

> [!NOTE]
>
> This tooling is for transforming data that's still in an external system's own schema (events, webhooks, API payloads) into FGA tuples. If your data is already shaped as FGA tuples (YAML, JSON, or CSV) and you just need to load it, you don't need the mapper at all. See [Import tuples](https://docs.fga.dev/getting-started/cli.md#import-tuples) instead.

| Command | Flags | Purpose |
| - | - | - |
| `fga mapping init [file]` | `--force`, `--minimal` | Scaffold a starter mapping YAML (rules + embedded tests). Output path is positional, defaults to `mapping.yaml`. |
| `fga mapping validate <file>` | `--model-file`, `--format text\|json`, `--verbose`/`-v` | Compile the mapping; with `--model-file`, also verify every tuple template is consistent with that FGA model. |
| `fga mapping test <file>` | `--format text\|json\|junit`, `--run <substr>`, `--fail-fast`, `--output-file`/`-o`, `--verbose`/`-v`, `--no-color` | Run the mapping's embedded `tests:` cases and report pass/fail. |
| `fga mapping run <file>` | `--input`, `--writes-only`, `--format jsonl\|json`, `--aggregate`, `--continue-on-error`, `--interactive`/`-i` | Feed JSON input (stdin or `--input`) through the mapping and emit the resulting tuple operations, for piping into `fga tuple write`. |

The mapping file path is a **positional argument**, not a flag, for example `fga mapping validate mapping.yaml --model-file model.fga`.

`run`'s default output is JSONL, one operation per line. `--writes-only` emits bare tuples (no `op` field, deletes dropped) in exactly the shape `fga tuple write --file` accepts:

```bash theme={null}
cat input.jsonl | fga mapping run mapping.yaml --writes-only --format json > tuples.json
fga tuple write --store-id 01H... --file tuples.json
```

`fga tuple write` doesn't read from stdin yet, so this is a two-step handoff through a file rather than a single pipe. If stdin support ships later, this collapses into one command.

**Offline limitation.** `run` can dedupe writes/deletes and detect conflicts without a store, but delete-by-filter rules (`tuple_filters` without a fully-specified tuple) need a live FGA Read to know what they match. Offline, these are reported as a stderr warning, and under `tuple_filter_operations` in the `--format json` output, instead of being expanded into concrete deletes. Fully-specified `delete` tuples still work normally.

**Exit codes** (all four commands):

| Code | Meaning |
| - | - |
| `0` | Success |
| `1` | Runtime failure: I/O, JSON parse/eval error, write/delete conflict, or a failing test |
| `2` | Usage error, compile error, or model inconsistency |

### Piping live Auth0 events into `fga mapping run`

`fga mapping run` reads JSON Lines from stdin, but it doesn't fetch events itself. To run a mapping against real tenant activity instead of a static input file, pipe it from the [Auth0 CLI](https://github.com/auth0/auth0-cli)'s own Events API consumer:

```bash theme={null}
auth0 event-streams subscribe --json-compact | jq --unbuffered -c 'select(.event) | .event' | fga mapping run ./fga/mapping.yaml
```

Each line `auth0 event-streams subscribe --json-compact` emits is `{"type": ..., "offset": ..., "event": {...}}`. `offset` is a resume cursor for the CLI itself, not something a mapping ever reads; `type` duplicates `event.type`. The mapping's `when` and `variables` expressions run against `input.data.object...` (see [Getting Started](https://docs.fga.dev/relationship-sync/getting-started.md#understand-the-mapping)), which lives inside that inner `event` object — so `jq` strips the wrapper down to just `.event` before handing each line to the mapper. `select(.event)` also drops heartbeat lines (`offset-only`), which carry no `event` field.

For example, an `organization.member.added` event arrives from `auth0 event-streams subscribe --json-compact` as:

```json theme={null}
{"type":"organization.member.added","offset":"NTY3ODkwMTIzCg==","event":{"id":"evt_abc123","specversion":"1.0","type":"organization.member.added","source":"urn:auth0:your-tenant.us.auth0.com","time":"2026-01-15T09:30:00Z","data":{"object":{"organization":{"id":"org_acme"},"user":{"user_id":"auth0|abc123"}}}}}
```

After `select(.event) | .event`, that's what `fga mapping run` actually sees as one line of its input, and what a rule's `input.*` expressions resolve against:

```json theme={null}
{"id":"evt_abc123","specversion":"1.0","type":"organization.member.added","source":"urn:auth0:your-tenant.us.auth0.com","time":"2026-01-15T09:30:00Z","data":{"object":{"organization":{"id":"org_acme"},"user":{"user_id":"auth0|abc123"}}}}
```

This is the same shape as a `tests:` case's `input:` field (see [Embedded tests](https://docs.fga.dev/relationship-sync/mapping-language.md#embedded-tests)) and as the sample event objects in the [Events API's SSE examples](https://auth0.com/docs/customize/events/consume-events-with-events-api#example-sse-stream) — just JSON instead of YAML, and one line instead of an indented document.

`--unbuffered` matters here: without it, `jq` holds output in a buffer and only flushes once enough bytes accumulate, so tuples show up in bursts instead of as each event arrives, which defeats the point of piping a live stream.

This is for exploring a mapping against real events as they happen, not for backfilling or production use: `fga mapping run` runs entirely offline (see [Using the CLI](#using-the-cli) above), so it never writes to your store. Pair it with `--writes-only` and hand the output to `fga tuple write` to actually apply the tuples, the same as with any other input source. `--writes-only` drops deletes, so tuples that should be removed stay in your store.
