> 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/requests/request-catalog.md).

# The request catalog and questionnaires

Make roles, groups and resources appear in the SelfService request catalog, and attach questionnaires that reach the approver.

## What this is, and when you need it

The SelfService request catalog is a four-step wizard: pick an **application category**, pick an **application** in it, pick the **roles and groups** attached to that application, then answer the **questionnaire**. Making something requestable means placing it correctly in that chain — there is no single "requestable" checkbox.

This page covers the catalog chain and questionnaires. Who approves the resulting request is [Approval flows](/administration-guide/requests/approval-flows.md); what happens when the target cannot be provisioned automatically is [Manual fulfilment](/administration-guide/requests/fulfilment.md).

## At a glance

|                          |                                                                                                                                                                                         |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | A role or group a test requester can find in SelfService under its application, with your questions attached and the answers visible to the approver                                    |
| **Where**                | The application category and application resource in the Webconsole; the wizard at `/selfservice/createRequest?id=<USER_ID>`; questions at `/webconsole/questionnaire?id=<RESOURCE_ID>` |
| **You need first**       | An `APPLICATION_CATEGORY` resource; a managed system, content provider or auth provider to be the "application"; an `ACTIVE` role or group linked to that application's resource        |
| **Takes**                | 20 to 30 minutes, plus reindex time before the catalog reflects a new role                                                                                                              |
| **Reversible**           | Yes for the links and the category. **No for questions**: a questionnaire save is a full replace, so anything not posted is deleted                                                     |

## Before you begin

* **A resource of type `APPLICATION_CATEGORY`.** Five ship: `BUSINESS_APPS`, `DIRECTORIES`, `INFRASTRUCTURE`, `MAIL_AND_COLLABORATION` and `NON_IT_RESOURCES`.
* **A managed system, content provider or authentication provider** whose **Application category** points at that category. This is the object the catalog calls an "application".
* **At least one role or group with `ACTIVE` status linked to that application's resource.**
* **A current search index.** The catalog reads categories, applications, roles and groups from the search index, not from the database, so nothing you create appears to a requester until it is indexed — while the Webconsole, which reads the database, shows it immediately. That single difference explains most "it works for me but not for them" reports.
* **A test requester who is not you.** An administrator's own view of SelfService is not representative: the entitlement filter is skipped for super security admins, and it is skipped for everybody while `org.openiam.authorization.create.request.disabled` sits at its shipped `true`.

## Make a role or group requestable

There is no "requestable" checkbox. A role appears in the catalog because it is `ACTIVE`, it is linked to an application's resource, and it is in the role search index — nothing else. Membership, ownership, risk and the **Is Visible** flag do not filter this list.

{% stepper %}
{% step %}

### Confirm or create the application category

**Access Control → Resource**. Either use one of the five shipped `APPLICATION_CATEGORY` resources, or create a new resource of that type and give it a parent resource if you want it nested.

Set its display name per language on the resource edit screen — that editor appears only for this resource type — and leave the category `ACTIVE`, because the dropdown in the next step offers only `ACTIVE` ones.
{% endstep %}

{% step %}

### Point the application at the category

**Provisioning → Managed System → Application category**, and set it to the category. That writes the object's `CATEGORY_TYPE_ID`, which is the only reason an application appears in a category. A content provider or an authentication provider is set the same way, on its own screen.
{% endstep %}

{% step %}

### Link the role or group to the application's resource, and check its status

The role or group must be linked to the application's resource and its status must be `ACTIVE`. That is the whole test.

{% hint style="danger" %}
**The `Is Visible` flag does not hide a role or group from the request catalog**, despite the SelfService label reading "Show this group in the service catalog". Nothing on this path reads it. If a role must not be requestable, unlink it from the application resource or move its status away from `ACTIVE` — do not build a control on `Is Visible`. Confirm it on your release: turn `Is Visible` off on a test role and open the catalog as a test requester.
{% endhint %}
{% endstep %}

{% step %}

