# config.yaml

> Every key in .ondaclip/config.yaml, your products' settings file: app URL, languages, voice, environment and freshness, with examples.

`.ondaclip/config.yaml` holds your product's settings. It's versioned with
your code, like a GitHub workflow, so change it through pull requests. The
studio writes it when you connect a product and reloads it after each merge.

- `version: 1` is required: it's the file format. Format 1 only grows. Keys
  added after v0.22.0 are marked **since vX** (see [the .ondaclip folder](/docs/ondaclip-folder/#versions)).
- Check it before you commit, with [config.schema.json](/docs/config.schema.json)
  (add `# yaml-language-server: $schema=https://ondaclip.com/docs/config.schema.json`
  at the top) or `validate_config` in the [MCP server](/docs/for-ai-agents/).
- An unknown key or a wrong type stops the shoot with a clear message, for
  example `"locales" must be a list, not text`.
- Write regular expressions as strings: `'@example\.com$'`.
- **Never put secrets in it.** Demo accounts are stored encrypted, outside the
  repository: by Ondaclip on Ondaclip Cloud, or in Secret Manager on your infrastructure.
- Set up before `.ondaclip/` existed? A `tutorials/` folder with `ondaclip.yml` or
  `tutorial-studio.yml` still works. Keep only one settings file per folder.

## Complete example

```yaml
version: 1

product:
  name: Hello Tasks

locales: [en-US, es-MX]

browser:
  locale: en-US
  timezoneId: America/Toronto

appEnvironment: dev
devGuard:
  envKey: TUTO_BASE_ENV
  appHostPattern: '^hello-tasks-dev\.example\.com$'

signInForm:
  path: /login
  email: testid:login-email-input
  password: testid:login-password-input
  submit: testid:login-submit-button
signedOutPattern: /login
homePath: /home

apiPattern: /api/
safeEmail: '@example\.com$'
neverShow:
  accountEmails: true

voice:
  project: hello-dev           # your infrastructure only (Ondaclip Cloud sets it)
agent:
  harness: agy

cloud:
  project: hello-dev           # your infrastructure only (Ondaclip Cloud sets it)
  appUrl: https://hello-tasks-dev.example.com
publish:                       # your infrastructure only (Ondaclip Cloud sets it)
  targets:
    dev: gs://hello-dev-tutorials/hello-tasks
freshness:
  manifest: gs://hello-dev-tutorials/hello-tasks/manifest.json

watch:
  branches: [main, dev]
```

## The product

| Key | Type | What it does |
|---|---|---|
| `version` | number | The file format. Always `1`. |
| `product.name` | text | The name the studio shows. |
| `locales` | list | The video languages: `en-US`, `fr-CA`, `es-MX`. Default `[en-US]`. |
| `browser` | group | The browser used for filming: `locale`, `timezoneId`, `colorScheme` (`light`, `dark`, `no-preference`). |
| `window` | group | The filmed window: `width` and `height` in CSS pixels (default 1440 x 900). |
| `brand` | group | The video colors: background, ring, click and text. |

## The app and its environment

| Key | Type | What it does |
|---|---|---|
| `appEnvironment` | text | `dev` (default), `staging` or `production`. The demo accounts must declare the same one. |
| `devGuard.envKey` | text | The name the demo accounts use to declare their environment, for example `TUTO_BASE_ENV`. |
| `devGuard.appHostPattern` | regex | The app host Ondaclip is allowed to film. Any other host stops the shoot. |
| `appAuth` | text | `iap` when the app is behind Identity-Aware Proxy. Leave it out for a public app. |
| `homePath` | text | The page to open after sign-in. |
| `appUrl` | text | Since v0.47.0. The app URL for a local run (cloud runs use the URL set in the studio). |
| `platform` | text | Since v0.42.0. `web` (default, a browser) or `android` (a Flutter app on an emulator). |
| `android.app` | text | Since v0.42.0. With `platform: android`: the Flutter app folder, relative to `.ondaclip/`. Ondaclip builds its release APK. If you leave it out, Ondaclip finds it. |
| `android.apk`, `android.package` | text | Since v0.42.0. A prebuilt APK and its package name, instead of a build. |
| `testIdAttribute` | text | Since v0.42.0. The second attribute used to identify elements, read after `data-ondaclip-id`: `data-testid` (default), or `flt-semantics-identifier` for a Flutter web app. |

## Sign-in

Choose one:

| Key | What it does |
|---|---|
| `signIn: none` | No sign-in: a public app, or one behind IAP. |
| `signInForm` | The sign-in form: `path`, `email`, `password`, `submit`, `signedOut`. Each field is `id:<its data-ondaclip-id>` (or `testid:`), or any CSS selector, such as `"#email"`, `"input[name=password]"` or `"button[type=submit]"`. |
| `signIn: manual` | Google sign-in only: a person signs in manually once. Doesn't work for cloud shoots. |
| `signIn: firebaseCustomToken` | Google or Microsoft sign-in through Firebase Auth: Ondaclip signs in with a signed token, including for cloud shoots. Your infrastructure only. Requires `firebase: { projectId, apiKey }`, `devGuard.firebaseProjectPattern` and a uid for each demo account. Since v0.49.0. See [a signed token](/docs/signed-token/). |

`signedOutPattern` (regex) is the URL that means "signed out".

## Safety

| Key | Type | What it does |
|---|---|---|
| `apiPattern` | regex | Requests to these URLs count as writes and are blocked unless a tutorial allows them. |
| `firebaseGuard` | `warn`, `block` or `off` | A Firebase app writes directly to Firestore, Storage and its functions, outside `apiPattern`. `warn` (default): Ondaclip lets undeclared writes through and logs them. `block`: blocks them like any other write. Declare each tutorial's writes (`writes:`, and reads that use POST in `allowedWrites`), then switch to `block`. Since v0.62.0. |
| `allowedWrites` | list | Writes every tutorial is allowed to make, for example the sign-in call. |
| `blockedHosts` | regex | Hosts that are never called during filming, for example analytics. |
| `safeEmail` | regex | Emails typed in a tutorial must match it. |
| `neverShow` | group | `texts` (list) and `accountEmails` (true): a shoot stops if any of them is visible and not hidden. `secret: sm://<project>/<secret>` (or `file` on a laptop) adds a private list that's read at shoot time and never stored in the repository or in logs. Each tutorial can add its own `neverShow` list. |
| `audiences` | list | Roles a published tutorial can target, for example `[client, vendor]`. |

`apiPattern`, `allowedWrites` and a tutorial's `writes:` are regular
expressions matched against the request **path** (`/api/orders`), never the
host. Firebase calls are also matched against host and path.

### A Firebase app

The Firebase SDK reads data with POST requests, and every callable is a
POST. List those reads in `allowedWrites` before you set
`firebaseGuard: block`:

```yaml
firebaseGuard: warn            # block once every tutorial lists its writes
allowedWrites:
  - '/google\.firestore\.v1\.Firestore/Listen/channel$'   # Firestore reads
  - '/v1/token$'                                           # the session token
  - '/(getProfile|listDeals)$'                             # callables that only read
```

A tutorial that saves data lists its writes the same way (`writes:`), for
example `'/google\.firestore\.v1\.Firestore/Write/channel$'` or the callable
it uses (`'/createDeal$'`).

### Filming a branch preview

A Firebase Hosting preview channel has a stable URL
(`<site>--<channel>-<hash>.web.app`). Put it in `cloud.appUrl` and allow its
host in `devGuard.appHostPattern` on your branch. It's the easiest way to film
new `data-ondaclip-id` attributes before they reach DEV. For the full recipe
(one guard for the live host and its previews, CORS, expiry, a run on a
branch), see
[film a branch on a preview](/docs/connect-your-product/#film-a-branch-on-a-preview).

The `# yaml-language-server: $schema=…` line at the top of a file only helps
your editor. Leave it out if your repository doesn't allow new comments.

## Voices and assistant

| Key | Type | What it does |
|---|---|---|
| `voice.project` | text | Your infrastructure only: the Google Cloud project where the voices run. On Ondaclip Cloud, Ondaclip sets it. |
| `voice.instructors` | list | The voices tutorials rotate between (each tutorial keeps one, based on its id). |
| `voice.style` | text | The style prompt for every tutorial, for example a warm, calm tone (one string, or one per language). A tutorial's own `voice.style` takes priority. |
| `agent.harness` | text | The AI assistant: `agy` (Gemini) or `claude-code` (Claude, in Model Garden). On your infrastructure, it runs in your project. |

## Cloud and publication

Keys marked **your infrastructure only** apply to your own Google Cloud
project. On Ondaclip Cloud, Ondaclip sets them, so leave them out. See
[where it runs](/docs/where-it-runs/).

| Key | Type | What it does |
|---|---|---|
| `cloud.project` | text | Your infrastructure only: the Google Cloud project where the shoots run. |
| `cloud.appUrl` | text | The URL of the app to film. |
| `cloud.region` | text | Your infrastructure only: the region where shoots run. |
| `accountsSecret` | text | Your infrastructure only: where the demo accounts are stored, `sm://<project>/<secret>`. Written by the studio. |
| `publish.targets` | group | Your infrastructure only: where videos are published, per environment: `dev: gs://…` (a bucket in your project). |
| `freshness.manifest` | text | Your infrastructure only: the published manifest to compare the app against. |
| `freshness.sharedPaths` | list | Paths where any change affects every tutorial. |
| `freshness.appPaths` | list | The app's folders in the repository, when it also contains other code. |
| `watch.branches` | list | The branches Ondaclip watches. Default: the default branch. |
| `findingsFile` | text | Where problems spotted during filming are written. |

## Demo data

Since v0.48.0.

| Key | Type | What it does |
|---|---|---|
| `demoData.reset` | text | How your team resets its demo data, for example `make demo-seed ENV=dev`. With it, tutorials that write data don't need a `cleanup`: your reset undoes their changes. Ready to film shows it, so whoever films knows when to run it. |

## Pull requests

Since v0.43.4.

| Key | Type | What it does |
|---|---|---|
| `pullRequests.mode` | text | `off` (default), `label`, `branch` or `manual`: when Ondaclip checks a pull request and posts the result on it. See [Updates and pull requests](../updates/). |
| `pullRequests.label` | text | The label that triggers a check (mode `label`). Default: `ondaclip`. |
| `pullRequests.branches` | text or list | The branch patterns to check (mode `branch`), like `ondaclip/*`. |
| `pullRequests.failOnBroken` | true or false | Fails the check when a user path no longer plays. Default: false (the check is informational only). |

## Several products in one repository

Since v0.47.0.

A repository with several apps (a monorepo with a web app, an admin panel,
two mobile apps) keeps **a single** `.ondaclip/` folder at its root. Its
`config.yaml` defines which apps Ondaclip films: Ondaclip only sees the
products you list under `products:`, no matter what else the repository
contains. Like a CI workflow, the file is yours. Write it yourself, or let
your AI agent write it using these docs and the
[Ondaclip MCP server](/docs/for-ai-agents/) (`validate_config`).

```yaml
version: 1
# Shared by every product below
locales: [en-US]
cloud: { project: my-app-dev }
voice: { project: my-app-dev, instructors: [Charon] }

products:
  dashboard:                       # the id: lowercase letters, digits and -
    name: My App Dashboard
    cloud: { appUrl: https://dashboard.my-app.dev }
    freshness: { appPaths: [apps/web/apps/dashboard, apps/web/packages] }
    tutorials: tutorials           # its tutorials (default: dashboard/tutorials)
    publication: publication.yaml  # its publication list (default: dashboard/publication.yaml)
  staff:
    name: My App Staff
    platform: android
    android: { app: ../apps/mobile/apps/staff }   # relative to .ondaclip/
    freshness: { appPaths: [apps/mobile/apps/staff] }
```

| Product key | What it does |
|---|---|
| the id (`dashboard`) | Identifies the product in the repository: `--product=dashboard` for the engine, and the option you pick when you add the product in the studio. |
| `name` | The name shown in the studio. |
| `tutorials`, `publication` | Where its tutorials and publication list live, inside `.ondaclip/`. Default: `<id>/tutorials/` and `<id>/publication.yaml`. |
| any key on this page | Overrides the shared key with the same name. Key groups (`cloud`, `voice`, `freshness`…) are merged one level deep: `voice: { instructors: [Kore] }` keeps the shared `voice.project`. |

```
.ondaclip/
  config.yaml              the shared keys and products:
  tutorials/               the tutorials of dashboard (tutorials: tutorials)
  publication.yaml
  staff/
    tutorials/             the tutorials of staff (the default place)
    publication.yaml
  output/                  what Ondaclip makes, one folder per product (not committed)
```

- **Always set `freshness.appPaths`**: a pull request is only checked for the
  products whose app folders it touches, and the agent that writes a tutorial
  only reads these folders. Without it, every pull request in the repository
  is checked for the product, and its agent reads the whole repository. List
  the app and only the packages it depends on (from its `package.json` or
  `pubspec.yaml`), not an entire `packages/` folder shared by other apps.
- **Shared or per product?** Shared: whatever is the same for every app
  (languages, `cloud.project`, `voice`, `agent`, `safeEmail`, `brand`). Per
  product: whatever identifies one app or one destination: `cloud.appUrl`,
  `devGuard.appHostPattern`, `signInForm`, `signedOutPattern`, `platform`,
  `android`, and above all `publish.targets` and `freshness.manifest`. **Each
  product publishes to its own folder** (`gs://<bucket>/dashboard`,
  `gs://<bucket>/staff` on your infrastructure; Ondaclip Cloud keeps each
  product's videos separate). Ondaclip rejects a file where two products share
  a publish target or a manifest.
- **The name**: with `products:`, each product has its own `name`, so leave
  out the top-level `product:` key.
- **Shared files**: `fixtures/` and `tutorial.config.cjs` serve every product.
  A product can have its own `<id>/fixtures/`. If `tutorial.config.cjs` needs
  to differ per product (a web sign-in, a local server), export a function of
  the product id: `module.exports = (product) => (product === "staff" ? {} : { signIn, startServer })`.
- **A new product needs**: its entry under `products:` (with its `name`,
  `appPaths`, and its own `publish` target if it publishes), its publication
  list (`<id>/publication.yaml`, with `chapters: []` until its first
  tutorial) and its tutorials folder (`<id>/tutorials/`, with a `.gitkeep`
  while it's empty).
- **Android products** (`platform: android`): Ondaclip builds the APK from
  `android.app` and films it on its emulator. `cloud.appUrl`,
  `devGuard.appHostPattern`, `signInForm`, `signedOutPattern` and the
  browser's `apiPattern` guard don't apply. Sign-in goes in the tutorials'
  `setup`, and demo data stays fictitious.
- **Every product has a name**: the studio writes `products:` starting with
  the first product (`app` by default), so a second app just adds a block.
- **A file without `products:`** describes one product (the short form, still
  valid). To add a second app, write `products:` with the first one as
  `tutorials: tutorials` and `publication: publication.yaml` (its other keys
  stay at the top as shared keys, so nothing moves), then add the second one.
  Before you merge, give the product that's already in the studio the same
  name (Settings, Source; Ondaclip v0.56.0 or later), so it keeps working the
  day the file changes.
- **In the studio**, "Add a product" lists the products in the file. Pick
  one. Each has its own demo accounts, languages, voices and videos, while the
  GitHub installation and where it runs (Ondaclip Cloud, or the same Google
  Cloud project) can be shared.
- **Check the file** before you commit, with the JSON schema
  [config.schema.json](/docs/config.schema.json) (add
  `# yaml-language-server: $schema=https://ondaclip.com/docs/config.schema.json`
  at the top for your editor) or `validate_config` in the MCP server.

Source: https://ondaclip.com/docs/configuration/
