# Manage configuration in code

Keep a project's configuration in your repository, and change it the way you change code: review the differences, then deploy them. Everything here is also in the portal, and a change made in one place shows in the other.

There are two ways to do it:

- The [`calmlens` CLI](#the-configuration-file) reads one file per project, prints the differences and makes them.
- The [configuration API](/docs/api/configuration) and the SDKs' `uploadPresets`, `projectSettings` and `metadataFields` methods, for your own deploy scripts.

## The configuration file

Add `calmlens.config.json` to your repository. Each project has its own file; use `--config` to choose one.

```json
{
  "policies": {
    "Community rules": { "presetId": "general-community" }
  },
  "policy": "Community rules",
  "uploadPresets": {
    "profile-photos": {
      "name": "Profile photos",
      "allowedContentTypes": ["image/*"],
      "maxSizeInBytes": 10000000,
      "visibility": "public",
      "revealDecision": true,
      "allowedOrigins": ["https://example.com"]
    },
    "feedback-form": {
      "name": "Feedback form",
      "access": "anyone",
      "dailyUploadLimit": 20,
      "policy": "Community rules"
    }
  },
  "lenses": {
    "card": { "name": "Card", "transform": { "width": 640, "format": "webp" } }
  },
  "metadataFields": {
    "userId": { "title": "User", "type": "string", "icon": "user", "color": "#2563eb" }
  },
  "settings": {
    "blockUnmoderatedAssets": true,
    "optOutJurisdictions": ["EU"]
  }
}
```

A section you leave out isn't managed, so you can start with one section and add others later.

| Section          | Items are named by                                   | What the file sets                                                                                                                                                                                                                                          |
| ---------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `policies`       | The policy's name                                    | `presetId` to create it as a copy of a [built-in policy](/docs/guides/built-in-policies), `settings` for its complete settings, and `description`. A policy that exists already is matched by name, and only its `settings` and `description` are compared. |
| `policy`         | A policy's name                                      | The policy the project moderates with. It can be in `policies` or be any of the organisation's policies.                                                                                                                                                    |
| `uploadPresets`  | The preset's [key](/docs/guides/upload-presets#keys) | The preset's whole definition. Fields you leave out take their defaults. `policy` names a policy instead of giving its ID.                                                                                                                                  |
| `lenses`         | The lens's identifier                                | The lens's whole definition. `type` defaults to `image` and `active` to `true`.                                                                                                                                                                             |
| `metadataFields` | The field's ID                                       | The field's title, type, icon and colour.                                                                                                                                                                                                                   |
| `settings`       | The setting's name                                   | Only the settings you list. Others keep their values. See [the project settings object](/docs/api/configuration#the-project-settings-object).                                                                                                               |

## Plan and apply

`calmlens plan` compares the file with the project, prints the differences and changes nothing.

```bash
calmlens plan
```

```text
  + policy "Community rules"
  ~ project policy "Community rules"
  ~ upload preset "profile-photos" (allowedOrigins)
  + lens "card"

2 to create, 2 to update, 0 to delete, 3 unchanged.
```

`calmlens apply` makes those changes.

```bash
calmlens apply
```

`apply` reads the project again and sends each change with the revision it read. If someone changes an item in the portal or another deploy changes it first, that change is refused with `upload-preset/revision-conflict`, `project-settings/revision-conflict` or `policy/revision-conflict` rather than overwritten. Run `calmlens plan` to see the latest differences, then decide which to keep. `apply` stops at the first refusal, and changes made before it stay, so running it again finishes the rest.

## Delete what the file doesn't list

By default nothing is deleted. Add `--prune` to delete upload presets, lenses and metadata fields that the file's section doesn't list. Check the plan first:

```bash
calmlens plan --prune
```

A section you leave out is never pruned. Policies are never pruned, because other projects in the organisation can use them. Delete a policy in the portal or with the [policies API](/docs/api/policies#delete-policy).

## What can't be set from the file

- Through a project's key you can change only the policies no other project uses. A policy that another project uses is refused with `policy/used-by-another-project`, so change it in the portal.
- Turning on `aiMediaSearchEnabled` is billed for indexing the project's existing media, so it needs a budget you agree to. Turn it on in the portal or with [Update Project Settings](/docs/api/configuration#update-project-settings) and `searchIndexingBudgetUnits`, then list it in the file.

## Deploy from CI

Create a key for your deploys in [API keys](/portal/proj_current/api-keys) with the [Configurator role](/docs/guides/authentication#roles). It can manage this project's configuration, but not its members, API keys or connectors. A custom key needs only the write permission for each section your file lists: `policies:write`, `upload-presets:write`, `lenses:write`, `metadata:write` or `settings:write`. `plan` reads only those sections, so a key that only plans needs just the matching `:read` permissions.

Store the key as a secret, check the plan on pull requests and apply it on your main branch. For GitHub Actions:

```yaml
name: CalmLens configuration
on:
  pull_request:
    paths: [calmlens.config.json]
  push:
    branches: [main]
    paths: [calmlens.config.json]

env:
  CALMLENS_API_KEY: ${{ secrets.CALMLENS_CONFIG_API_KEY }}
  CALMLENS_PROJECT_ID: ${{ vars.CALMLENS_PROJECT_ID }}

jobs:
  configuration:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npx calmlens plan
      - if: github.event_name == 'push'
        run: npx calmlens apply
```

Use a separate project and key for each environment, each with its own file or the same one.

## From your own code

The SDKs have the same operations, for deploy scripts of your own. `uploadPresets.ensure` creates or replaces presets by key and returns them with their revisions, `projectSettings.get` and `update` read and replace the settings, and `metadataFields.ensure` creates or replaces fields by ID. The [configuration API reference](/docs/api/configuration) shows each endpoint in every SDK.
