> 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/lifecycle/provisioning-processors.md).

# Attach a provisioning processor

Attach a Groovy pre- or post-processor to one managed system, prove it ran, and take it off again.

## What this is, and when you need it

A **provisioning processor** is a Groovy class OpenIAM calls immediately before or immediately after a provisioning operation on a user. It receives the `ProvisionUser` being provisioned and can read it, mutate it, or abort the operation by throwing. Attach one when the logic has to run for the operation as a whole — deriving a value from an external system, refusing to provision an incomplete record, notifying something once an account exists.

Per-attribute logic belongs on a row of a [policy map](/administration-guide/lifecycle/policy-map.md); exhaust the managed-system fields, the policy-map source types, roles and business rules first. Logic that must apply to *every* managed system is a [global provisioning script](/administration-guide/lifecycle/provisioning-processors/provisioning-processor-reference.md#global-provisioning-scripts) — a system property, not a row on one system.

The specific risk: a **pre**-processor that throws aborts the operation before anything is saved, which is usually what you want; a **post**-processor that throws aborts the *response* after the user has been saved and the connector requests dispatched — the caller sees a failure and the target already has the account. A processor that cannot be instantiated is silently replaced by one that does nothing. Read [Change configuration safely](/administration-guide/start/change-safely.md) first.

## At a glance

|                          |                                                                                                                                                                                                    |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | One `PRE_PROCESS` or `POST_PROCESS` row on one managed system, proven by the evidence your own script emits on a test user's operation. Your code then runs for every identity on that system      |
| **Where**                | **Administration → Groovy Manager** (`/webconsole/groovyManager`) for the script, then **Provisioning → Managed System**, `/webconsole/mngsystem?id=<MANAGED_SYSTEM_ID>`, **Attributes** table     |
| **You need first**       | The script saved and compiling with all nine methods implemented, its registry ID and not its path, and the right to save a managed system plus Groovy Manager (`UI_GROOVY_MANAGER` under `ADMIN`) |
| **Takes**                | 15–30 minutes to attach and prove an existing script; writing it is the longer job                                                                                                                 |
| **Reversible**           | Yes — remove the row and save, and OpenIAM falls back to the no-op default on the very next operation; not undone is a target write a post-processor throw left behind                             |

## Before you begin

* The script saved in the registry and compiling, extending `AbstractUserProvisioningPreProcessor` or `AbstractUserProvisioningPostProcessor`. Any other base class fails to instantiate.
* **All nine methods implemented**, even if you only care about one — they are all abstract. Clone `prov-helper/LDAPPreProcessor_1.0.groovy`, nine empty overrides with the correct signatures, rather than writing the declaration yourself. See [the base class](/administration-guide/lifecycle/provisioning-processors/provisioning-processor-reference.md#the-base-class).
* `<MANAGED_SYSTEM_ID>` and a `<TEST_USER>` you can provision repeatedly.

## Attach the processor

A managed-system processor is a resource property on the managed system, named `PRE_PROCESS` or `POST_PROCESS`.

{% stepper %}
{% step %}

### Save the script and get its registry ID

Write and save the script in **Groovy Manager** (`/webconsole/groovyManager`; the menu item ships as resource `UI_GROOVY_MANAGER` under `ADMIN` with display name `Groovy Manager`). The screen's copy button copies the *path* — `<path>/<name>_<version>.groovy` — which is not what you need here.

{% hint style="warning" %}
**The Webconsole offers no path to a script's registry ID.** No screen, field or export in the Webconsole displays it, and the Groovy Manager's copy button gives you the path instead. Ask OpenIAM support for the ID of a saved script, or have whoever owns the deployment's integrations retrieve it — the **Developer Guide** covers the script registry for anyone who needs IDs routinely. Do not substitute the path; see the warning below for what happens when you do.
{% endhint %}
{% endstep %}

{% step %}

### Add the attribute row on the managed system

Go to **Provisioning → Managed System**, open the system at `/webconsole/mngsystem?id=<MANAGED_SYSTEM_ID>`, and scroll to the attributes table. It has three columns — **Attribute Name**, **Metadata Element**, **Attribute Value** — and both Name and Value are required; saving a row with either blank raises `One or more required fields are empty` and the row is not added.

Enter `PRE_PROCESS` or `POST_PROCESS` as the Attribute Name, **exactly in upper case** — the name is matched exactly, so `Pre_Process` is a different property that nothing reads. Leave Metadata Element empty. Put the script's registry ID in Attribute Value.
{% endstep %}

{% step %}

### Save the managed system

**Save** writes the whole managed system, attributes included — there is no separate save for the attributes table. The attributes are stored as resource property rows on the managed system's resource. The save audits `MODIFY_MANAGED_SYSTEM`, or `CREATE_MANAGED_SYSTEM` on a system that did not exist yet — find it under `/webconsole/viewAuditLog` if you need to confirm the row was really persisted.
{% endstep %}

{% step %}

### Provision one test user and confirm the method ran

Put the managed system into simulation for `Add` and `Save` first, then provision one disposable `<TEST_USER>` on that system and look for the evidence your script emits. Until you see it, assume the fallback no-op processor ran.
{% endstep %}
{% endstepper %}

{% hint style="danger" %}
**The value must be a script ID, not a path.** The property value is looked up as an ID and nothing else, so a path such as `prov-helper/MyPreProcessor.groovy` resolves to nothing and you get the silent no-op fallback: the operation succeeds, the target is written, your script never ran.

Existing path-style values on an upgraded deployment do work, but only because the upgrade that introduced the Groovy manager rewrote every `PRE_PROCESS` and `POST_PROCESS` value ending in `.groovy` to the matching script's ID, once. It will not fix a row you add today. If you are unsure which form a row holds, read the value: an ID has no slashes and no `.groovy` suffix.
{% endhint %}

## Verification

* **The script ran at all.** The evidence has to come from your script — a log line, an audit note, a value on the user. If nothing appears, the default no-op processor was used instead of yours, and its methods log `<class>.add was called, which is a NO-OP` at **debug** level in the provisioning service log. Setting the logger category `org.openiam.idm.provisioning.preprocessor.user` to debug makes that line appear, and it is the fastest way to distinguish "my processor did nothing" from "my processor was never loaded" — see the [Installation Guide](/installation/installation.md) for how to change a logger level.
* **Instantiation failure.** `Cannot instantiate <the value you configured>` at error level in the provisioning service log is the definitive symptom of a wrong base class or a compile problem.
* **Audit.** The operation's own audit action carries the outcome: `PROVISIONING_ADD`, `PROVISIONING_MODIFY`, `PROVISIONING_DELETE`, `PROVISIONING_SET_PASSWORD`, `PROVISIONING_RESET_PASSWORD`, `PROVISIONING_ENABLE`, `PROVISIONING_DISABLE`, `PROVISIONING_SUSPEND`, `PROVISIONING_RESUME`. Enable and Disable also write per-identity children under `PROVISIONING_ENABLE_IDENTITY` and `PROVISIONING_DISABLE_IDENTITY`. A pre-processor throw shows up as a failed parent event with your message in the failure reason.
* **The attribute row itself.** Reopen `/webconsole/mngsystem?id=<MANAGED_SYSTEM_ID>` and read the attributes table back. Confirm the name is `PRE_PROCESS` in upper case and the value is an ID.
* **Effect on the object.** If the processor's job was to change what the connector receives, check the `provisionconnectorrequest` index, not the user record. See [Search indexes and the authorization graph](/administration-guide/reference/search-indexes.md). The message quotes the value it was given, so an empty property reads `Cannot instantiate null` — which is the fastest way to tell an unset property from a bad class.

## When it goes wrong

| Symptom                                                                                                                 | Cause                                                                                                                                                                                                                        | Fix                                                                                                                  |
| ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Nothing happens, no error anywhere                                                                                      | The value in `PRE_PROCESS`/`POST_PROCESS` is a file path, not a registry ID, so it resolves to nothing and the no-op default is used.                                                                                        | Replace the value with the script's registry ID.                                                                     |
| Nothing happens, `Cannot instantiate …` at error                                                                        | The class does not extend `AbstractUserProvisioningPreProcessor` (or the post-processor equivalent), or it fails to construct. A pre-processor script placed in `POST_PROCESS` fails the cast the same way.                  | Fix the base class; confirm pre and post scripts are in the right property.                                          |
| `Preprocess for X is not supported`                                                                                     | The processor was reached with an operation that has no method — for example an integration submitting `BulkRequest`.                                                                                                        | Only the ten operations in the routing table above reach a processor.                                                |
| A `PRE_PROCESS` row on the OpenIAM repository fires on Add and Update but not on Enable, Disable or password operations | Add and Update run the per-system processor for every identity including the default managed system; Enable, Disable and the password operations skip the default managed system deliberately.                               | Do not attach processors to the default managed system. Put repository-wide logic in the global script instead.      |
| `suspend` / `resume` never called                                                                                       | Lock and Unlock do not invoke processors at all.                                                                                                                                                                             | Use `disable`/`enable` with the Enable/Disable operations, or a business rule.                                       |
| Password processor skipped for one system                                                                               | The resource property `INCLUDE_IN_PASSWORD_SYNC` is `N` on that managed system. The audit entry carries a warning naming the property, and the provisioning service log says `Sync not allowed for sys=<id>` at debug level. | Set the property to something other than `N`, or remove it — only the literal `N`, in any case, excludes the system. |
| Global script runs, per-system script does not                                                                          | The `PRE_PROCESS` row is on the wrong object, or its name is not exactly upper-case `PRE_PROCESS` — the name is matched exactly, on the managed system's own resource and nowhere else.                                      | Re-add the row on the managed system, not on an application resource.                                                |
| Account created in the target although the operation reported failure                                                   | The throw came from a post-processor, which runs after the save and after connector dispatch.                                                                                                                                | Move the check into the pre-processor.                                                                               |
| `log` is unresolved when the processor script compiles                                                                  | The base class's logger is private and your script does not inherit it.                                                                                                                                                      | Declare your own logger, or use `println`.                                                                           |

The failures that come from the processor contract rather than from the attachment — a mutation that is never saved, an affiliation cascade, an ambiguous constructor, `ANOTHER_SAVE_IN_PROGRESS` — are in [Advanced symptoms](/administration-guide/lifecycle/provisioning-processors/provisioning-processor-reference.md#advanced-symptoms).

## Rollback

Remove the `PRE_PROCESS` or `POST_PROCESS` attribute row and save the managed system; OpenIAM falls back to the no-op default on the very next operation, because the property is read per operation and nothing about your processor is cached. For a global script, restore the shipped default from the table above — do not blank the property, since eight of the nine are seeded `EMPTY_OK = 'N'`.

## Advanced behaviour and implementation notes

**Change the object, do not save it.** A processor must never start a provisioning or save operation. Mutate the `ProvisionUser` you were given and return; OpenIAM rebuilds the diff after the pre-processors have run and saves the result itself. Calling a save from inside a processor starts a second operation while the first still holds the user's save lock, and the second fails with `ANOTHER_SAVE_IN_PROGRESS` — [Nested provisioning and the user save lock](/administration-guide/lifecycle/provisioning-processors/provisioning-processor-reference.md#nested-provisioning-and-the-user-save-lock).

**Not every operation reaches a processor.** Ten do — `Add`, `Update`, `Save`, `Lock`, `Unlock`, `Enable`, `Disable`, `SetPassword`, `ResetPassword` and `Delete` — and of those, `Lock` and `Unlock` never actually invoke one. Which method each reaches is [Operation → method routing](/administration-guide/lifecycle/provisioning-processors/provisioning-processor-reference.md#operation-to-method-routing).

### Which operations actually invoke a processor

| Operation             | Global pre                                     | Per-system `PRE_PROCESS`                                                                                     | Per-system `POST_PROCESS`         | Global post                                                        | Method reached                                                                                     |
| --------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `Add`                 | yes, unless the request set `skipPreprocessor` | yes, once for every login on the user                                                                        | yes                               | yes, unless the request set `skipPostProcessor`                    | `add`                                                                                              |
| `Update`              | yes, unless skipped                            | yes                                                                                                          | yes                               | yes, unless skipped                                                | `modify`                                                                                           |
| `Delete`              | yes, always                                    | yes, per login being deprovisioned                                                                           | yes                               | yes — **after** the per-system ones, the reverse of Add and Update | `delete`                                                                                           |
| `Enable` / `Disable`  | yes, always                                    | yes — see the note below on how many times                                                                   | yes, once for the whole operation | yes                                                                | `enable` / `disable`                                                                               |
| `SetPassword`         | yes, always                                    | yes, skipped for the default managed system and when the resource property `INCLUDE_IN_PASSWORD_SYNC` is `N` | yes, same conditions              | yes                                                                | `setPassword`                                                                                      |
| `ResetPassword`       | yes, always                                    | yes, skipped for the default managed system                                                                  | yes                               | yes                                                                | `resetPassword`                                                                                    |
| `SyncPasswordFromSrc` | **no global pre-processor at all**             | yes, gated on `INCLUDE_IN_PASSWORD_SYNC`                                                                     | yes                               | yes                                                                | `resetPassword` — this operation reaches your processor as `ResetPassword`, not under its own name |
| `Lock` / `Unlock`     | no                                             | no                                                                                                           | no                                | no                                                                 | none                                                                                               |

`skipPreprocessor` and `skipPostProcessor` are fields on the request itself, both defaulting to `false`, and they are set by whatever submitted the request rather than by any setting you can change. They suppress only the **global** script, never the per-managed-system one — so a global script that appears not to run on requests from one integration and to run on requests from the Webconsole is that integration setting the flag.

{% hint style="warning" %}
**Enable and Disable invoke the per-system pre-processor more than once.** On those two operations the per-system pre-processor is invoked once per identity *for each* identity — so for a user with three non-default identities, each of those three systems' `PRE_PROCESS` scripts runs three times. The `POST_PROCESS` script runs once per identity, as expected. Make Enable and Disable pre-processors idempotent, and if you need to see the multiplication, have the script add an audit info line and count them on the operation's audit entry.
{% endhint %}

The rest of the contract is in [Provisioning processor reference](/administration-guide/lifecycle/provisioning-processors/provisioning-processor-reference.md): what the processor may read, the global scripts and the connector response handler, the rules that decide whether a mutation is saved, and how to construct the exception that fails an operation attributably.

## Related

* [Provisioning processor reference](/administration-guide/lifecycle/provisioning-processors/provisioning-processor-reference.md) — the nine methods, the accessors, the routing table, the mutation rules and the save lock.
* [Build a policy map](/administration-guide/lifecycle/policy-map.md) — the per-attribute mechanism to try before a processor.
* [Configure provisioning](/administration-guide/lifecycle/configure-provisioning.md) — the settings that decide whether a provisioning operation happens at all, and the `ON_DELETE` decision table.
* [Configure reconciliation](/administration-guide/lifecycle/configure-reconciliation.md) — reconciliation has its own pre- and post-processor fields, a different mechanism from the two here.
* [Create a Groovy script](/administration-guide/automation/manage-groovy-scripts.md) — saving a script and finding its registry ID.
* [Which log answers which question](/administration-guide/operations/which-log-to-read.md#raising-the-detail-level) — the logger categories that unlock a diagnostic line, and which log it lands in.
* [Change configuration safely](/administration-guide/start/change-safely.md) — how to stage this change.

*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/lifecycle/provisioning-processors.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.
