> 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/developer-guide/validation-and-attribute-scripts.md).

# Validation and attribute scripts

Reject unusable rows before they are processed, declare which source attributes are read, and build the identifier an account carries in a target.

Two small script types run alongside transformation during a synchronisation, and a third builds the identifier an account carries in a target system. All three are short — most shipped examples are under twenty-five lines — and each prevents a problem that is expensive to unpick afterwards.

## What this builds, and how long it takes

|                |                                                                                                                                              |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| **You get**    | A synchronisation that rejects unusable rows, reads only the attributes it needs, and generates identifiers in the format the target expects |
| **Time**       | 30 minutes per script                                                                                                                        |
| **You need**   | A deployment, a synchronisation configuration, and a source containing rows you expect to fail                                               |
| **Reversible** | Yes                                                                                                                                          |

## Before you start

* [*Set up your environment*](/developer-guide/set-up-your-environment.md) and [*How scripting works*](/developer-guide/how-scripting-works.md).
* [*Transformation scripts*](/developer-guide/transformation-scripts.md) is worth reading first — these run around it.
* **A source with some bad rows in it.** A validation script that has never rejected anything has not been tested.

***

## Build it

### 1. Validation scripts

A validation script decides whether an inbound row should be processed at all. It runs **before** transformation, once per row.

```groovy
import org.openiam.idm.srvc.synch.dto.LineObject
import org.openiam.sync.service.ValidationScript

class HRFeedValidation implements ValidationScript {

    public int isValid(LineObject rowObj) {
        def columnMap = rowObj.columnMap

        if (!columnMap.get("EMPLOYEE_ID")?.value) {
            return ValidationScript.NOT_VALID
        }
        return ValidationScript.VALID
    }
}
```

**There are three return values, not two**, and the third is the one most often missed:

| Return                       | Value | What the synchronisation does                        |
| ---------------------------- | ----: | ---------------------------------------------------- |
| `ValidationScript.VALID`     |   `1` | Processes the row                                    |
| `ValidationScript.NOT_VALID` |   `2` | Rejects the row and records it as a failure          |
| `ValidationScript.SKIP`      |   `3` | Passes over the row without treating it as a failure |

**Use `SKIP` for rows that are not errors.** A feed that carries every employee including those out of scope for OpenIAM, a header repeated mid-file, a row for a population another job owns — those are not defects in the feed, and returning `NOT_VALID` for them fills the failure count with noise that hides real problems. Return `SKIP` and the row is quietly left alone.

**It implements an interface rather than extending a base class**, so there is no inherited `log` and no caches. Declare a logger if you need one.

**Use it for what makes a row unusable**, not for business rules. A missing identifier, a malformed date, a header line repeated mid-file — those belong here. Whether a user should be provisioned belongs in transformation or in policy.

**Rejecting is cheaper than repairing.** A row that reaches transformation with a missing key can create an object that later has to be found and merged.

### 2. Attribute scripts

An attribute script declares which source attributes the synchronisation reads.

```groovy
import org.openiam.sync.service.AttributesScript

class SAPAttributes implements AttributesScript {

    @Override
    String[] getAttributes() {
        return ["name",
                "description"
        ] as String[]
    }
}
```

**`as String[]` is required.** Groovy otherwise hands back a `List`, and the call fails at runtime rather than at compile time — which makes it slow to diagnose.

**Naming them narrows the read.** On a directory source in particular, asking for the attributes you use rather than everything is the difference between a synchronisation that finishes and one that times out.

### 3. Principal generators

A principal generator builds the identifier an account will carry in a managed system — a distinguished name, a user principal name, a login.

```groovy
import org.openiam.api.connector.groovy.AbstractPrincipalGenerator
import org.openiam.api.connector.model.ConnectorAttribute
import org.openiam.idm.provisioning.diff.model.user.ProvisionUserObjectDiff

class UserPrincipal extends AbstractPrincipalGenerator {

    @Override
    boolean isPerform(final ProvisionUserObjectDiff diffObject) {
        return super.isPerform(diffObject)
    }

    @Override
    void perform(final ConnectorAttribute attribute, final ProvisionUserObjectDiff diffObject) {
        super.perform(attribute, diffObject)
    }
}
```

**That shape — `isPerform` and `perform` — is the policy map shape**, because a principal generator is a policy map script specialised to the identifier. [*Policy map scripts*](/developer-guide/policy-map-scripts.md) explains both methods properly.

**Read the shipped generators before writing one.** `cn_1.0.groovy` for a directory common name, and `userPrincipalName_1.0.groovy` for Azure and Microsoft 365, are under `conf/iamscripts/provision/`. The format a target expects is exactly the kind of detail that is easier to copy than to derive.

### 4. Deploy them

Validation and attribute scripts go in the Groovy Manager under the folder for your synchronisation configuration, and are referenced by name in that configuration. Principal generators go under `provision/` for the managed system, and are referenced from its policy map. All compile on save.

***

## Verify it

**Run against a source containing rows you expect to fail.**

| Check                                | Where                                                                          |
| ------------------------------------ | ------------------------------------------------------------------------------ |
| Rows are being rejected              | The synchronisation result — a processed count lower than the source row count |
| The right rows are rejected          | Log the identifier in the rejecting branch                                     |
| Attributes are being read            | The transformation sees them; log `rowObj.columnMap.keySet()` once             |
| The principal is in the right format | The account in the target system                                               |

## If it goes wrong

| What you see                                                       | What it means                                             | What to do                                                            |
| ------------------------------------------------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------- |
| Every row is rejected                                              | The condition is inverted, or the column name is wrong    | Log the value being tested before the comparison                      |
| No row is ever rejected                                            | The script is not attached, or always returns `VALID`     | Confirm it is referenced by name in the synchronisation configuration |
| An attribute is empty in transformation although the source has it | It is not in `getAttributes()`                            | Add it                                                                |
| `getAttributes` fails at runtime with a cast error                 | The `as String[]` coercion is missing                     | Add it                                                                |
| A principal is generated in the wrong format                       | The target expects a different one                        | Compare against the shipped generator for that target                 |
| Accounts are created with duplicate principals                     | The generator produces the same value for different users | Include something unique; the shipped generators show how             |

## What this procedure does not cover

The matching rules that decide which existing object an inbound row corresponds to — those are configuration, covered in the [Administration Guide](/administration-guide/admin.md), not a script.

## Related

* [*Transformation scripts*](/developer-guide/transformation-scripts.md) — what runs after validation
* [*Policy map scripts*](/developer-guide/policy-map-scripts.md) — the `isPerform` / `perform` pattern in full

*Applies to OpenIAM 2026.8.1.*


---

# 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/developer-guide/validation-and-attribute-scripts.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.
