> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/platform/import-export-and-promotion.md).

# Promote configuration between environments

Promote roles, groups, organizations, resources and business rules from one environment to the next with the import-export utility, and plan the rest of the promotion around it.

## What this is, and when you need it

Moving a working configuration from development to UAT to production is partly a tool and mostly a plan. The tool is the separate **`import-export` command-line utility**, which promotes five object types — **roles, groups, organizations, resources and business rules** — from one environment into another. Everything else is created again by hand at the target, and that remainder is the larger half of the job.

{% hint style="danger" %}
**The Webconsole has no object export.** **Export Objects** downloads a fixed sample file whatever you tick, and **Import Objects** does nothing at all. A downloaded `export.json` looks like a configuration backup and is not. If a runbook in your organisation says "export configuration from the Webconsole", it is describing those two screens — see [Import and export reference](/administration-guide/platform/import-export-and-promotion/import-export-reference.md#the-webconsole-export-and-import-screens).
{% endhint %}

## At a glance

|                          |                                                                                                                                                                                                   |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | The source environment's roles, groups, organizations, resources and business rules present on the target, with their relationships assigned                                                      |
| **Where**                | The `import-export` utility, run from your own workstation against each environment. Nothing in the Webconsole does this                                                                          |
| **You need first**       | Python 3.8 or later; a service account **and** an Implicit-grant OAuth client on *each* environment; the utility's `v3` branch                                                                    |
| **Takes**                | 30 to 60 minutes for the first environment pair, most of it creating the OAuth clients; 5 to 15 minutes per run after that                                                                        |
| **Reversible**           | No. An import saves each object the way the Webconsole would, relationship assignment is add-only, and approver associations are deleted and rebuilt. Export the target before you import over it |

## Before you begin

* **Python 3.8 or later**, and the utility's dependencies — `requests>=2.31.0`, `PyYAML>=6.0`, `click>=8.1.0`, `python-dateutil>=2.8.2`, `jsonschema>=4.17.0`, `pytest>=7.0.0` — installed into a virtualenv with `pip install -r requirements.txt`.
* **HTTPS reach** from the machine running the utility to each environment. The export and import halves are independent, because the intermediate state is files on disk, so they can run from different hosts — and on a segregated production network they usually must.
* **A service account login and password** on each environment, entitled to read and save the objects you are promoting.

The utility never opens a database connection. It reads and writes objects the same way the Webconsole does, so the target applies its own validation, auditing and indexing exactly as if an administrator had typed each object in.

## Promote from one environment to the next

{% stepper %}
{% step %}

### Confirm you have the `v3` branch

The repository's **`master` branch is empty of the utility** — two files, a `.gitignore` and a `README.md`. Clone the default branch and you will reasonably conclude the utility does not exist. It lives on the `v1`, `v2` and `v3` branches, and `v3` is current.

{% code overflow="wrap" %}

```bash
git rev-parse --abbrev-ref HEAD && git log -1 --oneline
```

{% endcode %}

Record that output in the change record: the utility is versioned independently of OpenIAM, and the file format changed between branches. Do **not** use `main.py version` as the check — on `v3` it still prints `OpenIAM Import/Export Utility v1.0`. `v2` covers roles and business rules only; `v1` writes files that are not safe to import. The differences are set out in [Import and export reference](/administration-guide/platform/import-export-and-promotion/import-export-reference.md#what-an-exported-artifact-looks-like).
{% endstep %}

{% step %}

### Create an OAuth client on each environment

**Access Control → Authentication Providers → oAuth Client**, on *both* the source and the target.

* **Authorization Grant Flow** must be **Implicit**.
* **Redirect URL** must match the configuration file's `redirect_uri` character for character.
* **Scopes** are picked from the list on that screen, and two entries are required: `user_name`, and your content provider's entry for the Webconsole's administrative paths — it reads `<your content provider> - /webconsole/rest/api/*` in the picker. The utility cannot authenticate without both.

{% hint style="warning" %}
The utility's own `DEPLOYMENT.md` on the `v3` branch tells you to use **Client Credentials** and to configure a `client_secret` and a `token_endpoint`. `v3` calls no token endpoint at all, so following it produces *"Authorize did not redirect: 200"* on the first command. Use **Implicit**.
{% endhint %}
{% endstep %}

{% step %}

### Write one configuration file per environment

`--config`/`-c` names the file on every invocation; without it the utility takes the first of `config.yaml`, `config.yml` or `config.json` in the working directory. Four keys are required — `api.base_url`, `api.client_id`, `api.username`, `api.password` — and a run missing any of them stops immediately and names them.

{% code title="config-uat.yaml" overflow="wrap" expandable="true" %}

```yaml
# OpenIAM import-export utility — UAT
api:
  base_url: "https://uat-openiam.example.com"
  client_id: "<CLIENT_ID>"
  username: "svc.promotion"
  password: "${OPENIAM_UAT_PASSWORD}"
  redirect_uri: "http://localhost:5000/callback"
  timeout: 60

output:
  default_path: "./exports"

logging:
  level: "INFO"
  file: "./logs/openiam-util-uat.log"
```

{% endcode %}

Keep one file per environment — `config-dev.yaml`, `config-uat.yaml`, `config-prod.yaml` — and keep the passwords out of them. Every other key, its default, and the seven sample keys that no code reads are in [Import and export reference](/administration-guide/platform/import-export-and-promotion/import-export-reference.md#the-configuration-file).

{% hint style="danger" %}
**Do not start from the repository's `examples/config.yaml`.** It is the v1 sample: it sets `client_secret` and `token_endpoint`, which `v3` never uses, and it omits `username` and `password`, which `v3` requires. Loading it dies immediately with *"Missing required configuration keys: api.username, api.password"*. Start from the repository root's `config.yaml`.

**An unset environment variable is not an error.** A value is substituted only when the *entire* string is `${NAME}`, and when the variable is unset the literal `${NAME}` is left in place and sent as the password — producing a 401 that looks exactly like a wrong password. Partial interpolation such as `base_url: "https://${HOST}/openiam"` is not expanded at all.
{% endhint %}
{% endstep %}

{% step %}

### Prove both environments before you touch data

{% code overflow="wrap" %}

```bash
python main.py --config config-dev.yaml test-connection
python main.py --config config-uat.yaml test-connection
```

{% endcode %}

Both must print `✓ Successfully connected to OpenIAM API`. A failure here is an OAuth client problem — grant flow, redirect URI or scopes — not a data problem, and no amount of retrying the export will fix it. Note the hyphen in the command name.

One failure mode looks like a permissions problem and is not: every API call carries **both** an `Authorization: Bearer` header **and** the `OPENIAM_AUTH_TOKEN` cookie, so a reverse proxy or gateway that strips cookies breaks the utility with the bearer token intact.
{% endstep %}

{% step %}

### Promote the Groovy scripts first

A resource's script travels as a `{scriptPath, scriptVersion}` reference, never as a body. Export the scripts from the source and import them into the target **before** the resources that reference them, or the reference resolves to nothing. Groovy Manager's zip export is a real, audited tool — see [What does export: Groovy scripts](#what-does-export-groovy-scripts) below.
{% endstep %}

{% step %}

### Export from the source into a dated directory

{% code overflow="wrap" %}

```bash
EXPORT_DIR="./exports/dev-to-uat-$(date +%Y%m%d_%H%M%S)"
python main.py --config config-dev.yaml export organizations  --output "$EXPORT_DIR"
python main.py --config config-dev.yaml export resources      --output "$EXPORT_DIR"
python main.py --config config-dev.yaml export groups         --output "$EXPORT_DIR"
python main.py --config config-dev.yaml export roles          --output "$EXPORT_DIR"
python main.py --config config-dev.yaml export business-rules --output "$EXPORT_DIR"
```

{% endcode %}

`--output`/`-o` is required on every export. One fresh, dated directory per run, never a shared one: filenames carry a timestamp, so a second export into the same directory adds a *second generation* of the same objects and an import applies every match in filesystem order.

{% hint style="warning" %}
**Count the files.** A [20 to 40% success rate](/administration-guide/platform/import-export-and-promotion/import-export-reference.md#partial-exports-are-expected) is documented as normal for large role exports, and the command exits successfully either way. Compare the file count in `$EXPORT_DIR/roles/` against the source environment's role count before you treat the export as complete — that count is the only check that catches a two-thirds export.

**`--dry-run` on an export does nothing.** It prints *"DRY RUN MODE - No files will be written"* and returns before contacting the API, on all five object types. It is not a preview and it does not prove the environment is reachable.
{% endhint %}
{% endstep %}

{% step %}

### Read the files before you import them

They are JSON, they are small, and they are the only artefact of the promotion you can review. Commit them to source control at this point: that gives you a diffable record of what the target was asked to become. This is where you find what the export could not resolve — an `owner` with a `null` login, an empty `policyName`, a `groovyScript` that did not become a `{scriptPath, scriptVersion}` pair.

If a file contains `objectState` or `createDate`, it came from `v1` and is not safe to import. If it contains a raw `policyId` rather than a `policyName`/`policyDefId` pair, the same.
{% endstep %}

{% step %}

### Preview on the target, then dry-run

{% code overflow="wrap" %}

```bash
python main.py --config config-uat.yaml import roles --input "$EXPORT_DIR/roles" --preview
python main.py --config config-uat.yaml import roles --input "$EXPORT_DIR/roles" --dry-run
```

{% endcode %}

Run both, because they answer different questions. `--preview` prints the per-object Create/Update/Skip/Error decision and any validation issues, then returns without writing. `--dry-run` resolves the managed system, owner and admin references and searches the target for an existing object of the same name, then stops before the save. Neither exercises the second, relationship-assigning pass, so neither warns you about a missing group or child role.

`import resources` and `import organizations` have **no `--preview`** — `--dry-run` is your only look-before-you-leap on those two.
{% endstep %}

{% step %}

### Export the target's approver associations

Do this before the import, not after. An import **deletes every existing approver association on the target object and rebuilds it from the file**, so approvers an administrator configured on the target since the last export are destroyed and the run reports success. Export the target's roles, groups and resources into a separate directory now, purely as the record you will diff against.
{% endstep %}

{% step %}

### Import in dependency order, with an explicit conflict mode

{% code overflow="wrap" %}

```bash
python main.py --config config-uat.yaml import organizations  --input "$EXPORT_DIR/organizations"  --on-conflict update
python main.py --config config-uat.yaml import resources      --input "$EXPORT_DIR/resources"      --on-conflict update
python main.py --config config-uat.yaml import groups         --input "$EXPORT_DIR/groups"         --on-conflict update
python main.py --config config-uat.yaml import roles          --input "$EXPORT_DIR/roles"          --on-conflict update
python main.py --config config-uat.yaml import business-rules --input "$EXPORT_DIR/business_rules" --on-conflict update
```

{% endcode %}

The order is **organizations → resources → groups → roles → business rules**, and it is yours to get right: there is no cross-type ordering in the tool, and a reference that cannot be resolved is a *warning*, not a failure. Business rules go last because their conditions and targets name roles, groups, resources and organizations.

Always state `--on-conflict`. The default is `skip`, and `skip` does **not** mean "do nothing" — it skips only the scalar save and still assigns groups, organizations, child roles, resources, user members and approver associations to the object it found. If what you wanted was "import only what is new", filter the input directory instead.

Each command prints `Created=… Updated=… Skipped=… Failed=…`. Record all four in the change record, and read the warnings too — `Failed` is not a measure of correctness.
{% endstep %}

{% step %}

### Rebuild the indexes and the graph, then verify in the target

Reopen a sample of the promoted objects in the Webconsole and read their policy and their entitlements back. Rebuild the 32 search indexes and the authorization graph on the target — see [Search indexes and the authorization graph](/administration-guide/reference/search-indexes.md) — then run the negative tests: an entitled user gets in, an unentitled one does not. Fix nothing mid-run; the utility caches lookup **misses** as well as hits for the length of a run, so a dependency you create in the Webconsole while a run is going is still reported as missing until you start a new run.
{% endstep %}
{% endstepper %}

## Verification

| Evidence                                                                  | Meaning                                                                                                 |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `test-connection` prints `✓ Successfully connected to OpenIAM API`        | That environment's OAuth client grant flow, redirect URI and scopes are right                           |
| The number of files in `exports/<type>/` versus the source's object count | Whether the export was complete. A 20 to 40% success rate on large role exports is documented as normal |
| `Created=… Updated=… Skipped=… Failed=…`, recorded per object type        | What the import actually did. Record all four, not just `Failed`                                        |
| The run log with no `not found` warnings                                  | Every reference in the export resolved on the target. Warnings here do **not** raise `Failed`           |
| A promoted role reopened in the Webconsole, with its policy read back     | An absent or unresolved policy falls back to policy `4000`, silently. The run will not tell you         |
| The target's approver associations, diffed against your pre-import export | An import deletes and rebuilds them. This diff is the only evidence of what was lost                    |
| `git log -1 --oneline` in the utility checkout                            | Which version of the tool produced or consumed the files                                                |
| `REINDEX` audit events, completed rather than merely started              | The target's indexes were rebuilt after the data landed                                                 |
| `EXPORT_GROOVY_SCRIPTS`, success reason *"Exported N groovy scripts"*     | The zip really contained N scripts. Compare N with the source's script count                            |
| `IMPORT_GROOVY_SCRIPTS` with `GROOVY_SCRIPTS_PARTIALLY_IMPORTED`          | Some zip entries were skipped. Diff the target against the zip                                          |
| Seven matching `OPENIAM_VERSION` rows                                     | The promoted schemas are at the version you recorded                                                    |

## When it goes wrong

| Symptom                                                                  | Inspect                                                                                                                                                                                                                                                                        | Fix                                                                                                                                                           |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The utility checkout is empty                                            | Which branch you are on                                                                                                                                                                                                                                                        | `master` is a two-file stub. The utility is on `v1`, `v2` and `v3`; use `v3`                                                                                  |
| `Missing required configuration keys: api.username, api.password`        | Whether you started from `examples/config.yaml`                                                                                                                                                                                                                                | That file is the v1 sample. Start from the repository root's `config.yaml`                                                                                    |
| `Authorize did not redirect: 200`                                        | The OAuth client's Authorization Grant Flow                                                                                                                                                                                                                                    | It must be **Implicit**. `DEPLOYMENT.md` wrongly says Client Credentials                                                                                      |
| A 401 on login even though the password is right                         | Whether the environment variable was actually exported                                                                                                                                                                                                                         | An unset variable is left as the literal `${…}` and sent as the password                                                                                      |
| 401s on every API call, credentials correct                              | Whether a proxy between you and the environment strips cookies                                                                                                                                                                                                                 | Every call carries the bearer token **and** the `OPENIAM_AUTH_TOKEN` cookie; both are required                                                                |
| Import reports `Failed=0` but half the entitlements are missing          | The run's warnings                                                                                                                                                                                                                                                             | Unresolved references are warnings, not failures. Import in dependency order, then re-run the later types                                                     |
| Approvers configured on the target disappeared after an import           | The target object's approver associations, against your pre-import export                                                                                                                                                                                                      | An import deletes every existing association before rebuilding from the file                                                                                  |
| `--on-conflict skip` still changed the target's entitlements             | The second dependency pass                                                                                                                                                                                                                                                     | `skip` skips only the scalar save. Filter the input directory instead                                                                                         |
| An entitlement removed at the source is still on the target              | Whether anything removes it                                                                                                                                                                                                                                                    | Dependency assignment is add-only. Remove it by hand at the target                                                                                            |
| A promoted role landed on the wrong password policy                      | `policyName` in the export file                                                                                                                                                                                                                                                | An absent or unresolved policy falls back to `4000`, the seeded default                                                                                       |
| The same object was imported twice with different content                | Whether the export directory holds two generations                                                                                                                                                                                                                             | Filenames are timestamped, so exports accumulate. One dated directory per run; `cleanup --days N`                                                             |
| A dependency you created mid-run is still reported missing               | Nothing — this is the per-run cache                                                                                                                                                                                                                                            | The cache holds misses as well as hits. Stop, fix, re-run                                                                                                     |
| An export flag appears to be ignored, or a runbook quotes `source_db`    | [Commands and flags](/administration-guide/platform/import-export-and-promotion/import-export-reference.md#commands-and-flags) and [the corrections](/administration-guide/platform/import-export-and-promotion/import-export-reference.md#corrections-to-older-documentation) | `--dry-run` on an export and `--managed-system` on roles do nothing; `--name` on resources matches `coorelatedName`; there is no database-to-database tool    |
| Exported configuration contains objects that do not exist in your system | Whether the file contains `HR_GRP`, role `1` "End User" and resource `0` "OPENIAM"                                                                                                                                                                                             | You used **Export Objects** and are holding the shipped sample                                                                                                |
| Objects present but invisible in search on the target                    | Whether the indexes were rebuilt                                                                                                                                                                                                                                               | Reindex — see [Search indexes](/administration-guide/reference/search-indexes.md)                                                                             |
| Memberships correct but effective access wrong after promotion           | The authorization graph                                                                                                                                                                                                                                                        | It is stale, and there is no screen that rebuilds it — [Rebuild the authorization graph](/administration-guide/operations/rebuild-the-authorization-graph.md) |
| A promoted managed system cannot connect                                 | The vault entry at the target                                                                                                                                                                                                                                                  | Secrets are not in the object row; recreate the credential at the target                                                                                      |
| A promoted OTP provider's gateway secret does not work                   | `OTP_PROVIDER_ATTR.VALUE` — it is a UUID, not the secret                                                                                                                                                                                                                       | The vault key regenerates on save. Retype the value at the target                                                                                             |
| Groovy script change deployed as a file, no effect                       | Whether that script has already been moved into the `groovy` schema by an upgrade                                                                                                                                                                                              | Once it has, the database row is authoritative and the file is not read. Import the zip instead                                                               |
| Property set on the target, behaviour unchanged                          | The target's deployment manifest for `OPENIAM_PROP_<underscored_key>`                                                                                                                                                                                                          | The container override wins and is invisible from the Webconsole                                                                                              |

## Roll it back

There is no rollback command, no comparison mode and no way to make a target match a source by removing what the source does not have. Plan the reversal before the run:

1. **Objects created by the import** are removed by deleting them in the Webconsole, in reverse dependency order — business rules, roles, groups, resources, organizations.
2. **Objects updated by the import** are restored by importing the pre-import export you took of the target, with `--on-conflict update`. That restores scalar fields; it does not remove entitlements the promotion added, because assignment is add-only.
3. **Approver associations** come back only from the pre-import export. Nothing else holds them.

If the target is a fresh environment, the cheaper rollback is to rebuild it rather than to unpick the import.

## The rest of a promotion: what the utility does not move

Sorting your configuration into these four categories, once, is most of the work. They map onto the layers in [Where settings live](/administration-guide/start/where-settings-live.md). The utility covers part of category 1 and none of the other three.

### 1. Objects: five types by utility, the rest by hand

Every managed system, connector, policy map, content provider, URI pattern, authentication provider and rule, role, group, organization, resource, page template, custom field, approver association, request category, questionnaire, IT policy, OTP provider, mailbox configuration, mail template, analytics tile, synchronization and reconciliation configuration, business rule, SoD rule set and campaign is a row with a generated id and cross-references to other rows.

**Five of them — roles, groups, organizations, resources and business rules — are covered by the utility.** Everything else has no export of any kind, so promotion means creating it again at the target, in dependency order, and keeping a written record of what you created so the environments can be compared later. The item-by-item catalogue is in [What the utility does not cover](/administration-guide/platform/import-export-and-promotion/import-export-reference.md#what-the-utility-does-not-cover).

Two practices make the hand-built remainder survivable, and they also make the utility's name-based reference resolution work:

* **Use stable, meaningful ids where the screen lets you set one.** Every shipped object does — `RES` rows such as `COUNTE_ADM_M`, mailbox templates such as `OFFICE365_MBT`, managed systems such as `SERVICE_NOW_MANAGED_SYS`, which shipped Groovy refers to by name. An object whose id you chose can be referenced identically in both environments; one with a generated id cannot.
* **Keep environment-specific values out of the object** — a host name in a property rather than hard-coded in a script.

### 2. Files on disk: deployed, not promoted through the UI

| Artefact           | What it is                                                                                                    | Takes effect                                                                                                               |
| ------------------ | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Approval workflows | BPMN definition files — 49 shipped                                                                            | On restart of the workflow service. See [Shipped approval workflows](/administration-guide/reference/shipped-workflows.md) |
| Groovy extensions  | Script files on disk — 745 shipped — **or** rows in the `groovy` schema, once an upgrade has moved them there | On restart, or immediately for stored versions                                                                             |
| Properties files   | The deployment's own properties files                                                                         | On restart                                                                                                                 |
| Migration trees    | The schema migration files, one tree per schema                                                               | When the migration tool runs                                                                                               |
| UI label overrides | A `messages_<locale>.properties` file the deployment places on disk                                           | On restart of the UI services                                                                                              |

These belong in your source control and go through your normal deployment mechanism, which puts moving them outside this guide — see the [Installation Guide](/installation/installation.md). Groovy is the exception: its zip export is a real administrative tool, so export from the source, import into the target, and diff. For everything else in this table your part is knowing that a change here needs a restart before it counts, and that no screen will tell you whether the file arrived.

### 3. Properties: promoted as values, not as files

Property values live in `PROPERTY_FILE_VALUES` and are edited on **Administration → System Configuration**. Promote them as a list of `PROPERTY_ID` and value pairs recorded in your change record, and set them on the target. Check for a container `OPENIAM_PROP_*` override on the target before you conclude a value did not take — [Where settings live](/administration-guide/start/where-settings-live.md#the-container-override-convention) explains the precedence, and the shipped manifests genuinely use it for real tuning.

Thirty keys were seeded by one release and removed by a later one, so a promotion script that sets `org.openiam.syslog.*` or `org.openiam.password.expiration.warnDays` is writing rows nothing reads, and it will not fail. The list is at [Properties removed by a later migration](/administration-guide/reference/system-properties.md#properties-removed-by-a-later-migration).

### 4. Secrets: never promoted

{% hint style="danger" %}
Secrets are held in the vault and referenced by key, not stored in the object or property row. Their keys are per-object and some are regenerated on every save:

* Mailbox configurations: `<vault path>vault.secret.mailbox.<MB_ID>.<username|password|clientId|tenantId|clientSecret|authority|clientCertificate|clientCertificatePassword>`
* OTP provider attributes: `<vault path>vault.secret.otp.<random UUID stored in the attribute row>` — the UUID changes on each save
* Legacy SMTP: `/secret/openiam/vault.secret.mail.username` and `.password`

Never copy a production secret into a test environment, and never copy a test secret forward. Create the target environment's credential at the target, set it there, and record only its identity — never its value — in the change record. A secret that has appeared in a promotion script, a ticket or a screenshot must be rotated, not restored.
{% endhint %}

### What to promote in what order

1. Files on disk and the schema migration, so the target is at the same product version. Compare the seven `OPENIAM_VERSION` values.
2. Properties, including any container overrides.
3. Objects, in dependency order: managed systems → policy maps → resources → roles and groups → content providers and URI patterns → authentication providers and rules → request categories → IT policies and OTP providers → business rules → campaigns. Steps 5 to 10 of the procedure above replace the hand work for roles, groups, organizations, resources and business rules; the rest of this list is still created by hand at the target.
4. Secrets, created natively at the target.
5. Rebuild the search indexes and the authorization graph on the target, then run the negative tests.

## What does export: Groovy scripts

Groovy Manager has a genuine, audited zip export and import, and it is the mechanism step 5 of the procedure uses. The screen is `/webconsole/groovyManager`.

| Action on the screen                                                  | Result                                                                            |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Export All Groovy Scripts**                                         | `groovy-scripts.zip`, every script's latest version                               |
| **Export Selected Groovy Scripts**, after ticking scripts in the tree | `groovy-scripts-selected.zip`                                                     |
| **Upload Groovy Scripts (.zip)**                                      | Scripts created or versioned, confirming *"Groovy scripts imported successfully"* |

Entries inside the zip are named `<path>/<name>_<version>.groovy`, or `<name>_<version>.groovy` when the script has no path — the same naming the tree uses. The audit actions are `EXPORT_GROOVY_SCRIPTS` and `IMPORT_GROOVY_SCRIPTS`.

Behaviour worth knowing before you rely on it:

* **Import is upsert, not replace.** An incoming script matching an existing path, name and version reuses that script's id, so the import updates in place rather than duplicating — and every existing reference keeps working.
* **A non-`.zip` upload is rejected** with `INVALID_FILE_EXTENSION`, shown as *"Unable to import Groovy scripts. Please ensure all files have a .groovy extension."*
* **A partial import still reports success.** The distinguishing message is `GROOVY_SCRIPTS_PARTIALLY_IMPORTED`, on screen as *"Import completed. Some files were skipped. Please check audit logs for details."*, and the list of what was skipped is a warning on the `IMPORT_GROOVY_SCRIPTS` audit record. Read the message and that warning, not just the fact that the upload finished.
* **Export-all drops duplicate entry paths silently**, recording a warning *"Skipped duplicate entry: …"* on the audit event rather than failing. Two scripts with the same path, name and version produce one file in the zip and a warning you have to go and look for.
* Both exports write the number exported into the audit event's success reason, *"Exported N groovy scripts"*. Compare N against the source environment's script count before treating the zip as complete.

There is a related one-way migration that changes which copy of a script is authoritative. On upgrade, OpenIAM moved scripts from files on disk into the `groovy` schema and rewrote every reference to them — from authentication providers, URI patterns, policies, synchronization and reconciliation configurations, metadata elements, mail templates, mailbox configurations and reports, and also from property values, resource properties, adaptive-risk rows and OTP providers' `GROOVY_PATH` attribute. **After that migration the database row is authoritative and the file you edit is not.** The symptom of getting this wrong is a promoted script file that has no effect at all. Which side is authoritative on your installation is not shown on any screen; the practical check is to change a script through Groovy Manager, confirm the change takes effect, and promote scripts as zips from then on.

## Related

* [Import and export reference](/administration-guide/platform/import-export-and-promotion/import-export-reference.md) — the five object types field by field, every configuration key, the command flags, the artifact shape and the exclusion catalogue
* [What a backup and restore must cover](/administration-guide/platform/backup-and-restore-scope.md) — the seven schemas, the vault, OpenSearch, and the version check that is the only one available
* [Where settings live](/administration-guide/start/where-settings-live.md) — the four layers, and the container override convention
* [Search indexes and the authorization graph](/administration-guide/reference/search-indexes.md) — what must be rebuilt after a restore or a promotion
* [Change configuration safely](/administration-guide/start/change-safely.md) — the recording discipline promotion depends on
* [Role engineering and maintenance](/administration-guide/access/role-engineering.md) — the entitlement CSV format, the other way to load a role catalog into a fresh environment

*Applies to OpenIAM 2026.8.1. Last reviewed 7 August 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/platform/import-export-and-promotion.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
