> ## Documentation Index
> Fetch the complete documentation index at: https://docs.conduktor.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-service resources

> YAML resource definitions for Conduktor Self-service: Applications, ApplicationInstances, and permission schemas for GitOps-driven Kafka governance.

## Application

An application represents a streaming app or data pipeline that's responsible for producing, consuming or processing data in Kafka.

In Self-service, it's used as a method to organize and re-group multiple deployments of the same application (dev, prod) or different microservices that belong to the same team under one umbrella.

* **API key(s):** AdminToken
* **Managed with:** API, CLI, TF
* **Labels support:** Full

<Tabs>
  <Tab title="CLI">
    ```yaml theme={null}
    # Application
    ---
    apiVersion: self-serve/v1
    kind: Application
    metadata:
      name: "clickstream-app"
    spec:
      title: "Clickstream App"
      description: "FreeForm text, probably multiline markdown"
      owner: "groupA" # technical-id of the Conduktor Console Group
      policyRef:
        - "applicationgroup-restrictions"
    ```

    **Application checks:**

    * `spec.owner` is a valid Console group
    * `spec.policyRef` (optional), if set, has to be a valid list of [ResourcePolicy](#resourcepolicy).
    * Delete **has to fail** if there are associated `ApplicationInstance`
  </Tab>

  <Tab title="Terraform">
    [View Terraform documentation](https://registry.terraform.io/providers/conduktor/conduktor/latest/docs/resources/console_application_v1) <Icon icon="up-right-from-square" />
  </Tab>
</Tabs>

**Side effects:**

* None, deploying this object will only create the application in Console that can be managed on the **Application Catalog** page.

## ApplicationInstance

Application instance represents an actual deployment of an application on a Kafka cluster for a service account.

This is the core concept of Self-service, as it **ties everything together**: Kafka cluster, service account, ownership of resources and policies.

* **API key(s):** AdminToken
* **Managed with:** API, CLI, TF
* **Labels support:** Full

<Tabs>
  <Tab title="CLI">
    ```yaml theme={null}
    ---
    apiVersion: self-serve/v1
    kind: ApplicationInstance
    metadata:
      application: "clickstream-app"
      name: "clickstream-dev"
    spec:
      cluster: "shadow-it"
      serviceAccount: "sa-clicko"
      policyRef:
        - "generic-dev-topic"
        - "clickstream-naming-rule"
      defaultCatalogVisibility: PUBLIC # makes all owned topics visible in the Topic Catalog by default
      resources:
        - type: TOPIC
          patternType: PREFIXED
          name: "click."
        - type: CONSUMER_GROUP
          patternType: PREFIXED
          name: "click."
        - type: SUBJECT
          patternType: PREFIXED
          name: "click."
        - type: TRANSACTIONAL_ID
          patternType: LITERAL
          name: "clickstream-txn-id"
        - type: CONNECTOR
          connectCluster: shadow-connect
          patternType: PREFIXED
          name: "click."
        - type: TOPIC
          patternType: PREFIXED
          ownershipMode: LIMITED # Topics are still maintained by central team
          name: "legacy-click."
    ```

    **AppInstance checks:**

    * `metadata.application` is a valid application.
    * `spec.cluster` is a valid Console cluster technical Id.
    * `spec.cluster` is immutable (can't be updated after creation).
    * `spec.serviceAccount` (optional). If already used by another *AppInstance* on the same `spec.cluster`, it can't be set.
    * `spec.applicationManagedServiceAccount` (optional), default is `false`. If set to `true`, the service account ACLs will be managed by the application owners directly instead of being synchronized by the *ApplicationInstance*. [Find out more about managed service account](#application-managed-service-account).
    * `spec.policyRef` (optional), if set, has to be a valid list of [ResourcePolicy](#resourcepolicy).
    * `spec.topicPolicyRef` (optional), if defined, has to be a valid list of [TopicPolicy](#topicpolicy). Will be deprecated in a future release — prefer `spec.policyRef` with [ResourcePolicy](#resourcepolicy) instead.
    * `spec.defaultCatalogVisibility` (optional), default is `PUBLIC`. Can be `PUBLIC` or `PRIVATE`.
    * `spec.resources[].type` can be `TOPIC`, `CONSUMER_GROUP`, `SUBJECT`, `TRANSACTIONAL_ID` or `CONNECTOR`:
      * `spec.resources[].connectCluster` is **only mandatory** when `type` is `CONNECTOR`;
      * `spec.resources[].connectCluster` is a valid Connect cluster linked to the Kafka cluster `spec.cluster`.
    * `spec.resources[].patternType` can be `PREFIXED` or `LITERAL`.
    * `spec.resources[].name` has to not overlap with any other *ApplicationInstance* on the same cluster. I.e.: if there's already an owner for `click`, this is forbidden:
      * `click.orders.`: resource is a child-resource of `click`
      * `cli`: resource is a parent-resource of `click`
    * `spec.resources[].ownershipMode` (optional), default is `ALL`. Can be `ALL` or `LIMITED`.
  </Tab>

  <Tab title="Terraform">
    [View Terraform documentation](https://registry.terraform.io/providers/conduktor/conduktor/latest/docs/resources/console_application_instance_v1) <Icon icon="up-right-from-square" />
  </Tab>
</Tabs>

**Side effects:**

* Console
  * Members of the owner group can create ApplicationGroups.
  * To create API keys, request or grant access, or manage service accounts, assign the corresponding `instancePermissions` to an [ApplicationGroup](#applicationgroup).
  * Resources with `ownershipMode` set to `ALL`: *ApplicationInstance* is given **all permissions** in the UI and the CLI over the owned resources.
  * Resources with `ownershipMode` set to `LIMITED`: *ApplicationInstance* is restricted the **create/update/delete permissions** in the UI and the CLI over the owned resources:
    * can't use the CLI `apply` command
    * can't create/delete the resource in the UI
    * everything else (restart connector, browse and produce from topic, etc.) is still available. [Find out more about ownership](/guide/use-cases/self-service#limited-ownership-mode).
* Kafka
  * Service account is granted the following ACLs over the declared resources depending on the type:
    * Topic: `READ`, `WRITE` and `DESCRIBE_CONFIGS`
    * ConsumerGroup: `READ`
    * TransactionalId: `WRITE` and `DESCRIBE`
  * For Confluent Cloud and Confluent Platform clusters with their provider settings set to have role bindings enabled, the following RBAC role bindings will be created for the service account instead:
    * Topic: `DeveloperRead`, `DeveloperWrite`
      * There's also an implicit permission granted for subjects that share the same topic prefix. If there's `write` access to a topic, the service account will also receive `write` access over the subject.
    * Subject: `DeveloperRead`, `DeveloperWrite`
    * ConsumerGroup: `DeveloperRead`
    * TransactionalId: `DeveloperRead`, `DeveloperWrite`

Find out how to migrate RBAC role bindings to [Confluent Cloud](/guide/tutorials/migrate-confluent-cloud-rbac) or [Confluent Platform](/guide/tutorials/migrate-confluent-platform-rbac).

### ApplicationInstancePermission

Define permissions for the application instance to enable collaboration between teams.

* **API key(s):** AdminToken, AppToken
* **Managed with:** API, CLI, TF
* **Labels support:** Missing

<Tabs>
  <Tab title="CLI">
    ```yaml theme={null}
    # Permission granted to other applications
    ---
    apiVersion: self-serve/v1
    kind: ApplicationInstancePermission
    metadata:
      application: "clickstream-app"
      appInstance: "clickstream-app-dev"
      name: "clickstream-app-dev-to-another"
    spec:
      resource:
        type: TOPIC
        name: "click.event-stream.avro"
        patternType: LITERAL
      userPermission: NONE
      serviceAccountPermission: READ
      grantedTo: "another-appinstance-dev"
    ```

    **Application instance permission checks:**

    * `spec` is immutable:
      * once created, you'll only be able to update its metadata. **This is to protect you from making a change that could impact an external application**.
      * this resource affects target *ApplicationInstance*'s Kafka service account ACLs.
      * to edit this resource, delete and re-create it.
    * `spec.resource.type` can be `TOPIC`.
    * `spec.resource.patternType` can be `PREFIXED` or `LITERAL`.
    * `spec.resource.name` has to reference any 'sub-resource' of `metadata.appInstance`. For example, if you're the owner of the `click.` prefix, you can grant `READ` or `WRITE` access to:
      * the whole `click.` prefix,
      * a sub prefix `click.orders.`,
      * a literal topic name `click.orders.france`.
    * Permissions can be set in one of two mutually exclusive ways:
      * Shortcut: set `spec.permission` to `READ` or `WRITE` to apply the same permission to both users and service accounts.
    * * Independent: set `spec.userPermission` and `spec.serviceAccountPermission` together — each accepts `READ`, `WRITE`, or `NONE`. Use `NONE` to\
        grant access to one but not the other (e.g. `spec.serviceAccountPermission: READ` with `spec.userPermission: NONE`). Both fields must be provided if
        either is set.
    * `spec.serviceAccountPermission` can be `READ` or `WRITE`.
    * `spec.grantedTo` has to be an *ApplicationInstance* on the same Kafka cluster as `metadata.appInstance`.
  </Tab>

  <Tab title="Terraform">
    [View Terraform documentation](https://registry.terraform.io/providers/conduktor/conduktor/latest/docs/resources/console_application_instance_permission_v1) <Icon icon="up-right-from-square" />
  </Tab>
</Tabs>

**Side effects:**

* Console
  * Members of the `grantedTo` *ApplicationInstance* are given the associated permissions (`Read`/`Write`) in the UI over the resources.
* Kafka
  * Service account of the `grantedTo` *ApplicationInstance* is granted to the following ACLs over the `resource`, depending on the `spec.permission`:
    * `READ`: READ, DESCRIBE\_CONFIGS
    * `WRITE`: READ, WRITE, DESCRIBE\_CONFIGS
  * For Confluent Cloud and Confluent Platform clusters with their provider settings set to have role bindings enabled, the following RBAC role bindings will be created for the service account instead:
    * `READ`: `DeveloperRead`
    * `WRITE`: `DeveloperRead`, `DeveloperWrite`
    * There's also an implicit permission granted for subjects when a topic permission is given. If there's `write` access to a topic called **example**, the service account will also receive `write` access to the subject **example**.

Find out how to migrate RBAC role bindings to [Confluent Cloud](/guide/tutorials/migrate-confluent-cloud-rbac) or [Confluent Platform](/guide/tutorials/migrate-confluent-platform-rbac).

## ApplicationGroup

Creates an application group to directly reflect how your application operates. You can create as many application groups as required - to restrict or enable the different teams that use Console. For example:

* the support team can only have `Read` access in production environment,
* the devOps team has extended access across all environments,
* the engineering team is granted higher permissions in dev environment only.

To simplify application group provisioning at scale, you can apply an [ApplicationGroupTemplate](#applicationgrouptemplate) from the **Resource access** tab as a reusable starting point.

* **API key(s):** "AdminToken", "AppToken"
* **Managed with:** API, CLI, UI, TF
* **Labels support:** "MissingLabelSupport"

<Tabs>
  <Tab title="CLI">
    #### Example

    ```yaml theme={null}
    # Permissions granted to Console users in the application
    ---
    apiVersion: self-serve/v1
    kind: ApplicationGroup
    metadata:
      application: "clickstream-app"
      name: "clickstream-support"
    spec:
      displayName: Support Clickstream
      description: |
        Members of the Support Group are allowed:
          Read access on all the resources
          Can restart owned connectors
          Can reset offsets
          Can request and grant access
      instancePermissions:
        - appInstance: clickstream-app-dev
          permissions: ["applicationInstancePermissionRequestAccess", "applicationInstancePermissionGrantAccess"]
      permissions:
        - appInstance: clickstream-app-dev
          resourceType: TOPIC
          patternType: "LITERAL"
          name: "*" # All owned and subscribed topics
          permissions: ["topicViewConfig", "topicConsume"]
        - appInstance: clickstream-app-dev
          resourceType: CONSUMER_GROUP
          patternType: "LITERAL"
          name: "*" # All owned consumer groups
          permissions: ["consumerGroupCreate", "consumerGroupReset", "consumerGroupDelete", "consumerGroupView"]
        - appInstance: clickstream-app-dev
          connectCluster: local-connect
          resourceType: CONNECTOR
          patternType: "LITERAL"
          name: "*" # All owned connectors
          permissions: ["kafkaConnectViewConfig", "kafkaConnectStatus", "kafkaConnectRestart"]
      members:
        - user1@company.org
        - user2@company.org
      externalGroups:
        - GP-COMPANY-CLICKSTREAM-SUPPORT
      externalGroupRegex:
        - GP-COMPANY*
    ```

    **ApplicationGroup checks:**

    * `spec.instancePermissions` (optional) is a list of instance-level permission assignments.
    * `spec.instancePermissions[].appInstance` has to be an application instance associated with this application (`metadata.application`).
    * `spec.instancePermissions[].permissions` is a list of valid instance permissions: `applicationInstancePermissionRequestAccess`, `applicationInstancePermissionGrantAccess`, `applicationInstancePermissionProposeAccess`, `applicationInstanceApiKeyManage`, `applicationInstanceChargebackView`, `alertManage`, `serviceAccountManage`.
    * `spec.permissions[].appInstance` has to be an application instance associated with this application (`metadata.application`).
    * `spec.permissions[].resourceType` can be `TOPIC`, `SUBJECT`, `CONSUMER_GROUP` or `CONNECTOR`. When set to `CONNECTOR`, an additional field `spec.permissions[].connectCluster` is mandatory and has to be a valid *KafkaConnectCluster* name.
    * `spec.permissions[].patternType` can be `PREFIXED` or `LITERAL`.
    * `spec.permissions[].name` has to reference any 'sub-resource' of `metadata.appInstance` or any subscribed topic. Use `*` to include to all owned and subscribed resources associated to this *appInstance*.
    * `spec.permissions[].permissions` are valid permissions.
    * `spec.members` has to be an email addresses of members that you want to add to this group.
    * `spec.externalGroups` a list of LDAP or OIDC groups to sync with this Console group. Members added this way will not appear in `spec.members` list.
    * `spec.externalGroupRegex` a list of regex patterns that can match to a series of LDAP or OIDC groups to sync with this Console group. Members added this way will not appear in `spec.members` list.
  </Tab>

  <Tab title="Terraform">
    [View Terraform documentation](https://registry.terraform.io/providers/conduktor/conduktor/latest/docs/resources/console_application_group_v1) <Icon icon="up-right-from-square" />
  </Tab>
</Tabs>

**Side effects:**

* Console
  * Members of the *ApplicationGroup* are given the associated resource permissions (`spec.permissions`) in the UI over the resources.
  * Members with `instancePermissions` can perform the corresponding actions on the application instance:
    * `applicationInstancePermissionRequestAccess`: request access to other teams' topics through the Topic Catalog.
    * `applicationInstancePermissionGrantAccess`: approve or grant access requests to owned topics.
    * `applicationInstancePermissionProposeAccess`: propose access via GitOps — view incoming access requests and copy the CLI snippet, without being able to approve them directly from the UI.
    * `applicationInstanceApiKeyManage`: create and delete application instance API keys.
    * `applicationInstanceChargebackView`: view Chargeback cost data for the application instance.
    * `alertManage`: view, create, edit and delete alerts owned by the application instance.
    * `serviceAccountManage`: manage service accounts for the application instance.
  * Members of the LDAP or OIDC groups will be automatically added or removed upon login.

### Instance permissions reference

Instance permissions control application-instance-level actions, separate from resource-level permissions.

| Permission                                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `applicationInstancePermissionRequestAccess` | Request read or write access to topics owned by other applications through the Topic Catalog.                                                                                                                                                                                                                                                                                                                                                    |
| `applicationInstancePermissionGrantAccess`   | Approve or grant incoming access requests to topics owned by this application instance.                                                                                                                                                                                                                                                                                                                                                          |
| `applicationInstancePermissionProposeAccess` | Propose access via GitOps — view incoming access requests and copy the CLI snippet, without being able to approve them directly from the UI.                                                                                                                                                                                                                                                                                                     |
| `applicationInstanceApiKeyManage`            | Create and delete API keys for this application instance.                                                                                                                                                                                                                                                                                                                                                                                        |
| `applicationInstanceChargebackView`          | View [Chargeback](/guide/conduktor-concepts/chargeback) cost data for this application instance. Members see only the applications they hold this permission on.                                                                                                                                                                                                                                                                                 |
| `alertManage`                                | View, create, edit, delete, enable and disable [alerts](/guide/monitor-brokers-apps/alerts) owned by this application instance. Doesn't cover alerts owned by a group or an individual user. To create a topic, consumer group or Kafka Connect alert, members also need the matching `Describe` permission on the underlying resource. Data quality alerts can't be owned by an application instance, so this permission never applies to them. |
| `serviceAccountManage`                       | Manage service accounts for this application instance (requires [application-managed service account](#application-managed-service-account) to be enabled).                                                                                                                                                                                                                                                                                      |

## Application-managed service account

The Self-service service account is not configured by the central team at the `ApplicationInstance` level.

Instead, the central platform team decides to delegate this responsibility to the application team, which needs to declare their own service account(s) and associated ACLs within the limits of what the `ApplicationInstance` is allowed to do.

* **API key(s):** AppToken
* **Managed with:** API, CLI
* **Labels support:** Full

```yaml theme={null}
---
apiVersion: v1
kind: ServiceAccount
metadata:
  appInstance: "clickstream-app-dev"
  cluster: shadow-it
  name: clickstream-sa
spec:
  authorization:
    type: KAFKA_ACL
    acls:
      - type: TOPIC
        name: click.event-stream.avro
        patternType: PREFIXED
        operations:
          - Write
          - Read
      - type: CLUSTER
        name: kafka-cluster
        patternType: LITERAL
        operations:
          - DescribeConfigs
      - type: CONSUMER_GROUP
        name: cg-name
        patternType: LITERAL
        operations:
          - Read
      - type: TRANSACTIONAL_ID
        name: clickstream-txn-id
        patternType: LITERAL
        operations:
          - Write
          - Describe
```

**Service account checks:**
The checks are the same as the [service account](/guide/reference/kafka-reference/#service-account) resource with additional **limitations**:

* a service account is claimed by the first application team declaring it.
* ACL operations that are not aligned with Self-service approach or would prevent configured policies to apply, are not allowed on service account:
  * **Topic**: topic name has to refer to a topic owned by *ApplicationInstance* or allowed by granted *ApplicationInstancePermission*: `Describe`, `DescribeConfigs`, `Read`, `Write`.
  * **Consumer group**: resource name has to refer to a consumer group owned by *ApplicationInstance* with `Describe` and `Read`.
  * **Cluster**: `Describe` and `DescribeConfigs`.
  * **Transactional Id**: resource name has to refer to a transactional ID owned by *ApplicationInstance* with `Write` and `Describe`.
  * **Delegation token**: out of scope, has to be assigned by a central team.
* When an *ApplicationInstancePermission* is removed, we don't drop the ACLs on the *ServiceAccount*. Instead, consecutive CLI calls to apply the resource will fail.

## ResourcePolicy

Resource policies enforce rules on resources across your Kafka infrastructure. They can be linked at the [KafkaCluster](/guide/reference/console-reference#kafkacluster), [KafkaConnectCluster](/guide/reference/console-reference#kafkaconnectcluster), [Application](#application) or [ApplicationInstance](#applicationinstance) level. Typical use cases include:

* enforcing [Topic](/guide/reference/kafka-reference#topic) partition counts, replication factor and min in-sync replicas
* enforcing [Topic](/guide/reference/kafka-reference#topic) naming conventions
* enforcing business metadata conventions using [resource labels](/guide/use-cases/self-service#resource-labels)
* restricting allowed [Connector](/guide/reference/kafka-reference#connector) plugin classes or capping `tasks.max`
* enforcing [Subject](/guide/reference/kafka-reference#subject) compatibility levels or naming conventions
* restricting what permissions can be given in [ApplicationGroups](#applicationgroup) created by an Application
* restricting what [ApplicationInstancePermission](#applicationinstancepermission) grants are allowed on a cluster

<Warning>
  Resource policies are not applied automatically. You have to explicitly link them to an [ApplicationInstance](#applicationinstance) or [Application](#application) with `spec.policyRef`, or to a [KafkaCluster](/guide/reference/console-reference#kafkacluster) or [KafkaConnectCluster](/guide/reference/console-reference#kafkaconnectcluster) with `spec.policiesRef`.
</Warning>

* **API key(s):** AdminToken
* **Managed with:** API, CLI, TF
* **Labels support:** Partial

<Tabs>
  <Tab title="CLI">
    ```yaml theme={null}
    ---
    apiVersion: self-serve/v1
    kind: ResourcePolicy
    metadata:
        name: "generic-dev-topic"
        labels:
            business-unit: delivery
    spec:
        targetKind: Topic
        description: A policy to check some basic rule for a topic
        rules:
            - condition: spec.replicationFactor == 3
              errorMessage: replication factor should be 3
            - condition: int(string(spec.configs["retention.ms"])) >= 60000 && int(string(spec.configs["retention.ms"])) <= 3600000
              errorMessage: retention should be between 1m and 1h
    ---
    apiVersion: self-serve/v1
    kind: ResourcePolicy
    metadata:
        name: "clickstream-naming-rule"
        labels:
            business-unit: delivery
    spec:
        targetKind: Topic
        description: A policy to check some basic rule for a topic
        rules:
            - condition: metadata.name.matches("^click\\.[a-z0-9-]+\\.(avro|json)$")
              errorMessage: topic name should match ^click\.(?<event>[a-z0-9-]+)\.(avro|json)$
            - condition: metadata.labels["data-criticality"] in ["C0", "C1", "C2"]
              errorMessage: data-criticality should be one of C0, C1, C2
    ```

    **SelfServicePolicy checks:**

    * `spec.targetKind` can be `Topic`, `Connector`, `Subject`, `ApplicationGroup` or `ApplicationInstancePermission`.
    * `spec.rules[].condition` is a valid [CEL expression](https://cel.dev) <Icon icon="up-right-from-square" /> and will be evaluated against the resource.
    * `spec.rules[].errorMessage` is a string that will be displayed when the condition is not met.

    [Use this CEL playground](https://playcel.undistro.io/) <Icon icon="up-right-from-square" /> to test your expressions.

    With the two policies declared, the following topic resource will succeed validation:

    ```yaml theme={null}
    ---
    apiVersion: kafka/v2
    kind: Topic
    metadata:
      cluster: shadow-it
      name: click.event-stream.avro  # Checked by metadata.name.matches("^click\\.[a-z0-9-]+\\.(avro|json)$")
      labels:
        data-criticality: C2         # Checked by metadata.labels["data-criticality"] in ["C0", "C1", "C2"]
    spec:
      replicationFactor: 3           # Check by spec.replicationFactor == 3
      partitions: 3
      configs:
        cleanup.policy: delete
        retention.ms: '60000'        # Check int(string(spec.configs["retention.ms"])) >= 60000 && int(string(spec.configs["retention.ms"])) <= 3600000
    ```
  </Tab>

  <Tab title="Terraform">
    [View Terraform documentation](https://registry.terraform.io/providers/conduktor/conduktor/latest/docs/resources/console_resource_policy_v1) <Icon icon="up-right-from-square" />
  </Tab>
</Tabs>

### Restricting ApplicationGroup permissions

After the [Console 1.45.0 owner group migration](/guide/release-notes#self-service-owner-group-permissions-v1-45-0), application teams manage their own ApplicationGroups and decide which `permissions` and `instancePermissions` to assign. Platform teams can use a `ResourcePolicy` with `targetKind: ApplicationGroup` to enforce guardrails on what teams are allowed to grant themselves.

A common pattern is **GitOps-only approval**: the platform team mandates that approving access requests and any write operation on resources has to go through the CLI (typically from a peer-reviewed pull request), not directly in the Console UI. The policy below restricts ApplicationGroups to a read-only resource permission set (`topicViewConfig`, `consumerGroupView`, `subjectView`, `kafkaConnectorStatus`) and to three safe instance permissions: `applicationInstancePermissionRequestAccess`, `applicationInstancePermissionProposeAccess` and `applicationInstanceApiKeyManage`. Permissions like `topicDelete`, `topicEditConfig`, `applicationInstancePermissionGrantAccess` or `serviceAccountManage` are not allowed, so any approval, edit or delete has to happen through GitOps.

```yaml theme={null}
---
apiVersion: self-serve/v1
kind: ResourcePolicy
metadata:
  name: applicationgroup-gitops-only
  labels:
    business-unit: delivery
spec:
  targetKind: ApplicationGroup
  description: Restrict ApplicationGroups to read-only resource permissions and GitOps-only access approval
  rules:
    - condition: spec.permissions.all(p, p.permissions.all(x, x in ["topicViewConfig", "consumerGroupView", "subjectView", "kafkaConnectorStatus"]))
      errorMessage: resource permissions are limited to topicViewConfig, consumerGroupView, subjectView and kafkaConnectorStatus. Permissions like topicDelete, topicEditConfig, consumerGroupReset and kafkaConnectorEditConfig have to go through the CLI
    - condition: '!has(spec.instancePermissions) || spec.instancePermissions.all(p, p.permissions.all(x, x in ["applicationInstancePermissionRequestAccess", "applicationInstancePermissionProposeAccess", "applicationInstanceApiKeyManage"]))'
      errorMessage: applicationInstancePermissionGrantAccess and serviceAccountManage are not allowed. Use applicationInstancePermissionProposeAccess and approve access via GitOps
    - condition: size(metadata.members) == 0
      errorMessage: spec.members not allowed. Use external group mapping instead
```

With this policy linked through `spec.policyRef`, an ApplicationGroup like the following will pass validation — members can list and view resources, request and propose access, and manage API keys, but cannot approve incoming requests or perform any edit or delete from the UI:

```yaml theme={null}
---
apiVersion: self-service/v1
kind: ApplicationGroup
metadata:
  application: clickstream-app
  name: clickstream-support
spec:
  displayName: Support Clickstream
  description: Read-only access with GitOps-only approval
  instancePermissions:
    - appInstance: clickstream-app-dev
      permissions: ["applicationInstanceApiKeyManage", "applicationInstancePermissionProposeAccess", "applicationInstancePermissionRequestAccess"]
  permissions:
    - appInstance: clickstream-app-dev
      resourceType: TOPIC
      patternType: LITERAL
      name: "*"
      permissions: ["topicViewConfig"]
    - appInstance: clickstream-app-dev
      resourceType: CONSUMER_GROUP
      patternType: LITERAL
      name: test
      permissions: ["consumerGroupView"]
    - appInstance: clickstream-app-dev
      resourceType: SUBJECT
      patternType: LITERAL
      name: test2
      permissions: ["subjectView"]
    - appInstance: clickstream-app-dev
      connectCluster: connector
      resourceType: CONNECTOR
      patternType: LITERAL
      name: acme-connector
      permissions: ["kafkaConnectorStatus"]
  externalGroups:
    - GP-COMPANY-CLICKSTREAM-SUPPORT
```

Adding any other permission — for example `topicDelete`, `topicEditConfig`, `applicationInstancePermissionGrantAccess` or `serviceAccountManage` — will be rejected when the ApplicationGroup is applied, directing the team to make those changes through their GitOps workflow instead.

### Keeping Chargeback data centralized

The `applicationInstanceChargebackView` instance permission lets application owners see the Chargeback costs of their own applications. Because application teams manage their own ApplicationGroups, a team can assign this permission to itself. Platform teams that want cost data to stay centralized can block it with a single rule:

```yaml theme={null}
---
apiVersion: self-serve/v1
kind: ResourcePolicy
metadata:
  name: no-self-granted-chargeback
spec:
  targetKind: ApplicationGroup
  description: Only platform administrators can grant Chargeback access
  rules:
    - condition: '!has(spec.instancePermissions) || !spec.instancePermissions.exists(ip, ip.permissions.exists(p, p == "applicationInstanceChargebackView"))'
      errorMessage: Chargeback view can only be granted by a platform administrator
```

With this policy linked through `spec.policyRef`, an application owner who adds `applicationInstanceChargebackView` to an ApplicationGroup gets the error message above. Other instance permissions are unaffected. Platform administrators bypass ResourcePolicy validation, so they can still grant the permission to teams that ask for it.

To take the opposite approach and let teams opt themselves in, don't link a policy — the permission isn't granted by default, so teams have to assign it deliberately.

### Restricting application instance permission grants

From Console 1.47.0, a `ResourcePolicy` with `targetKind: ApplicationInstancePermission` validates every application instance permission grant against your rules. Use it to restrict what permissions Self-service applications can grant — for example, to block WRITE access on topics:

```yaml theme={null}
---
apiVersion: self-serve/v1
kind: ResourcePolicy
metadata:
  name: no-topic-write-permission-policy
spec:
  targetKind: ApplicationInstancePermission
  description: No WRITE access may be granted on TOPIC resources.
  rules:
    - condition: "spec.resource.type != 'TOPIC' || spec.userPermission != 'WRITE'"
      errorMessage: "WRITE permission cannot be granted to a user on a TOPIC resource."
    - condition: "spec.resource.type != 'TOPIC' || spec.serviceAccountPermission != 'WRITE'"
      errorMessage: "WRITE permission cannot be granted to a service account on a TOPIC resource."
```

Conditions are evaluated against the [ApplicationInstancePermission](#applicationinstancepermission) resource, so rules can reference `spec.resource.type`, `spec.resource.name`, `spec.resource.patternType`, `spec.userPermission`, `spec.serviceAccountPermission` and `spec.grantedTo`. Each condition has to evaluate to true for the grant to be allowed — read the example as "pass unless it's a TOPIC and the permission is WRITE".

A grant can also carry its permission in the `spec.permission` shorthand, which sets the user and service account levels at once. Console expands the shorthand into `userPermission` and `serviceAccountPermission` before rules run, so rules on those two fields also catch grants that used the shorthand. Don't reference `spec.permission` in a condition: the field never exists at evaluation time, so the condition fails to evaluate and the rule rejects every grant it applies to — including grants the policy should allow. Platform administrators bypass ResourcePolicy validation.

Unlike Topic, Connector and Subject policies, ApplicationInstancePermission policies can't be referenced from an Application or ApplicationInstance `policyRef` list. They're cluster scoped: link them to a [KafkaCluster](/guide/reference/console-reference#kafkacluster) with `spec.policiesRef` through GitOps, or assign them manually from the cluster management screen under **Cluster resource policies**.

### Moving from TopicPolicy

To replicate the behavior of the *TopicPolicy* with the `ResourcePolicy`, here's how you can transform the different policies:

#### Range constraint

Before:

```yaml theme={null}
spec.configs.retention.ms:
  constraint: Range
  max: 3600000
  min: 60000
```

After: [open in playground](https://playcel.undistro.io/?content=H4sIAAAAAAAAA4VTXWvcMBD8K1s%2FJDmIfSGBPpimEMoVWq5t6FcodR%2F25D2fOGklJNmXo%2FS%2Fd6UzpH0otV%2FEzs7s7Fj%2BWSkyVVstl%2FBARjlLkBykHcGr1RruDR6H4Ebun3UsLX%2FXQEdABs2JAqqkJxKNzV2MZDfmCN4dKFAPxJMOji1xytr06I0LJMw%2Bnynoghx02p3mOmsdw%2BrRB4pRy3GNPIw4EFzI%2FEWTnRQ3NTwEnQiObgzFGj1xdjJ6bvoSqQhjIASB8jnqnmDrgpj3Y4IeE17KGb7dvVuDlN9%2B%2BvA%2B4xbTrHKfleH848jnZYsJzYj%2FGI4Dao6pTHoaMAut5v0zTzljSJITjtuKAlpvKM7GotcBM9bJKyFfRE%2BqUY63eojfuypQkuAEb2zsqh8LeHkLz6%2FkgbMzKP0paB7%2BR1vAi1u4KcSr6rLKTt9kz3Ip6rruGL3%2BSiHv1cIet3tcTtcd7zX3LXx2XqtOPm3CzGs7BlBmjHIhWog77N2h1ilXGS21gmm1b2gSA7W4I7QNTsHlBoMbMrEoAPTOSoJz%2F6nxBKD3yvVFad9x3qwwAnnpLGG9lpvoZPpNrnsMSedqnAtzDPMYq7mRnI%2BsmllB%2BrrquqtOuDKEPPrGO8GOLfRkJLwT9meMmVQCzETJ0IpFiS%2F%2FWb9%2BA%2Bwuz6hhAwAA) <Icon icon="up-right-from-square" />

```yaml theme={null}
- condition: int(string(spec.configs["retention.ms"])) >= 60000 && int(string(spec.configs["retention.ms"])) <= 3600000
  errorMessage: retention should be between 1m and 1h
```

#### Value constraint

Before:

```yaml theme={null}
spec.replicationFactor:
  constraint: OneOf
  values: ["3"]
```

After: [open in playground](https://playcel.undistro.io/?content=H4sIAAAAAAAAA3VTTWvcMBD9K1Nf0kLsLQn0YMghlC20bNvQr1DwZVae9QpLIyHJ3iyl%2F70j2W0oIdZlmDfvvfnAvypFpmqrzQbuyShnCZKDdCR4u93BncHzENzE%2FYuOpeT%2FHOgIyKA5UUCV9Eyisb%2BNkezenMG7EwXqgXjWwbElTlmbHrxxgYTZ55iCLshJp%2BPi66x1DNsHHyhGLeEOeZhwIHgp%2Fq%2Ba3Enppob7oBPB2U2htEaPnKNYr0XfIxVhDIQgUI6j7gkOLkjzfkrQY8JLieHn7ccdSPrD18%2BfMm4xrSp3WRkuvkx8UaaY0Uz4jDkOqDmm4vRosApt1%2FkzTzljSDYnHHcQBbTeUFwbi14HzFgnL3pSTSBvtCq5d7Jwqbq5gevqssry77ORXLKu647R6x8UcjMtjHgYcTNfdTxq7lv45rxWndwjYea1HQMoM0W5YgvxiL071TrlLKOlVjCtxoZmuVIdkyzRNjgHlwsM7snEogDQOytjr%2FVL4QKg98r1RWlcJimMJ9O0cJ3zHkPSORvXhHJ80MNfG6u5keWc%2Bd8%2BpK6rrrpqwZUh5Mk33gl2bqEnQ4kWLEjEWbqxhfTmtXyZKDu00qKsL%2F8Ov%2F8A22by1BYDAAA%3D) <Icon icon="up-right-from-square" />

```yaml theme={null}
- condition: spec.replicationFactor == 3
  errorMessage: replication factor should be 3
```

#### In list constraint

Before:

```yaml theme={null}
metadata.labels.data-criticality:
  constraint: OneOf
  values: ["C0", "C1", "C2"]
```

After: [open in playground](https://playcel.undistro.io/?content=H4sIAAAAAAAAA3VTTY%2FTQAz9KyaXBalJl0XikNtqVSRQgRUfu0KEgztxU6vzpZlJuhXiv%2BOZBFUgkRzi%2BNnPz2%2BSn5UiXbXVeg2PpJUzBMlBOhDcbbZwr%2FE8BDfa%2FllnpeTvHHAEtMA2UUCVeCLh2N3GSGanz%2BDdiQL1QHbi4KwhmzI3PXntAklnn2MKXJATp8M81xnjLGyefKAYWcIt2mHEgeC5zH%2FRZCVFTQ2PgRPB2Y2hSKNLz0FGL0VfIxViDIQgUI4j9wR7F0S8HxP0mHAlMXy7fb8FSb%2F7%2FPFDxg2mheU%2BM8PVp9FelS0m1CP%2BZzgOyDamMukyYCHaLPvnPuW0JnFOetxeGNB4TXERFj0HzFgnt6GEmaTRuCMdv3dVfquVGMAKNadzV%2F3IGwhyd91VK5Dny%2BV5I1i1Kh1vsxw577quO4ueHyhkyS0ccX%2FE9XTT2SPbvoUvzrO6zG07C6D0GOWsW4gH7N2p5pSzFg21grE6NjTJWdYxidWmwSm4XDBLLgwAvTNizlI%2FF84Aeq9cX5iOS%2Bk%2FG7Ygm4ktpApXIC8cxaE38vk50fUq5z2GxDkbl4Ryds%2FDHwGGbSPmnq1qFgap6yrxaMaVJrSjb7wTTGb2pCnRjAWJbKZuTGl6fS1XbhR3jYgXY%2FPv9Os311aOeVYDAAA%3D) <Icon icon="up-right-from-square" />

```yaml theme={null}
- condition: metadata.labels["data-criticality"] in ["C0", "C1", "C2"]
  errorMessage: data-criticality should be one of C0, C1, C2
```

#### Regex constraint

Before:

```yaml theme={null}
metadata.name:
  constraint: Match
  pattern: ^click\.(?<event>[a-z0-9-]+)\.(avro|json)$
```

After: [open in playground](https://playcel.undistro.io/?content=H4sIAAAAAAAAA3VTbYsTMRD%2BK%2BMiXIu32%2BMEwf12SAWl6uHbIa7CNDttY5NJSLLb66n%2F3Um6Uvzg7pfpPPO8ZLL9WSkyVVstFnBHRjlLkBykHcGL5QpuDR63wQ3cP%2BpYRv7tgY6ADJoTBVRJjyQa65sYya7NEbw7UKAeiEcdHFvilLXp3hsXSJh9rinoghx02p18nbWOYXnvA8WopVwhbwfcEszEf97kJCVNDXdBJ4KjG0KJRmfOTqynoU%2BRijAGQhAo11H3BBsXJLwfEvSY8FJq%2BHLzZgXSfv3h3duMW0yTym1Whov3A1%2BUU4xoBvyPOW5Rc0zF6WwwCS2n82eecsaQbE44biMKaL2hOAWLXgfMWCevpYRZpGG01EgstaM466rvymi17%2BRpvmL9cFU%2Fr789KT9nOAb360d0PH%2FcVfPqssr8VzmO3Hdd1x2j158p5Mgt7HGzx8V43fFec9%2FCR%2Be1Ovu2HQMoM0S56xbiDnt3qHXK3ZyohZKjoVHuso5JVm2bHCAPGFyTiUUBoHdWljPNnwZPAHqvXF%2BU9p0cnlRhBPIyWfbwUj4yJ%2B5Pc99jSDp349RQjjd6%2B9fGam5khUdWzaQgc1113VUnXBlCHnzjnWDHFnoylOiEBak4Sze2kJ5dyZOJskMrEWV9%2BU%2Fz%2Bw9HD%2BgYPAMAAA%3D%3D) <Icon icon="up-right-from-square" />

```yaml theme={null}
- condition: metadata.name.matches("^click\\.[a-z0-9-]+\\.(avro|json)$")
  errorMessage: topic name should match ^click\.(?<event>[a-z0-9-]+)\.(avro|json)$
```

#### Tips for CEL expressions

There are multiple things to consider when writing CEL expressions in the context of resource policies:

* For field-like configuration value/label (that you don't know the type of) and want to compare to a number, convert it to a *string* and then to an *int* like this: `int(string(spec.configs["retention.ms"]))`.

* For field key that contains dots `.` or dashes `-`, you have to access them with the `[]` operator: `metadata.labels["data-criticality"]`.

* For field-like label key/config that can be absent, we recommend adding a check to see if the field is present: `has(metadata.labels.criticality) && {your condition}`. If the field has a dot or dash, use `"retention.ms" in spec.configs && {your condition}`.

## TopicPolicy

Topic policies force application teams to conform to topic rules, set at their `ApplicationInstance` level. Typical use cases include:

* safeguarding from invalid or risky topic configuration
* enforcing a naming convention
* enforcing metadata

<Warning>
  TopicPolicy will be deprecated in a future release. Please use [ResourcePolicy](#resourcepolicy) instead.
</Warning>

* **API key(s):** AdminToken
* **Managed with:** API, CLI, TF
* **Labels support:** Partial

<Tabs>
  <Tab title="CLI">
    ```yaml theme={null}
    ---
    apiVersion: self-serve/v1
    kind: TopicPolicy
    metadata:
      name: "generic-dev-topic"
    spec:
      policies:
        metadata.labels.data-criticality:
          constraint: OneOf
          values: ["C0", "C1", "C2"]
        spec.configs.retention.ms: 
          constraint: Range
          max: 3600000
          min: 60000
        spec.replicationFactor:
          constraint: OneOf
          values: ["3"]
    ---
    apiVersion: self-serve/v1
    kind: TopicPolicy
    metadata:
      name: "clickstream-naming-rule"
    spec:
      policies:
        metadata.name:
          constraint: Match
          pattern: ^click\.(?<event>[a-z0-9-]+)\.(avro|json)$
    ```

    **TopicPolicy checks:**

    * `spec.policies` require YAML paths that are paths to the [topic resource YAML](/guide/reference/kafka-reference#topic). For example:
      * `metadata.name` to create constraints on topic name
      * `metadata.labels.<key>` to create constraints on topic label `<key>`
      * `spec.partitions` to create constraints on partitions number
      * `spec.replicationFactor` to create constraints on replication factor
      * `spec.configs.<key>` to create constraints on topic config `<key>`
    * `spec.policies.<key>.constraint` can be `Range`, `OneOf` or `Match`

    With the two topic policies declared above, the following topic resource would succeed validation:

    ```yaml theme={null}
    ---
    apiVersion: kafka/v2
    kind: Topic
    metadata:
      cluster: shadow-it
      name: click.event-stream.avro  # Checked by Match ^click\.(?<event>[a-z0-9-]+)\.(avro|json)$ on `metadata.name`
      labels:
        data-criticality: C2         # Checked by OneOf ["C0", "C1", "C2"] on `metadata.labels.data-criticality`
    spec:
      replicationFactor: 3           # Checked by OneOf ["3"] on `spec.replicationFactor`
      partitions: 3
      configs:
        cleanup.policy: delete
        retention.ms: '60000'        # Checked by Range(60000, 3600000) on `spec.configs.retention.ms`
    ```
  </Tab>

  <Tab title="Terraform">
    [View Terraform documentation](https://registry.terraform.io/providers/conduktor/conduktor/latest/docs/resources/console_topic_policy_v1) <Icon icon="up-right-from-square" />
  </Tab>
</Tabs>

### Topic policy constraints

There are currently five available constraints:

* `Range` validates a range of numbers
* `OneOf` validates against a list of predefined options
* `NoneOf` rejects a value if it matches any item in the list
* `Match` validates using a regex (regular expression)
* `AllowedKeys` limits a set of keys in the dictionaries

#### Range

Validates whether the property belongs to a range of numbers (inclusive):

```yaml theme={null}
spec.configs.retention.ms:
  constraint: "Range"
  min:   3600000 # 1 hour in ms
  max: 604800000 # 7 days in ms
```

Validation will succeed with these inputs:

* 3600000 (min)
* 36000000 (between min and max)
* 604800000 (max)

Validation will fail with these inputs:

* 60000 (below min)
* 999999999 (above max)

#### OneOf

Validates whether the property is one of the expected values:

```yaml theme={null}
spec.configs.cleanup.policy:
  constraint: OneOf
  values: ["delete", "compact"]
```

Validation will succeed with these inputs:

* `delete`
* `compact`

Validation will fail with these inputs:

* `delete, compact` (valid in Kafka but not allowed by policy)
* `deleet` (typo)

#### Match

Validates the property against a regex:

```yaml theme={null}
metadata.name:
  constraint: Match
  pattern: ^wikipedia\.(?<event>[a-z0-9]+)\.(avro|json)$
```

Validation will succeed with these inputs:

* `wikipedia.links.avro`
* `wikipedia.products.json`

Validation will fail with these inputs:

* `notwikipedia.products.avro2`: `^` and `$` prevents anything before and after the pattern
* `wikipedia.all-products.avro`: `(?<event>[a-z0-9]+)` prevents anything else than lowercase letters and digits

#### AllowedKeys

Validates whether the keys are within an allowed key list. Applies to dictionary type (Key/Value maps). Can be used on `spec.configs` and `metadata.labels`.

```yaml theme={null}
spec.configs:
  constraint: AllowedKeys
  keys:
    - retention.ms
    - cleanup.policy
```

Validation will succeed with this input:

```yaml theme={null}
---
apiVersion: kafka/v2
kind: Topic
metadata:
  cluster: shadow-it
  name: click.event-stream.avro
spec:
  replicationFactor: 3
  partitions: 3
  configs:
    cleanup.policy: delete
    retention.ms: '60000'
```

Validation will fail with this input (`min.insync.replicas` is not an allowed key in `spec.configs`):

```yaml theme={null}
---
apiVersion: kafka/v2
kind: Topic
metadata:
  cluster: shadow-it
  name: click.event-stream.avro
spec:
  replicationFactor: 3
  partitions: 3
  configs:
    min.insync.replicas: '2' # Not in AllowedKeys
    cleanup.policy: delete
    retention.ms: '60000'
```

#### Optional flag

Constraints can be marked as optional. In this scenario, the constraint will only be validated if the field exists. E.g.:

```yaml theme={null}
spec.configs.min.insync.replicas:
  constraint: ValidString
  optional: true
  values: ["2"]
```

This object will pass the validation:

```yaml theme={null}
---
apiVersion: kafka/v2
kind: Topic
metadata:
  cluster: shadow-it
  name: click.event-stream.avro
spec:
  replicationFactor: 3
  partitions: 3
  configs:
    cleanup.policy: delete
    retention.ms: '60000'
```

This object will fail the validation due to a new incorrect definition of `insync.replicas`:

```yaml theme={null}
---
apiVersion: kafka/v2
kind: Topic
metadata:
  cluster: shadow-it
  name: click.event-stream.avro
spec:
  replicationFactor: 3
  partitions: 3
  configs:
    min.insync.replicas: 3
    cleanup.policy: delete
    retention.ms: '60000'
```

## TopicTemplate

Topic templates are admin-curated starting points for the Console **Create Topic** form. When an application team creates a topic, they can pick a template to pre-fill the partition count, replication factor, configs and labels, then adjust the values before they submit. Templates are suggestions, not rules — unlike a [ResourcePolicy](#resourcepolicy), they don't block anything. Pair the two to point teams at a sensible default and enforce the boundaries.

Typical use cases include:

* offering a high-throughput or compacted topic preset

* suggesting a naming convention with `{{placeholder}}` values the user replaces

* seeding the labels your governance model expects

* **API key(s):** AdminToken

* **Managed with:** API, CLI

* **Labels support:** Partial

<Tabs>
  <Tab title="CLI">
    ```yaml theme={null}
    ---
    apiVersion: v2
    kind: TopicTemplate
    metadata:
      name: high-partition-topic
      labels:
        category: template
    spec:
      displayName: "High Partition Topic"
      description: "Optimized for high-throughput workloads. 24 partitions, 3x replication, 7-day retention."
      defaults:
        metadata:
          name: "my-topic-{{department}}"
          labels:
            throughput: high
        spec:
          partitions: 24
          replicationFactor: 3
          configs:
            cleanup.policy: delete
            retention.ms: "604800000"
            min.insync.replicas: "2"
    ```

    **TopicTemplate checks:**

    * `metadata.name` is the unique identifier of the template.
    * `spec.displayName` is required and is the name shown in the **Create Topic** template picker.
    * `spec.description` (optional) appears under the display name to help users choose.
    * `spec.defaults` (all optional) pre-fill the form: `metadata.name`, `metadata.labels`, `spec.partitions`, `spec.replicationFactor` and `spec.configs`.
    * `spec.defaults.spec.partitions` and `spec.defaults.spec.replicationFactor`, when set, have to be greater than zero.
    * `{{placeholder}}` values are validated for syntax (`{{` followed by a letter or underscore, then letters, digits or underscores, then `}}`) but are never resolved on the server. The user replaces them in the form before they submit.

    **Side effects:**

    * The template appears in the **Create Topic** form for every user who can create topics. Selecting it pre-fills the form, and the user can still change any value.
    * Placeholders aren't substituted automatically. Console flags a topic name that still contains a `{{placeholder}}` and blocks submission until the user replaces it.
  </Tab>
</Tabs>

## ConnectorTemplate

Connector templates work like [TopicTemplate](#topictemplate) for the Console connector wizard. They pre-fill the connector class, configuration and labels so application teams start from a vetted setup instead of a blank form. Like topic templates, they're suggestions rather than rules.

Typical use cases include:

* offering a standard sink preset, for example an S3 or Elasticsearch sink, with sensible batching and retry defaults

* starting teams from an approved connector plugin class

* suggesting a naming convention with `{{placeholder}}` values the user replaces

* **API key(s):** AdminToken

* **Managed with:** API, CLI

* **Labels support:** Partial

<Tabs>
  <Tab title="CLI">
    ```yaml theme={null}
    ---
    apiVersion: v2
    kind: ConnectorTemplate
    metadata:
      name: s3-sink-standard
      labels:
        category: sink
    spec:
      displayName: "S3 Sink (Standard)"
      description: "Standard S3 sink connector with flush every 10,000 records or 60 seconds. Avro format on S3 storage."
      defaults:
        metadata:
          name: "s3-sink-{{department}}"
          labels:
            category: sink
        spec:
          class: io.confluent.connect.s3.S3SinkConnector
          config:
            tasks.max: "2"
            flush.size: "10000"
            rotate.interval.ms: "60000"
            storage.class: "io.confluent.connect.s3.storage.S3Storage"
            format.class: "io.confluent.connect.s3.format.avro.AvroFormat"
    ```

    **ConnectorTemplate checks:**

    * `metadata.name` is the unique identifier of the template.
    * `spec.displayName` is required and is the name shown in the connector wizard template picker.
    * `spec.defaults.spec.class` is required — the connector plugin class the template starts from. The wizard offers the template only when the user selects a matching connector class.
    * `spec.defaults.spec.config` holds the connector configuration to pre-fill. Keys that look like secrets — for example `password`, `secret`, `credentials` or `sasl.jaas.config` — are rejected, so templates never store sensitive values.
    * `{{placeholder}}` values follow the same syntax and pass-through rules as [TopicTemplate](#topictemplate).

    **Side effects:**

    * The template appears in the connector wizard when a user selects the matching connector class. Selecting it pre-fills the configuration, and the user can still change any value.
    * As with topic templates, Console blocks submission while a `{{placeholder}}` is unresolved.
  </Tab>
</Tabs>

## ApplicationGroupTemplate

Application group templates are administrator-managed sets of resource permissions for [ApplicationGroup](#applicationgroup) resources. They provide application teams with a consistent starting point while still allowing the resulting permissions to be reviewed and adjusted before saving.

Templates are instance-agnostic: they don't contain an application or application instance name. The user chooses one application instance, or all instances in the application, when applying the template in Console.

* **API key(s):** AdminToken
* **Managed with:** API, CLI
* **Labels support:** Partial

<Tabs>
  <Tab title="CLI">
    ```yaml theme={null}
    ---
    apiVersion: v2
    kind: ApplicationGroupTemplate
    metadata:
      name: standard-read-access
      labels:
        category: template
    spec:
      displayName: "Standard read access"
      description: "Read access to application-owned Kafka resources."
      defaults:
        permissions:
          - resourceType: TOPIC
            patternType: LITERAL
            name: "*"
            permissions:
              - topicViewConfig
              - topicConsume
          - resourceType: CONSUMER_GROUP
            patternType: LITERAL
            name: "*"
            permissions:
              - consumerGroupView
          - resourceType: SUBJECT
            patternType: LITERAL
            name: "*"
            permissions:
              - subjectView
    ```

    **ApplicationGroupTemplate checks:**

    * `metadata.name` is the unique identifier of the template and can contain lowercase letters, numbers, `_`, `-` and `.`.
    * `spec.displayName` is required and is the name shown in the Console template picker.
    * `spec.description` is optional.
    * `spec.defaults.permissions` is optional and contains resource permission entries. Application instance permissions aren't supported by templates.
    * `spec.defaults.permissions[].resourceType` can be `TOPIC`, `CONSUMER_GROUP`, `SUBJECT` or `CONNECTOR`.
    * `spec.defaults.permissions[].patternType` can be `LITERAL` or `PREFIXED`.
    * `spec.defaults.permissions[].name` identifies the resource or pattern. Use `*` to target all resources of that type owned by the selected application instance.
    * `spec.defaults.permissions[].connectCluster` applies only to `CONNECTOR` entries. Set it to a Kafka Connect cluster that is linked to every application instance where the entry will be applied; otherwise, adjust the generated entry before saving.
    * `spec.defaults.permissions[].permissions` must contain valid permission IDs for the selected resource type.

    Creating, updating and deleting templates requires the `platform.resource.template.edit` platform permission. Application group templates aren't available on the Free plan.

    Manage templates with the [Conduktor CLI](/guide/conduktor-in-production/automate/cli-automation) or [Console API](/guide/conduktor-in-production/automate/api-automation).
  </Tab>
</Tabs>
