---
sidebar_label: Special schema keys
toc_max_heading_level: 2
doc_id: f3b8b799-cbe3-4e13-ba16-beaf3c74cc62
description: Guide to special JSON schema keywords used across service, catalog, and scope specifications.
keywords:
  - JSON schema
  - schema keywords
  - services
  - catalogs
  - scopes
  - configuration
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Special JSON schema keys

Inside your **services** and **scopes** (and their links/actions), and **catalogs**, you define JSON schemas that specify the attributes of each specification.
Within those schemas, **a set of keys has special meaning across the platform**. Get familiar with them to implement specs effectively.


## List of special keys

These are the special keys you should know about:

| **Keyword**          | **Description**                                                                                      | **Applicable to**                          | **More**                                         |
| -------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------ |
| `secret`             | Mark attributes within a service or link as secret.                                                  | • Services <br />• Scopes                  | See the [secret](#secret) section.               |
| `export`             | Export an attribute value as an application parameter.                                               | • Services <br />• Scopes                  | See the [export](#export) section.               |
| `exportIgnore`       | Exclude attributes when exporting values to application parameters.                                  | • Services <br />• Scopes                  | See [export ignore](#export-ignore).             |
| `target`             | Map an action result to an attribute in a service, catalog, scope, or link.                          | • Services <br />• Scopes                  | See [target](#target).                           |
| `config`             | Use it to determine whether an attribute or action parameter retrieves its value from an NRN entry. | • Services <br />• Scopes                  | See [config](#config).                           |
| `additionalKeywords` | Populate schema keywords dynamically from runtime context (instance, application, services, scopes) or [external data](/docs/services/craft-a-service/external-context). | • Services <br />• Scopes                  | See [additional keywords](#additional-keywords). |
| `comparable`         | Exclude attributes from diff/compare views to avoid noisy changes.                                   | • Services <br />• Scopes                  | See [comparable](#comparable).                   |
| `visibleOn`          | Control property visibility by lifecycle stage (`create`, `read`, `update`).                         | • Services <br />• Scopes <br />• Catalogs | See [visibleOn](#visibleon).                     |
| `editableOn`         | Control when a property is editable by lifecycle stage.                                              | • Services <br />• Scopes <br />• Catalogs | See [editableOn](#editableon).                   |
| `comparable`         | Exclude attributes from diff/compare views to avoid noisy changes.                                   | • Services <br />• Scopes                  | See [comparable](#comparable).                   |
| `$data`              | Reference other fields in validation keywords to enforce cross-property rules.                      | • Services <br />• Scopes                  | See [cross-property validation](#cross-property-validation-data). |



## Secret

Use `secret: true` to mark sensitive attributes within a service, scope, or link as secret so they're hidden/redacted
in responses and the UI.

```json
{
  "attributes": {
    "schema": {
      "password": {
        "type": "string",
        "secret": true,
        "readOnly": true
      }
    }
  }
}
```

:::note
If you have an action, remember to add the `secret` keyword to the parameters and results of the associated secret attribute.
:::

## Export

Export an attribute’s value as an **application parameter**. The field can be either `boolean` or an `object`.

- **Boolean** form  
  - `true`: export as an environment variable using the spec **slug** and the **attribute name**.  
  - `false`: do not export (equivalent to omitting the field).
- **Object** form

```json
{
  "type": "environment_variable" | "file",
  "target": "name-of-the-env-var-or-file",
  "secret": true | false
}
```

When using the **object** variant, note that:

- The `target` parameter allows expression evaluation using the service and link as variables, for example:
  ```json
  {
    "type": "environment_variable",
    "target": "${service.slug}_${link.slug}",
    "secret": false  
  }
  ```

### Exporting a secret

If you need to export a sensitive value, set `secret: true` so the platform generates a **secret parameter** in your application.

```json
{
  "attributes": {
    "schema": {
      "password": {
        "type": "integer",
        "export": {
          "secret": true
        },
        "readOnly": true
      }
    }
  }
}
```

## Export ignore

Use `exportIgnore` at the object level to exclude attributes when exporting values to application parameters.

```json5
exportIgnore: "my_attribute"
// or
exportIgnore: ["my_attribute", "another_attribute"]
```

## Target

Use `target` to map an **action result** to an attribute in your **service** or **link**.

```json
 {
  "id": "12341234-4321-abcd-134-abcd1324bdcda",
  "name": "S3 Bucket Update Action",
  "slug": "s3-bucket-update-action",
  "type": "update",
  // ... more properties
  "results": {
    "schema": {
      "type": "object",
      "required": [
        "bucket_arn",
        "bucket_id"
      ],
      "properties": {
        "bucket_id": {
          "type": "string",
          "target": "bucket_id"
          // <== the action's result 'bucket_id' will be assigned to the service's 'bucket_id'
        },
        "bucket_arn": {
          "type": "string",
          "target": "bucket_arn"
          // <== the action's result 'bucket_arn' will be assigned to the service's 'bucket_arn'
        }
      }
    },
    "values": {}
  }
}
```

:::warning Targets must be explicit
Attributes are **not** auto-mapped. You must declare a `target` for each result you want to persist.
:::

## Config

This `config` keyword tells nullplatform to map a field to an [NRN](/docs/NRN.md) key.

Here's an example where the `account_id` field will be filled with the `aws.account_id` key from the NRN API:

```json
{
  "id": "12ced23430-bbff-495b-bbff-02fed729b0",
  "name": "Dynamo DB",
  "slug": "dynamodb",
  "type": "dependency",
  // ... service spec
  "attributes": {
    "schema": {
      "account_id": {
        "type": "string",
        "config": {
          // Here account_id will automatically take
          // the value from NRN's aws.account_id key
          "key": "aws.account_id"
        },
        "export": false,
        "readOnly": true
      }
    }
  }
}
```

## Additional keywords

Use `additionalKeywords` to make schema keywords dynamic using data from the current
**service instance**, **link**, **application**, **scopes**, or the parent **namespace** and **account**.

### Example: building enums dynamically

```json
{
  "roles": {
    "type": "array",
    "items": {
      "type": "string",
      "additionalKeywords": {
        "enum": ".service.attributes.availableRoles"
      }
    }
  }
}
```

You can apply `additionalKeywords` to any JSON Schema keyword (`enum`, `default`, `minimum`, `maximum`, …). Values
are **jq filters** applied to a context of the form:

  ```json
  {
    "service": { ... },
    "link": { ... },
    "scope": { ... },
    "application":{ ... },
    "scopes": [{ ... }, { ... }],
    "services": [{ ... }, { ... }],
    "namespace": { ... },
    "account": { ... },
    "dimensions": { ... },
    "external": { ... }
  }
  ```
  :::note keyword details
  Each key is only present when the request carries the matching context parameter (`service_id`, `link_id`, `scope_id`, `application_id`, `dimensions`). The `namespace` and `account` keys are derived automatically from whichever entity you pass — you don't need to request them explicitly.

  - `service`: The attributes of a specific service. You can reference values directly within the service. Present when `service_id` is passed.
  - `link`: The attributes of a service link. You can reference values directly within the link. Present when `link_id` is passed.
  - `scope`: The attributes of a specific scope. Present when `scope_id` is passed.
  - `application`: The attributes of the application. You can reference values directly within the application. Present when `application_id` is passed.
  - `scopes`: All active scopes within the application. For example, it allows you to select services by `scope_slug`. Present when `application_id` is passed.
  - `services`: All active services within the application. For example, it allows you to select services by `specification_id`. Present when `application_id` is passed.
  - `namespace`: The namespace the resolved entity belongs to. Derived automatically by walking up the NRN of the service, link, scope, or application in context. Use it to reference namespace-level metadata (e.g. `.namespace.slug`).
  - `account`: The account the resolved entity belongs to. Derived automatically from the same NRN cascade as `namespace`. Use it to reference account-level metadata (e.g. `.account.slug`).
  - `dimensions`: The resolved dimensions for the request, taken (in priority order) from the scope, the explicit `dimensions` query parameter, the link, or the service.
  - `external`: Data fetched from your infrastructure via the nullplatform agent. Only available when the specification has an [`external` field configured](/docs/services/craft-a-service/external-context). Use it to populate dropdowns with real-time values like database users, DNS zones, or cloud resources.
  :::

- Practical examples of using `additionalKeywords` with different jq filters:

  ```json
  {
    "additionalKeywords": {
      "enum": ".service.attributes.availableRoles",
      "enum": ". | [.scopes[]?.slug // \"You need to create an scope\"]",
      "enum": "[.services[] | select(.specification_id == \"xxx\") | .slug] | if length == 0 then [\"You need to create a yyyy\"] else . end",
      "default": ".application.metadata.my_metadata",
      "default": ".namespace.slug",
      "default": ".account.slug"
    }
  }
  ```

## visibleOn

Control visibility by lifecycle stage of the **instance** (service, catalog entity, or scope).

- `create`: visible when creating the instance  
- `read`: visible when reading/viewing  
- `update`: visible when editing
- `list`: visible as list columns (only available for builds right now)


```json
{
  "attributes": {
    "schema": {
      "my_property": {
        "type": "string",
        "visibleOn": ["create", "update"]
      }
    }
  }
}
```

:::note
If empty, the default setting is `"visibleOn": ["read", "create", "update"]`.
:::

## editableOn

Control when a property is editable by lifecycle stage.

- `create`, `read`, `update`

If empty, the property is not editable in any stage.

```json
{
  "attributes": {
    "schema": {
      "my_property": {
        "type": "string",
        "editableOn": ["create"]
      }
    }
  }
}
```
This indicates that `my_property` is only available to be set when the instance is being created.

:::note
If empty, the default setting is `"editableOn": ["read", "create", "update"]`.
:::


## comparable

To avoid noisy diffs, you can mark attributes **non-comparable** in your service specification using the `comparable: false` flag.

- Default is `true`.


Example:

```json
{
  "attributes": {
    "schema": {
      "account_id": {
        "type": "string",
        "config": { "key": "aws.account_id" },
        "export": false,
        "readOnly": true,
        "comparable": false
      }
    }
  }
}
```

These attributes will be excluded from `diff` outputs.

## Cross-property validation ($data)

Forms generated from JSON Schema support **cross-property validation**
using AJV’s `$data` extension. Use it to express rules like “field A must be greater than field B” directly in
the schema.

### Example: interval must be greater than timeout

```json
{
  "type": "object",
  "properties": {
    "timeout": {
      "type": "number",
      "title": "Timeout (seconds)"
    },
    "interval": {
      "type": "number",
      "title": "Interval (seconds)",
      "exclusiveMinimum": { "$data": "1/timeout" }
    }
  }
}
```

- `"$data": "1/timeout"` is a **relative JSON Pointer**: `1` = parent object, `timeout` = property.
- Because this uses `exclusiveMinimum`, `interval` must be **strictly greater** than `timeout`.

Other keywords such as `minimum`, `maximum`, `minLength`, and `maxLength` can also use `$data` for cross-field rules.