### Add the questions, as one complete list

**Access Control → Resource**, open the *application's* resource — not the role or group — and go to `/webconsole/questionnaire?id=<RESOURCE_ID>` from its edit menu. One `QUESTIONNAIRE` row is one question.

Set **Type** to exactly `Text`, `Select` or `MultiSelect` — the comparison is case-sensitive, and what each one renders is in [The three types](#the-three-types-and-what-they-actually-render). Give every non-`Text` question at least one answer option, or a Groovy script that supplies them: **a `Select` or `MultiSelect` question with no answer rows and no script disappears from the catalog silently.**

{% hint style="danger" %}
**The save is a replace, not a merge.** OpenIAM writes the whole list back and deletes every existing question for that resource that is not in it — including a question the screen never loaded. Always post the complete list.
{% endhint %}

Mark the questions **Required?** if you want the answers to reach the approver at all: an empty answer list is not serialised, and no placeholder is stored.
{% endstep %}

{% step %}

### Reindex, then check the catalog as the test requester

All three catalog lookups read the search index. A newly created role, group or application will not appear until it is indexed — see [Search indexes and the authorization graph](/administration-guide/reference/search-indexes.md).
{% endstep %}

{% step %}

### Raise one request and read the answers as the approver

Submit as the test requester, then open the resulting task at `/selfservice/task` as the approver and confirm the answers render. They will not render for a question the requester was not obliged to answer.
{% endstep %}
{% endstepper %}

## Verification

Verify at the far end rather than at the save, and do it as a **test requester** rather than as an administrator: open `/selfservice/createRequestSelect?id=0`, where `0` resolves to the signed-in user.

| Evidence                                                                       | Meaning                                                                                                                                        |
| ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| The **Service catalog** tile is offered                                        | The requester holds the `SERVICE_CATALOG_BTN` menu entitlement. Without it nothing else on this page is reachable                              |
| The category, then the application, then the role or group each appear in turn | All four links in the chain are correct and indexed. A break at any one step looks identical from the tile                                     |
| The questionnaire tab shows every question, with the input you expect          | Textarea for `Text`, checkboxes for `MultiSelect`, radios for `Select`. A label with no input under it means `TYPE` is wrong                   |
| A `SAVE_QUESTIONNAIRE` audit event, and the `QUESTION_SAVED` message on screen | The question list was written. If the screen confirms but no audit event exists, you are looking at a cached page rather than a completed save |
| The approver's task view renders the answers                                   | The `Questionnaire` process variable was set. A blank task view usually means the posted answer list was empty, not that rendering failed      |

## When it goes wrong

| Symptom                                                 | Cause to check                                                                                                                                                        | Fix                                                                                                                                                                           |
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No **Service catalog** tile in SelfService              | Requester not entitled to the `SERVICE_CATALOG_BTN` menu resource                                                                                                     | Grant the menu entitlement; it hangs off the `CREATE_REQUEST_FOR_MYSELF` menu tree                                                                                            |
| Catalog is empty of categories after a change           | `org.openiam.authorization.create.request.disabled` was set to `false` and the user has no `APPLICATION_CATEGORY` entitlements                                        | Grant the categories, or restore the shipped `true`                                                                                                                           |
| Every user sees every category                          | `org.openiam.authorization.create.request.disabled` is at its shipped `true`, which skips the entitlement check                                                       | Set it to `false` and grant categories explicitly                                                                                                                             |
| An application is missing from a category               | The managed system, content provider or auth provider has no `CATEGORY_TYPE_ID` pointing at that category resource                                                    | Set **Application category** on the object                                                                                                                                    |
| Application shows in the Webconsole but not SelfService | SelfService reads the search index, the Webconsole reads the database                                                                                                 | Reindex; see [Search indexes](/administration-guide/reference/search-indexes.md)                                                                                              |
| A new role does not appear under its application        | The catalog reads the role search index, not the database                                                                                                             | Reindex the role                                                                                                                                                              |
| A role appears that should not                          | It is `ACTIVE` and linked to the application resource; `Is Visible` is not applied on this path                                                                       | Unlink it from the resource, or change its status                                                                                                                             |
| Category tile shows a raw resource name                 | The `APPLICATION_CATEGORY` resource has no display name for that language                                                                                             | Add it on the resource edit screen; the per-language editor appears for this resource type only                                                                               |
| Question missing from the questionnaire tab             | A non-`Text` question with zero `QUESTIONNAIRE_ANS` rows and no `GROOVY_SCRIPT_ID` is dropped from the catalog                                                        | Add answer options or a script                                                                                                                                                |
| Question label shows with no input under it             | `TYPE` is not one of `Text`, `Select`, `MultiSelect`, and the React catalog renders nothing for an unrecognised type                                                  | Correct `TYPE` — the comparison is case-sensitive                                                                                                                             |
| A question behaves as single-choice unexpectedly        | The older catalog falls through to radio buttons for any unrecognised type that has answer options                                                                    | Correct `TYPE` to `MultiSelect`                                                                                                                                               |
| Multi-select answers look like radios to the approver   | The task view renders every non-`Text` answer set as radios                                                                                                           | Display-only; the stored `Questionnaire` variable is correct                                                                                                                  |
| Questionnaire script fails                              | The script did not return a list of answer objects; the log carries `Can't load and execute groovy script` and the response code is `CANNOT_INSTANTIATE_GROOVY_CLASS` | Return the right type; the only binding available is `question`                                                                                                               |
| Answer options edited but requesters see different ones | A `GROOVY_SCRIPT_ID` is set, and the script's list replaces the stored rows for requesters while the editor still shows the stored ones                               | Clear the script, or change the script                                                                                                                                        |
| Questions disappeared after a save                      | Saving a questionnaire replaces the whole question list, so a save that omits a question deletes it                                                                   | Never save a partial list. Reopen the questionnaire tab, confirm every question you expect is on it, and save from there                                                      |
| Approver sees no answers                                | The `Questionnaire` variable was never set, because the posted answer list was empty — an empty list is not serialised                                                | Mark the questions mandatory so an answer is always posted; the [break-glass query](#the-workflow-stores-it-in-one-variable) distinguishes "never stored" from "not rendered" |

## The chain, exactly

The wizard lives at `/selfservice/createRequestSelect?id=<USER_ID>` (entry page) and `/selfservice/createRequest?id=<USER_ID>` (the wizard itself).

### Step 0: is the catalog even offered?

OpenIAM reads the authorization menu rooted at `CREATE_REQUEST_FOR_MYSELF` and shows the service catalog tile only when the requester is entitled to the child menu whose ID is `SERVICE_CATALOG_BTN` — the ID comes from `org.openiam.ui.selfservice.access.request.create.request.service.catalog`. The sibling entry points on that menu are `MYSELF_CREATE_REQUEST`, `ACCESS_REVIEW`, `SELECT_PROFILE_BTN`, `CLONE_RIGHTS_BTN`, `REVOKE_ACCESS_BTN` and `BULK_PASSWORD_RESET_BTN`. If the catalog tile is missing for a user, that menu entitlement is the first thing to check — not the resource.

### Step 1: application categories

The first step lists resources whose type is `APPLICATION_CATEGORY` and whose status is `ACTIVE`, roots only, sorted by name.

**Whether the list is filtered by the requester's entitlements is decided by one property:**

| Property                                            | Label                                                                                                                          | Type    | Shipped default            |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------- | -------------------------- |
| `org.openiam.authorization.create.request.disabled` | Disable Create Request Category Authorization (backward compatibility — previously all categories were available to all users) | Boolean | `true` (category `System`) |

At the shipped `true`, the entitlement check is skipped entirely: **every active application category is visible to every requester.** At `false`, OpenIAM resolves the requester's own entitlements and keeps only the `APPLICATION_CATEGORY` resources they hold, by ordinary RBAC.

**Confirm which behaviour you have** by signing into SelfService as a test requester who is entitled to no categories at all. If they still see the full list, the property is at its shipped `true`.

{% hint style="warning" %}
The shipped default is the permissive one, and the property name inverts the meaning — `true` *disables* the authorization check. Turning it to `false` is a real behaviour change: a category no user is entitled to becomes invisible to everyone, including administrators using SelfService, and the catalog will look empty until you grant the categories.
{% endhint %}

Sub-categories are resolved the same way, with the parent category as the root — so the hierarchy is resource-to-resource parentage, and the same authorization rule applies at every level.

Five categories have shipped since 4.0.0.0:

| Resource ID              | Display name           |
| ------------------------ | ---------------------- |
| `BUSINESS_APPS`          | Business apps          |
| `DIRECTORIES`            | Directories            |
| `INFRASTRUCTURE`         | Infrastructure         |
| `MAIL_AND_COLLABORATION` | Mail and Collaboration |
| `NON_IT_RESOURCES`       | Non-IT Resources       |

The same release introduced the `APPLICATION_CATEGORY` resource type — hierarchical, searchable and provisionable — and moved categories off metadata types onto resources. Since then, a managed system, auth provider or content provider names its category in its own `CATEGORY_TYPE_ID` column, which points at a `RES` row.

A category's catalog label comes from the resource's display names, and the display-name-per-language editor appears on the resource edit screen **only** for this resource type — if you cannot find that editor, check the resource type before anything else. A category with no display name for the reader's language shows its raw resource name.

### Step 2: applications in a category

Step 2 runs three searches against that category and concatenates the results, sorted by lower-cased name: authentication providers, managed systems, then content providers, each matched on `CATEGORY_TYPE_ID`.

Two behaviours matter. **From SelfService these come from the search index; from the Webconsole they come from the database.** That is why the same category can look different in the two consoles when the index is stale, and it is the first thing to check when an application is visible to you but not to a requester. And when tenant filtering by organization is enabled and the requester is not a super security admin, the search is restricted to the requester's organizations; a requester with no organization sees nothing.

A category you created but left inactive will not be selectable in the **Application category** dropdown, because that dropdown offers only `ACTIVE` `APPLICATION_CATEGORY` resources.

### Step 3: the entitlements under an application

Step 3 builds a tree whose root rows are the application resources you selected. Under each, OpenIAM lists the roles linked to that resource whose status is `ACTIVE` and whose name contains the search filter, sorted by name — and then the groups, on identical terms. All three lookups read the search index, not the database.

So the answer to "what makes a role requestable" is: **it is `ACTIVE`, it is linked to the application's resource, and it is present in the role search index.** Nothing else — not membership, not ownership, not risk, and not the **Is Visible** flag.

Because both searches read the index, a newly created role will not appear until it is indexed. See [Search indexes and the authorization graph](/administration-guide/reference/search-indexes.md).

The tree also annotates access the user already holds: those rows are shown greyed out and cannot be selected again, and rows expiring within `org.openiam.workflow.access.auto.expire.days.reminder` days (shipped `7`) are moved under a "near expiring" row so they *can* be re-requested.

## Questionnaires

A questionnaire is a set of questions attached to a **resource** — the application's resource, not a role or group. One `QUESTIONNAIRE` row is one question.

Edit them in the Webconsole at `/webconsole/questionnaire?id=<RESOURCE_ID>`, reached from the resource's edit menu. Remember that the save is a full replace, not a merge.

### The table

Questions live in `QUESTIONNAIRE`:

| Column             | Field            | Type         | Notes                                            |
| ------------------ | ---------------- | ------------ | ------------------------------------------------ |
| `ID`               | `id`             | String       | Primary key                                      |
| `RESOURCE_ID`      | `resourceId`     | String(32)   | The resource the questionnaire belongs to        |
| `TYPE`             | `type`           | String(20)   | `Text`, `Select` or `MultiSelect`                |
| `QUESTION`         | `question`       | String(2000) | The question text                                |
| `GROOVY_SCRIPT_ID` | `groovyScriptId` | String(80)   | Optional script that supplies the answer options |
| `MANDATORY`        | `mandatory`      | Y/N          | Rendered as **Required?**                        |

Answer options are `QUESTIONNAIRE_ANS` rows, with a `QUESTIONNAIRE_ID` and a `TEXT`. One thing to know before you write a script: the field carrying an *answer's* display text is named `question`, not `answer`. That naming catches everyone once.

### The three types, and what they actually render

The **Type** dropdown offers exactly three values — `Text`, `Select` and `MultiSelect`, labelled *Text*, *Select* and *Multi-Select*. The stored values are the three strings above, and they are compared case-sensitively when the question is rendered, so `Multiselect` or `multiselect` is **not** the same as `MultiSelect`. You can only reach a wrong value by writing it directly to the table or by importing it.

**Neither `Select` nor `MultiSelect` produces a dropdown:**

| Stored `TYPE` | UI label     | What the requester actually sees                                                |
| ------------- | ------------ | ------------------------------------------------------------------------------- |
| `Text`        | Text         | A multi-line text box. Answer options, if any, are discarded on save            |
| `MultiSelect` | Multi-Select | A group of **checkboxes**, one per answer option — several may be chosen        |
| `Select`      | Select       | A group of **radio buttons**, one per answer option — exactly one may be chosen |

So "Multi-Select" is genuinely multi-valued, but it is a checkbox list, not a multi-select dropdown. Two known limitations follow, both worth stating because they fail silently:

* **An unrecognised `TYPE` fails differently in the two catalog UIs.** The older SelfService catalog treats any type that is neither `Text` nor `MultiSelect`, but which has answer options, as single-choice radio buttons — so a typo degrades to single-choice rather than failing visibly. The React catalog renders **nothing at all** for an unrecognised type: the question label appears with no input under it. In both cases the mandatory flag is only enforced for the three known types, so an unrecognised mandatory question can neither be answered nor block **Next**. If a question label appears with no input, `TYPE` is wrong.
* **On the approver's task view, multi-select answers are drawn as radio buttons.** The task view treats everything that is not `Text` as a set of disabled radios. Multiple checked options do all display, so no information is lost, but a multi-select answer reads visually as several selected radios. The stored data is correct; only the rendering is misleading — do not raise it as data corruption.

### Supplying answers from a Groovy script

Set the **Groovy script** field on the question dialog — stored as `GROOVY_SCRIPT_ID` — to have the answer options computed when the requester opens the catalog instead of read from `QUESTIONNAIRE_ANS`. The contract is narrow, so it is worth stating exactly:

* **The script receives one binding, `question`** — the question's text, nothing else. It does not get the requester, the resource or the request, so you cannot vary the options per user from here.
* **It must return a list of answer objects.** Return anything else and the script fails at runtime: the response code is `CANNOT_INSTANTIATE_GROOVY_CLASS`, and the service log carries `Can't load and execute groovy script`. Search the log for that line as written.
* **The returned list replaces the stored answers entirely.** Any `QUESTIONNAIRE_ANS` rows for that question are ignored while a script is set.
* **Set both the display text and an identifier on each answer.** The display text is what the requester reads; the identifier is what the rendered input and the stored answer are keyed on, so an answer without one cannot be recorded.
* **The script runs for the SelfService catalog and not for the Webconsole editor.** The editor deliberately shows the stored rows, which is what you want while editing. So a script whose output is wrong will look right in the Webconsole and wrong to the requester — always check the catalog itself as a test requester.

{% hint style="info" %}
The catalog keeps a question only if its type is `Text`, **or** it has at least one answer option. A `Select` or `MultiSelect` question with no stored answer rows and no script therefore disappears from the catalog silently — no error, no empty question. This is the most common "my question is not showing" cause, and the check is to open the catalog as a test requester and count the questions.
{% endhint %}

## How answers reach the approver

The chain is worth spelling out because there is exactly one workflow variable involved and no database table for answers. Four hops:

### SelfService collects the answers

The wizard collects one answer per question — the question text, the chosen options or the free-text value, the question's position in the list, its type, and the resource it belongs to — and submits the whole set with the request.

### OpenIAM serialises them to a single JSON string

If — and only if — the requester answered at least one question, OpenIAM serialises the whole set to JSON and attaches it to the request. An empty answer set is not serialised at all, and no placeholder is stored. What the break-glass query below returns looks like this:

{% code overflow="wrap" %}

```json
{"questionAnswers":[{"id":"…","question":"…","type":"…","answer":"…","answers":[],"order":0,"resourceId":"…"}]}
```

{% endcode %}

### The workflow stores it in one variable

That JSON becomes one Activiti process variable named **`Questionnaire`**, set when the process starts and carried forward on every decision. There is no answers table — **the answers exist only as this process variable**, so deleting the process instance deletes them, and no amount of reporting over the OpenIAM schema will recover them.

{% hint style="warning" %}
**Break-glass: reading the `Questionnaire` variable directly.** No Webconsole screen shows a process variable by name, so if an approver reports missing answers and the task view is blank, the only way to tell "never stored" from "not rendered" is to read Activiti's own history:

{% code overflow="wrap" %}

```sql
SELECT PROC_INST_ID_, NAME_, TEXT_
FROM ACT_HI_VARINST
WHERE NAME_ = 'Questionnaire' AND PROC_INST_ID_ = ?;
```

{% endcode %}

This is outside the supported change path. It is read-only, so it risks no data, but it bypasses OpenIAM's access control and audit entirely — nothing records that you read a requester's answers, which for a questionnaire containing personal justification text is a real consideration, not a formality. No row means the variable was never set, which means the posted answer list was empty. The supported alternative is to reproduce the request as a test requester and watch the approver's task view.
{% endhint %}

### The approver's task view reads it back

OpenIAM parses the variable back out and renders it read-only on the approver's task view at `/selfservice/task`. The approver cannot change an answer, and there is no path for them to ask the requester for a correction other than rejecting the request.

Two consequences: an approver sees answers only for questions that existed when the request was raised, and editing a questionnaire afterwards does not change what an in-flight request shows. That is a feature — it keeps the evidence of what was actually asked — but it does mean a badly worded question stays badly worded for every request already in flight.

`org.openiam.workflow.task.decision.details` ("Additional information fields on the task view page", type MultiSelect, shipped **empty**) controls the *other* fields on that page, not the questionnaire. Setting it will not make a missing questionnaire appear.

## Manual fulfilment

When the requested access lives in a system OpenIAM cannot provision, fulfilment becomes a human task: the `manualTask` workflow, whose single user task is named *Manual Task*. Its approvers are **the target resource's administrators, not the approver associations** used everywhere else — which is why a manual task can land on someone who appears nowhere in your approval configuration.

Full configuration is in [Manual fulfilment](/administration-guide/requests/fulfilment.md).

## Related

* [Approval flows](/administration-guide/requests/approval-flows.md) — who approves what the catalog produces
* [Manual fulfilment](/administration-guide/requests/fulfilment.md) — the `manualTask` workflow
* [Shipped approval workflows](/administration-guide/reference/shipped-workflows.md) — the 49 BPMN definitions
* [Shipped batch tasks](/administration-guide/reference/shipped-batch-tasks.md) — reminders and escalation ship disabled
* [Enumerated values](/administration-guide/reference/enums.md#workflow-variables) — the `Questionnaire` variable and its siblings
* [System properties](/administration-guide/reference/system-properties.md) — `System` and `Workflow` categories
* [Search indexes and the authorization graph](/administration-guide/reference/search-indexes.md) — why the catalog can be stale
* [Change configuration safely](/administration-guide/start/change-safely.md) — the change, test and rollback standard

*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/requests/request-catalog.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.
