# The tutorial file

> Every key in a tutorial file (.ondaclip/tutorials/<id>.yaml), from the demo account to the steps, narration and cleanup, with examples.

A tutorial is a single YAML file in `.ondaclip/tutorials/`. Its file name is
its id: `tutorials/track-orders.yaml` contains `id: track-orders`.

## A complete example

```yaml
id: track-orders                  # = the file name
account: client                   # the role of a demo account
title: { en-US: Track your orders, es-MX: Dar seguimiento a sus pedidos }
voice: { name: Charon }           # optional; chosen from the id otherwise
clock: "09:30"                    # fixed clock (default); "real" keeps it live

hide:                             # blurred on every screen
  - { id: hub-manager-email }

writes:                           # API writes allowed (read-only otherwise)
  - "^/commands/orders$"

setup: []                         # actions before the first step, not filmed
steps:
  - id: find-order
    title: { en-US: Find an order, es-MX: Encontrar un pedido }
    start: /orders                # optional; otherwise go on from the current screen
    shots:
      - say:
          en-US: To find an order, type a word from its name in the search box.
          es-MX: Para encontrar un pedido, escriba una palabra de su nombre en el cuadro de búsqueda.
        type: { target: { id: orders-search-input }, text: brunch }
cleanup: []                       # always runs, even after a failure
```

## File keys

| Key | Required | What it does |
|---|---|---|
| `id` | yes | The id, equal to the file name. |
| `account` | yes | The role of the demo account that signs in (configured in the studio). |
| `title` | yes | The title, in every language. |
| `steps` | yes | The video's chapters. Each has an `id`, a `title`, an optional `start` path and its `shots`. |
| `voice` | no | `{ name: <voice> }`. See [voices and languages](/docs/voices-and-languages/). |
| `clock` | no | The time the app shows: `"09:30"` by default, or `"real"` to keep it live. Dates stay the same from one shoot to the next. |
| `locales` | no | This tutorial's languages, a subset of the product's: `[en-US]` to ship a new feature in one language first. Your page shows this version in the other languages. Default: every language of the product. |
| `hide` | no | Elements blurred on every screen: `{ id }` (its `data-ondaclip-id` or `data-testid`) or `{ css }`. Applies to every match, even ones that appear later. Also works per step. |
| `before` | no | Values set before the app code runs, for example `localStorage: { theme: dark }`. |
| `writes` | no | API paths the tutorial is allowed to write to (regular expressions). Without it, the tutorial is read-only. |
| `frozenResponses` | no | `{ url: "<regex>", delay: <ms> }`: AI or external responses recorded once and then replayed, so every shoot shows the same response. |
| `microphone` | no | `{ say: { <locale>: <sentence> }, style? }`: the browser hears this sentence through its microphone, for voice-driven apps. `style` is the style prompt for that voice. |
| `neverShow` | no | Text this tutorial must never show, added to the `neverShow` list in `config.yaml`. If one is visible and not hidden, the shoot stops. |
| `replace` | no | `{ file: team.json }`: fictitious identities on the page (see below). |
| `singleShoot` | no | `true` keeps the captures between renders, for tutorials that write data. |
| `setup` | no | Actions before the first step, not filmed. |
| `cleanup` | no | Actions after the last step. They always run, even after a failure, to undo what the tutorial wrote. Required when the tutorial `writes`, unless `config.yaml` defines how your team resets its demo data (`demoData.reset`). |
| `quiz` | no | Questions after the video: `question`, `choices` (per language) and `answer` (index). |

## Shots

A shot is one sentence and one action. The sentence (`say`) sets the length
of the shot, and the action is what happens on screen while it's spoken.

```yaml
shots:
  - say: { en-US: Your orders are here. }
    show: { id: orders-table }
  - say: { en-US: Open the first one. }
    click: { id: orders-row-1 }
```

For every action and every way to target an element, see
[actions and targets](/docs/actions/).

## Fictitious identities

When the demo workspace shows real people (your team), `replace` gives them
fictitious names in the video without changing your data:

```yaml
setup:
  - run: node scripts/demo-team.cjs   # writes team.json in TUTORIAL_OUTPUT
replace: { file: team.json }
```

The file is a list, written at shoot time and never committed:

```json
[{ "email": "real@example.com", "name": "Real Name", "initials": "RN",
   "fake": { "email": "alex@example.com", "name": "Alex Martin", "initials": "AM" } }]
```

Every page shows the fictitious identity, in text as well as titles and
labels, each time the page changes. Profile photos aren't loaded. If a real
name or email is still visible on a filmed screen, the shoot stops.

For names that must never appear and that you don't want in your
repository, `config.yaml` can read a private list at shoot time:
`neverShow: { secret: sm://<project>/<secret> }` (a JSON list, or one entry
per line; your infrastructure only). It's never logged or written to the
outputs.

## Rules

- Every text viewers read or hear must exist in every language of the product.
- Emails typed in a tutorial must match `safeEmail` (fictitious addresses only).
- Keep videos short: 50 seconds at most per clip. Split long tasks into steps
  or into several tutorials.

Check a file before you commit it, with [tutorial.schema.json](/docs/tutorial.schema.json) or `validate_tutorial` in the [MCP server](/docs/for-ai-agents/). For the whole folder, see [the .ondaclip folder](/docs/ondaclip-folder/).

Source: https://ondaclip.com/docs/tutorial-format/
