# Define configuration, custom input, and custom output

Define fields that your users set in the Stripe Dashboard and output values your action returns.

You can ask users of your scripts for values that your script can use at runtime. This lets you change script behavior without rebuilding and redeploying. You can also define output values that downstream workflow steps can reference.

There are two kinds of input you can collect from users:

- [Configuration](https://docs.stripe.com/extensions/scripts/define-config-and-schemas.md#define-configuration): Values set once when a user activates your extension, available at every runtime.
- [Custom input](https://docs.stripe.com/extensions/scripts/define-config-and-schemas.md#define-custom-input): Values provided each time a user configures an action step in a workflow.

Custom actions can also declare an output schema:

- [Custom output](https://docs.stripe.com/extensions/scripts/define-config-and-schemas.md#define-custom-output): Values your action returns at runtime for downstream workflow steps to reference.

## Define configuration

When a user [activates](https://docs.stripe.com/extensions/how-extensions-work.md#lifecycle) an extension, you can ask them for values that your script can use at runtime. These values are passed to your script’s methods in the `config` argument. For example, a billing extension for prorations might ask users to set a maximum proration amount. A custom action that sends email might ask which of the preconfigured email templates to use.

1. You add properties to your config interface in your extension’s `src/index.ts`.
2. When you build or upload your app, Stripe converts the interface into a JSON schema stored in `generated/config.schema.json`.
3. When a user sets up your extension, a form with these fields appears in the Dashboard UI. The user provides static values as input to this form.
4. At runtime, Stripe passes the user-provided values to your script’s methods in the `config` argument so that you can use them in your custom logic.

Each property has a data type. Stripe renders a specific UI control for each type and applies default validations. You can add additional validation rules using TSDoc annotations.

## Add fields to your config interface

This example adds `description` as a `string` data type to the config interface.

```ts
interface MyExtensionConfig extends Record<string, unknown> {
  description: string;
}
```

You can then add TSDoc annotations to set validation rules. Stripe enforces these rules when the user submits the form.

```ts
interface MyExtensionConfig extends Record<string, unknown> {
  /**
   * @displayName Description
   * @minLength 1
   * @maxLength 50
   */
  description: string;
}
```

## Supported types for configuration

You can use standard TypeScript types such as `string`, `number`, and `boolean`. Import Stripe-specific types such as `MonetaryAmount`, `Percent`, `Time`, and `Decimal` from `@stripe/extensibility-sdk`.

Each type has its own set of available TSDoc validators:

| Type | Validators |
| --- | --- |
| String | - `@minLength`
- `@maxLength`
- `@pattern` |
| Boolean | Validated to `true` or `false` by default |
| Enum | Validated to the declared string union values by default |
| Decimal | - `@minimum`
- `@maximum`
- `@exclusiveMinimum`
- `@exclusiveMaximum`
- `@multipleOf` |

The following image shows how the Dashboard renders the different supported data types. The example also shows how validations like maximum character length for the string field and maximum value for the number field appear.
![Configuration form with string, number, boolean, enum, monetary amount, and decimal field types in the Dashboard.](https://b.stripecdn.com/docs-statics-srv/assets/config-form.b900919bbb731c531f6679ef9edda8db.png)

## Define custom input

Some extension points let you define a custom input schema. Unlike configuration, which is set once at activation, custom input is provided each time a user configures an action step in the workflow builder. This lets you request input that’s specific to each workflow, for example, which email template to use. These values are passed to your `execute` method at runtime as `request.customInput`.

> #### Custom actions only
> 
> Only the Workflows [custom action extension point](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md) supports custom input. Billing extension points don’t use custom input.

You define custom input by adding JSON files to your extension’s `src/` directory:

- `custom_input.schema.json` (required): A JSON Schema that defines the fields and their types
- `custom_input.ui.schema.json` (optional): A UI schema that controls how the fields render in the workflow builder (layout, dynamic selects, conditional visibility). If you don’t provide a UI schema, the form renders each field as a standard control based on its JSON Schema type. Add a UI schema when you need a custom layout, dynamic selects, dynamic schemas, or field options like multiline text areas.

### Define the input schema

Create `custom_input.schema.json` in your extension’s `src/` directory. This defines the fields and their types using [JSON Schema](http://json-schema.org/):

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "title": "Name",
      "description": "The name of the item."
    },
    "description": {
      "type": "string",
      "title": "Description",
      "description": "A brief description of the item."
    }
  },
  "required": ["name", "description"],
  "additionalProperties": false
}
```

### Define the UI schema  (optional)

If you need a custom layout, dynamic selects, or field options, create `custom_input.ui.schema.json` in the same directory. This controls the layout and rendering of your fields in the workflow builder. You must add a `Control` element for each field you want to display. Fields without a corresponding control don’t appear in the UI:

```json
{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/name"
    },
    {
      "type": "Control",
      "scope": "#/properties/description"
    }
  ]
}
```

Use `"format": "dynamic_select"` in the element’s `options` to render a field as a dropdown populated by `get_form_state`.

### Reference in the manifest

Add the schema files under `methods.execute.custom_input` in `stripe-app.yaml`. The `input_schema` is required. The `ui_schema` is optional—include it only if you create a UI schema file:

```yaml
methods:
  execute:
    implementation_type: "script"
    custom_input:
      input_schema:
        type: "json_schema"
        content: "extensions/my-extension/src/custom_input.schema.json"
      ui_schema:
        type: "jsonforms"
        content: "extensions/my-extension/src/custom_input.ui.schema.json"
  get_form_state:
    implementation_type: "script"
```

### Access custom input at runtime

Stripe passes custom input values to your `execute` method in `request.customInput`:

```ts
execute(
  request: Extend.Workflows.CustomAction.ExecuteCustomActionRequest,
  _config: MyCustomActionConfig,
  _context: Context
) {
  const input = request.customInput ?? {};
  const name = input.name;
  const description = input.description;

  // Use these values in your action logic
  return {};
}
```

Make custom input fields dynamic by using the `get_form_state` method, which populates dropdown options and controls field visibility based on user selections.

## Define custom output

Custom actions can return output values that downstream workflow steps reference. Define a custom output schema to declare what your action returns.

> #### Custom actions only
> 
> Only the Workflows [custom action extension point](https://docs.stripe.com/extensions/custom-actions/how-custom-actions-work.md) supports custom output. Billing extension points don’t use custom output.

Add a `custom_output.schema.json` file to your extension’s `src/` directory:

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "messages_sent": {
      "type": "integer",
      "description": "Total number of messages delivered"
    },
    "campaign_id": {
      "type": "string",
      "description": "Unique identifier for the campaign that was sent"
    }
  }
}
```

Supported types are: `string`, `boolean`, `integer`, `object`, and `array` (array items must be `string`, `boolean`, or `integer`).

### Reference in the manifest 

Add the output schema under `methods.execute.custom_output` in `stripe-app.yaml`:

```yaml
methods:
  execute:
    implementation_type: "script"
    custom_output:
      output_schema:
        type: "json_schema"
        content: "extensions/my-extension/src/custom_output.schema.json"
```

### Return output at runtime

Return a `custom_output` object from your `execute` method:

```ts
execute(
  request: Extend.Workflows.CustomAction.ExecuteCustomActionRequest,
  _config: MyCustomActionConfig,
  _context: Context
) {
  const input = request.customInput ?? {};

  // ... action logic ...

  return {
    custom_output: {
      messages_sent: 150,
      campaign_id: "camp_abc123",
    },
  };
}
```

### Behavior

- Only fields declared in the output schema are passed to downstream steps. Extra fields are dropped.
- If your schema declares a field but your response doesn’t include it, downstream steps see it as `null`.
- Returning `{}` or omitting `custom_output` entirely is valid—the action succeeds with no output values.
- A type mismatch (for example, schema declares `integer` but you return a string) fails the action with a non-retryable error.
- `null` values are accepted for any declared type.

## See also

- [Create the schemas](https://docs.stripe.com/extensions/custom-actions/build-with-script.md#create-schemas)
- [Build a custom action with a script](https://docs.stripe.com/extensions/custom-actions/build-with-script.md)
- [Build a custom action with a remote function](https://docs.stripe.com/extensions/custom-actions/build-with-remote-function.md)
