> For the complete documentation index, see [llms.txt](https://docs.pandium.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations.md).

# Dynamic Configurations

Define dynamic configs in PANDIUM.yaml whose options are fetched per tenant from an API or database and stored in tenant metadata.

Typically when creating integration configurations, most fields will be static; that is, the options for the field will be the same for every tenant. However, it can be useful to include dynamic configurations, where the integration populates the options with values from an API or database, so they are different for each tenant.

To see an example of an integration that benefits from a dynamic config, and what it looks like for users on the Integration Hub or the In-App marketplace, review [this article](https://docs.pandium.com/integration-hub/managing-tenant-connection-settings#dynamic-configurations).

The per-tenant options live in [tenant metadata](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/tenant-metadata.md), and a config references them with `$ref: '#/metadata/<key>'`.

{% hint style="warning" %}
Manifest versions below 1.0 used a `definitions` entry filled from the saved stdout of the tenant's last init sync instead. Those versions are deprecated. See [Migrating to Manifest Version 1.0](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/migrating-to-manifest-version-1.0.md) to move an existing integration over.
{% endhint %}

## How It Works

A dynamic config takes three things:

1. A property in `metadata_schema.schema.properties` to hold the fetched options.
2. A property in `configs.schema.properties` that references it with `$ref: '#/metadata/<key>'`.
3. A flow in the integration code that fetches the options and prints them to stdout under that same key.

Pandium validates the run's stdout against `metadata_schema`, merges it into the tenant's metadata, and resolves the reference against that metadata each time it renders the Connection Settings page.

### Declaring the Metadata Property

Every **metadata** key a config references must be declared in `metadata_schema` **before a tenant can save values under that key**, since stdout is validated against that schema and undeclared keys are rejected (unless the schema explicitly allows additional properties). Declare it as an array:

```yaml
metadata_schema:
  schema:
    type: object
    properties:
      audiences:
        type: array
        items:
          type: string
```

### Referencing it from a Config

The config property points straight at the metadata key:

```yaml
configs:
  schema:
    properties:
      mailchimp_audience:
        $ref: '#/metadata/audiences'
```

The shape of the metadata value determines the control that renders:

* An array of strings resolves to an `enum`, one option per string.
* An array of `{const, title}` objects resolves to a `oneOf`, where `const` is the value the integration receives and `title` is the label shown to the user.

Static option lists do not go through metadata. A field whose options are the same for every tenant should still list them inline or reference a `definitions` entry, and should not go through metadata.

### Printing the Options from the Integration Script

The integration script provides the options by printing them to stdout as a JSON object. Any run can do this. The usual place is an init branch: when a user connects a new tenant through the In-App Marketplace, Pandium runs an init sync (run mode `init`) before showing the Connection Settings form, so options printed there are in metadata the first time the form renders. In the script, the run mode is available in the `PAN_CTX_RUN_MODE` context environment variable.

```javascript
if (pandium.context.runMode === 'init') {
    const mcAudiences = await mcClient.getMany('audience');
    const kLists = await kClient.getMany('audience');
    const stdout = {
        // Each key matches a property in metadata_schema, which configs reference as '#/metadata/<key>'.
        'audiences': mcAudiences.map(({ name }) => name),
        'lists': kLists.map((list) => ({const: list.id, title: list.name}))
    };
    // Print json string to stdout and end the script.
    console.log(JSON.stringify(stdout));
    return
}
```

Options that rarely change can be fetched once and left alone afterwards. Because metadata is merged rather than replaced, a later run that prints only a sync cursor does not wipe out the option lists an earlier run wrote.

Until a run has written metadata for a referenced key, the field renders in an unsynced state with its options disabled.

### Putting it All Together

Here is a full PANDIUM.yaml with one static config and two dynamic ones:

```yaml
version: 1.0
base: node:20.9.0
build: npm install --production && npm i --save-dev @types/node && npm run build
run: node .

configs:
  schema:
    type: object

    definitions:
      log_level_options:
        enum:
          - INFO
          - DEBUG
          - WARNING
          - ERROR

    properties:
      log_level:
        $ref: '#/definitions/log_level_options'
        default: INFO
        type: string
      mailchimp_audience:
        $ref: '#/metadata/audiences'
      klaviyo_list:
        $ref: '#/metadata/lists'

  uischema:
    type: VerticalLayout
    elements:

    - type: Section
      label: First Section
      elements:
      - type: Control
        label: Log Level
        scope: "#/properties/log_level"
      - type: HorizontalLayout
        elements:
        - type: Control
          label: MailChimp Audience
          scope: "#/properties/mailchimp_audience"
        - type: Control
          label: Klaviyo List
          scope: "#/properties/klaviyo_list"

metadata_schema:
  schema:
    type: object
    properties:
      audiences:
        type: array
        items:
          type: string
      lists:
        type: array
        items:
          type: object
```

`log_level` is static and lists its options in a `definitions` entry. `mailchimp_audience` and `klaviyo_list` are dynamic and reference metadata keys.

The run that populates those keys prints:

```log
[OUT] {"audiences":["Friends","Family","Acquaintances"],"lists":[{"title":"Prospective Customers","const":1},{"title":"One Time Customers","const":2},{"title":"Loyal Customers","const":3}]}
```

Once that metadata is written, this produces the following Connection Settings page on the Integration Hub:

<figure><img src="https://4017407078-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MfJn-9R_dn6dvcGNcdk%2Fuploads%2FqlQEZey4R8EbH326g1aK%2FConnection%20Settings%20my-first-tenant.png?alt=media&amp;token=3d45a63b-dfd9-494f-9318-15090463d6b2" alt=""><figcaption></figcaption></figure>

For more detailed instructions on how to implement dynamic configurations review [this tutorial](https://docs.pandium.com/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-2).


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
