Skip to main content

Configuring Inputs with CustomField

Last updated on

Workflow input fields often need to do more than accept typed text. A dropdown may need to list live data from an external system, a value may need to be validated before the form is submitted, or a step may need a code editor for a configuration block.

In IDP workflow builder, SelectFieldFromApi renders an API-backed dropdown and ValidateAndFetch renders a button that called an API, and each is configured differently.

CustomField provides all of them from a single field extension. Set ui:field: CustomField on an input property, and use fieldType to select the input that renders. Check Complete example to know how it fits together in the workflow YAML.

customFieldText:
title: Custom Field (text)
type: string
ui:field: CustomField
ui:options:
fieldType: text
placeholder: Enter text
customFieldDropdown:
title: Custom Field (dropdown)
type: string
ui:field: CustomField
ui:options:
fieldType: dropdown
options:
- label: Option 1
value: v1
- label: Option 2
value: v2

fieldType is the only required option. Everything else is configured under ui:options, and the options that apply depend on the fieldType you choose.

Field types at a glance

fieldTypeRendersUse it for
textSingle-line or multi-line text inputNames, emails, slugs, free-form notes, values that need regex or API validation
dropdownSingle-select or multi-select pickerStatic option lists, API-backed lists, searchable lists, dependent pickers
buttonAn in-form button that calls an APIPre-submission validation, pre-checks, creating a resource before submission
jsonAn embedded JSON or YAML code editorConfiguration objects, pipeline snippets, structured input

When to use CustomField

CustomField covers the behavior previously split across SelectFieldFromApi and ValidateAndFetch, and adds field types that neither component provided.

AspectSelectFieldFromApiValidateAndFetchCustomField
PurposeFetch a list from an API, then selectButton-triggered API call, optionally set contextOne component, many field types (text, dropdown, button, json)
UI stackMix of Material UI (MUI) and HarnessMUI-heavyHarness UI only, built on @harness/uicore and Harness design tokens
Config surfacepath, request, OAuth, valueSelector, setContextData, and othersrequest, button, setContextDataSingle schema: fieldType plus type-specific options
ValidationNot availableNot availableRegex, named validators, API validation on change or on button, debounce
ContextsetContextData (Nunjucks)setContextDatasetContextData with selectors and Nunjucks templates, plus updateFormContext
DependenciesPath-based dependency keysParameters in the requestdependsOn for dropdowns, form context for all field types
ExtensibilityOne-off componentOne-off componentNew field types are added in one place

Use CustomField for new workflows. Template authors set ui:field: CustomField once and configure behavior through ui:options, instead of choosing between several components. Because the configuration is validated against a schema, an incorrect YAML configuration surfaces a clear validation error in the workflow form rather than failing silently.

CustomField keeps the same formContext, updateFormContext, and setContextData semantics as SelectFieldFromApi and ValidateAndFetch, so downstream fields and ContextViewer continue to behave the same way.


Before you begin

  • Be familiar with configuring workflow inputs and the spec.parameters structure of workflow.yaml.
  • For any field that calls an external API through apiOptions, apiValidation, or apiAction, configure a Backend Proxy first. The proxy holds the target base URL and the authorization headers.
info

API paths in CustomField use the format proxy/<endpoint-name>/<api-path>, the same format used by the Dynamic Workflow Picker. Here <endpoint-name> is the proxy endpoint you declared under Configure > Plugins > Configure Backend Proxies. Do not prefix the path with /api/proxy/. A path that does not resolve returns an inline error on the field.


Common options

These options apply to every fieldType.

OptionTypeDescription
fieldTypetext, dropdown, button, jsonRequired. Selects the input that renders.
titlestringLabel shown above the field.
placeholderstringPlaceholder text shown in the empty field.
descriptionstringHelper text shown below the field.
defaultValueanyValue the field starts with.
setContextDataobjectMap of context keys to a selector or a Nunjucks template. Stores values in form context for other fields to consume.
messagesobjectOverrides for the built-in field messages.

Override built-in messages

Use messages to replace the default text that the field shows for the required state, a failed validation, a failed API call, and the loading state.

projectName:
type: string
title: Project Name
ui:field: CustomField
ui:options:
fieldType: text
title: Project Name
messages:
required: Provide a project name before continuing
validationError: That project name is not in the expected format
apiError: Could not reach the naming service, try again
loading: Checking the project name

Text fields

Set fieldType: text for single-line and multi-line text input.

OptionTypeDescription
multilinebooleanRenders a multi-line text area instead of a single-line input.
regexobjectpattern is the regular expression the value must match. message is the optional error shown when it does not match.
validatorstringName of a built-in validator to apply to the value, for example kebabCase or email.
apiValidationobjectValidates the value against an API. Accepts path, method, params, headers, body, responseValidPath, and errorMessagePath.
apiValidationTriggeronChange, onClickRuns API validation as the user types, or only when the validate button is selected.
validateButtonTextstringLabel of the validate button when apiValidationTrigger is onClick.
debounceMsnumberDelay in milliseconds before validation runs after the user stops typing.

Text input with a named validator

projectName:
type: string
title: Project Name
ui:field: CustomField
ui:options:
fieldType: text
title: Project Name
placeholder: my-cool-service
description: Must be kebab-case (lowercase letters, numbers, hyphens)
validator: kebabCase
debounceMs: 300
userEmail:
type: string
title: User Email
ui:field: CustomField
ui:options:
fieldType: text
title: Email Address
placeholder: you@company.com
description: We validate this is a proper email
validator: email
debounceMs: 400

Text input with regex validation

Use regex when the format you need is not covered by a named validator. Set message to the error the user sees when the value does not match.

slugField:
type: string
title: Slug
ui:field: CustomField
ui:options:
fieldType: text
title: URL Slug
placeholder: my-service
description: Only lowercase, numbers, and hyphens allowed
regex:
pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
message: Must be a valid URL slug (for example, my-service-123)
debounceMs: 300

Multi-line text input

notesField:
type: string
title: Notes
ui:field: CustomField
ui:options:
fieldType: text
title: Additional Notes
placeholder: Describe your project requirements...
description: Multi-line free-form text
multiline: true

Together, these fields render as follows. The placeholder appears inside each input and the description appears beneath it.

Text input validated against an API

Use apiValidation when the value has to be checked against an external system, for example to confirm that a name is still available.

serviceName:
type: string
title: Service Name
ui:field: CustomField
ui:options:
fieldType: text
title: Service Name
description: Checked against the service registry before submission
apiValidation:
path: proxy/dummyjson/products/1
method: GET
params:
select: title
headers:
Accept: application/json
responseValidPath: id
errorMessagePath: message
apiValidationTrigger: onClick
validateButtonText: Check availability
debounceMs: 400
  • responseValidPath is the path in the API response that indicates success.
  • errorMessagePath is the path in the API response that holds the error message to display.
  • apiValidation accepts method values GET, POST, PUT, and PATCH. Use body to send a payload with the non-GET methods.
  • With apiValidationTrigger: onChange, the call runs while the user types, throttled by debounceMs. With onClick, the field renders a button labelled by validateButtonText and calls the API only when that button is selected.

Set fieldType: dropdown for single-select and multi-select pickers, backed by either a static list or an API.

OptionTypeDescription
optionsarrayStatic list of label and value pairs.
apiOptionsobjectFetches the list from an API. Accepts path, method, params, headers, body, arraySelector, valueSelector, labelSelector, and searchQueryParam.
multiSelectbooleanAllows more than one value to be selected.
allowCustomValuebooleanAllows the user to enter a value that is not in the list.
dependsOnarray of stringsForm keys the dropdown waits for before it loads its options.

Static dropdown

environment:
type: string
title: Environment
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Target Environment
description: Where should this service be deployed?
placeholder: Select environment...
options:
- label: Production
value: prod
- label: Staging
value: staging
- label: Development
value: dev
- label: QA
value: qa

The user sees the label and the workflow receives the value.

API-backed dropdown

githubRepo:
type: string
title: GitHub Repo
ui:field: CustomField
ui:options:
fieldType: dropdown
title: GitHub Repository (public repos from GitHub API)
description: Fetches repos from the GitHub public API via proxy
placeholder: Search for a repo...
apiOptions:
path: proxy/github-api/orgs/backstage/repos
method: GET
params:
per_page: "20"
sort: updated
arraySelector: ""
valueSelector: full_name
labelSelector: full_name
  • arraySelector points to the array inside the response. Leave it as an empty string when the response body is itself the array. Set it to a key name, such as users, when the array is nested under that key.
  • valueSelector and labelSelector point to the keys used for the stored value and the displayed label. Set them to different keys when the identifier and the display name differ.
  • params are appended to the request as query parameters. headers and body are sent with the request, and are useful when the endpoint is not a plain GET.

The next example fetches product categories and stores a different key for the value and the label, so the form submits the slug while the user reads the display name.

productCategory:
type: string
title: Product Category
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Product Category (from DummyJSON)
description: Fetches product categories from dummyjson.com
placeholder: Pick a category...
apiOptions:
path: proxy/dummyjson/products/categories
method: GET
valueSelector: slug
labelSelector: name

Server-side search and context data

Set searchQueryParam to send what the user types to the API as a query parameter, so filtering happens server side instead of in the browser. Use setContextData in the same block to store extra fields from the selected object.

assignedUser:
type: string
title: Assigned User
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Assign to User (DummyJSON)
description: Dynamic user list fetched from dummyjson.com with search
placeholder: Search users...
apiOptions:
path: proxy/dummyjson/users
method: GET
params:
limit: "15"
arraySelector: users
valueSelector: id
labelSelector: firstName
searchQueryParam: q
setContextData:
selectedUserEmail: email
selectedUserName: firstName

If the request fails, the field shows the failure inline along with a Retry link, and the rest of the form stays usable.

info

An inline API error is most often caused by an incorrect path. Confirm that the path starts with proxy/ and is not prefixed with /api/proxy/, that the endpoint name matches the one declared under Configure > Plugins > Configure Backend Proxies, and that the remaining path is valid on the target API.

Multi-select dropdown

For multi-select, define the property as an array with items, and set multiSelect: true. The selected values are submitted as an array.

multiRegion:
type: array
title: Regions
items:
type: string
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Deploy Regions
description: Select one or more regions
placeholder: Choose regions...
multiSelect: true
options:
- label: US East (N. Virginia)
value: us-east-1
- label: US West (Oregon)
value: us-west-2
- label: EU West (Ireland)
value: eu-west-1
- label: AP South (Mumbai)
value: ap-south-1

Allow a value outside the list

Set allowCustomValue: true when the list is a set of suggestions rather than a closed set, and the user is permitted to type a value that the API or the static list does not return.

serviceTier:
type: string
title: Service Tier
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Service Tier
description: Pick a tier, or enter one that is not listed
placeholder: Select or type a tier...
allowCustomValue: true
options:
- label: Tier 1
value: tier-1
- label: Tier 2
value: tier-2

Dependent dropdowns

Use dependsOn to list the form keys a dropdown waits for before it loads its options. This is needed when the API path or the query parameters are built from an earlier answer, so that the request is not made with an empty value.

organization:
type: string
title: GitHub Organization
ui:field: CustomField
ui:options:
fieldType: text
title: GitHub Organization
placeholder: backstage
repository:
type: string
title: Repository
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Repository
description: Loads after you provide an organization
placeholder: Search for a repo...
dependsOn:
- organization
apiOptions:
path: proxy/github-api/orgs/{{ parameters.organization }}/repos
method: GET
arraySelector: ""
valueSelector: full_name
labelSelector: full_name

Reference an earlier field in the path with {{ parameters.<propertyId> }}, where <propertyId> is the key of the property whose value you want to substitute.


Button fields

Set fieldType: button to run an API call from inside the form. Use it to validate a configuration, run a pre-check, or create a resource before the workflow is submitted.

OptionTypeDescription
buttonTextstringLabel on the button.
buttonVariationprimary, secondary, tertiaryVisual style of the button.
apiActionobjectThe call to run. Accepts path, method, params, headers, body, successMessage, and errorMessage.

apiAction.method accepts GET, POST, PUT, PATCH, and DELETE.

Validation button

Use setContextData alongside apiAction to write values from the API response into form context, so that later fields can read them.

validateSetup:
type: string
title: Validate
ui:field: CustomField
ui:options:
fieldType: button
title: Validate Project Setup
description: Calls DummyJSON API to simulate a validation check
buttonText: Run Validation
buttonVariation: primary
apiAction:
path: proxy/dummyjson/products/1
method: GET
successMessage: Validation passed, project setup looks good
errorMessage: Validation failed, check your inputs
setContextData:
productTitle: title
productPrice: price

Action button with a request body

Reference other inputs in the request body with {{ parameters.<propertyId> }}.

triggerAction:
type: string
title: Create Resource
ui:field: CustomField
ui:options:
fieldType: button
title: Create a Test Resource
description: POSTs to DummyJSON to simulate creating a resource
buttonText: Create Resource
buttonVariation: secondary
apiAction:
path: proxy/dummyjson/products/add
method: POST
headers:
Content-Type: application/json
body:
title: "{{ parameters.projectName }}"
category: "{{ parameters.productCategory }}"
successMessage: Resource created successfully
errorMessage: Failed to create resource

Both buttons render inline in the form, each with its own title above it and description below it.

When the call succeeds, the successMessage appears as a toast and the button is marked Completed. When it fails, the errorMessage appears instead, and the user can run the action again.


JSON and YAML editor fields

Set fieldType: json to embed a code editor for structured input. The editor supports both JSON and YAML through the language option.

OptionTypeDescription
languagejson, yamlSyntax highlighting and parsing mode for the editor.
jsonSchemaobjectJSON Schema used to validate the content the user enters.
readOnlybooleanRenders the editor as read-only.
editorHeightstringHeight of the editor, for example 350px.
contextKeystringPopulates the editor from a value already stored in form context.

Use defaultValue to pre-fill the editor. For JSON, supply the object directly. For YAML, supply a block scalar.

JSON editor with schema validation

serviceConfig:
type: string
title: Service Configuration
ui:field: CustomField
ui:options:
fieldType: json
title: Service Configuration (JSON)
description: Paste or edit your service configuration object
language: json
editorHeight: 350px
defaultValue:
name: my-service
replicas: 3
port: 8080
env:
NODE_ENV: production
LOG_LEVEL: info
jsonSchema:
type: object
required:
- name
- port
setContextData:
parsedConfig: "name"

The jsonSchema block above requires name and port to be present in the object the user submits.

YAML editor

yamlSnippet:
type: string
title: YAML Snippet
ui:field: CustomField
ui:options:
fieldType: json
title: Pipeline YAML Snippet
description: Enter a YAML snippet (validated on submit)
language: yaml
editorHeight: 250px
defaultValue: |
stages:
- stage:
name: Build
type: CI
spec:
execution:
steps:
- step:
type: Run
name: Build App
spec:
command: npm run build

Read-only editor populated from form context

Set contextKey to fill the editor from a value that an earlier field wrote to form context. Combine it with readOnly: true when the content is for review only.

resolvedConfig:
type: string
title: Resolved Configuration
ui:field: CustomField
ui:options:
fieldType: json
title: Resolved Configuration
description: The configuration captured earlier in this form
language: json
readOnly: true
editorHeight: 250px
contextKey: parsedConfig

Share values between fields

CustomField writes to the same global form context used by the other workflow pickers. Add setContextData to a field to store values, then read them back in a later field.

assignedUser:
type: string
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Assign to User
apiOptions:
path: proxy/dummyjson/users
arraySelector: users
valueSelector: id
labelSelector: firstName
setContextData:
selectedUserEmail: email
ownerEmail:
type: string
title: Owner Email
readonly: true
ui:field: ContextViewer
ui:options:
getContextData: ${{formContext.selectedUserEmail}}
  • The keys on the left of setContextData are the names you choose in form context.
  • The values on the right are selectors into the API response object, or Nunjucks templates.
  • Any field can read them back with ContextViewer and getContextData, and a json field can read them with contextKey.
info

Form context is active per workflow session and is not rendered accurately in the Workflow Playground. Test context-driven fields in an actual workflow execution.

Values collected by CustomField inputs appear in the review step before submission, and are passed to the backend the same way as any other input.


Complete example

The following workflow uses all four field types across three form pages, and logs the collected values in a backend step.

Example workflow.yaml
apiVersion: harness.io/v1
kind: Workflow
name: CustomFieldExtensionDemo
identifier: custom_field_demo
type: service
owner: team_idp
metadata:
description: Demonstrates all CustomField field types with live API calls
spec:
parameters:
- title: Text Input Examples
required:
- projectName
- userEmail
- slugField
- notesField
properties:
projectName:
type: string
title: Project Name
ui:field: CustomField
ui:options:
fieldType: text
title: Project Name
placeholder: my-cool-service
description: Must be kebab-case (lowercase letters, numbers, hyphens)
validator: kebabCase
debounceMs: 300
userEmail:
type: string
title: User Email
ui:field: CustomField
ui:options:
fieldType: text
title: Email Address
placeholder: you@company.com
description: We validate this is a proper email
validator: email
debounceMs: 400
slugField:
type: string
title: Slug
ui:field: CustomField
ui:options:
fieldType: text
title: URL Slug
placeholder: my-service
description: Only lowercase, numbers, and hyphens allowed
regex:
pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
message: Must be a valid URL slug (for example, my-service-123)
debounceMs: 300
notesField:
type: string
title: Notes
ui:field: CustomField
ui:options:
fieldType: text
title: Additional Notes
placeholder: Describe your project requirements...
description: Multi-line free-form text
multiline: true
- title: Dropdown Examples
required:
- environment
- githubRepo
- productCategory
- multiRegion
properties:
environment:
type: string
title: Environment
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Target Environment
description: Where should this service be deployed?
placeholder: Select environment...
options:
- label: Production
value: prod
- label: Staging
value: staging
- label: Development
value: dev
- label: QA
value: qa
githubRepo:
type: string
title: GitHub Repo
ui:field: CustomField
ui:options:
fieldType: dropdown
title: GitHub Repository (public repos from GitHub API)
description: Fetches repos from the GitHub public API via proxy
placeholder: Search for a repo...
apiOptions:
path: proxy/github-api/orgs/backstage/repos
method: GET
params:
per_page: "20"
sort: updated
arraySelector: ""
valueSelector: full_name
labelSelector: full_name
productCategory:
type: string
title: Product Category
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Product Category (from DummyJSON)
description: Fetches product categories from dummyjson.com
placeholder: Pick a category...
apiOptions:
path: proxy/dummyjson/products/categories
method: GET
valueSelector: slug
labelSelector: name
assignedUser:
type: string
title: Assigned User
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Assign to User (DummyJSON)
description: Dynamic user list fetched from dummyjson.com with search
placeholder: Search users...
apiOptions:
path: proxy/dummyjson/users
method: GET
params:
limit: "15"
arraySelector: users
valueSelector: id
labelSelector: firstName
searchQueryParam: q
setContextData:
selectedUserEmail: email
selectedUserName: firstName
multiRegion:
type: array
title: Regions
items:
type: string
ui:field: CustomField
ui:options:
fieldType: dropdown
title: Deploy Regions
description: Select one or more regions
placeholder: Choose regions...
multiSelect: true
options:
- label: US East (N. Virginia)
value: us-east-1
- label: US West (Oregon)
value: us-west-2
- label: EU West (Ireland)
value: eu-west-1
- label: AP South (Mumbai)
value: ap-south-1
- title: Button and JSON Examples
required:
- validateSetup
- triggerAction
- serviceConfig
- yamlSnippet
properties:
validateSetup:
type: string
title: Validate
ui:field: CustomField
ui:options:
fieldType: button
title: Validate Project Setup
description: Calls DummyJSON API to simulate a validation check
buttonText: Run Validation
buttonVariation: primary
apiAction:
path: proxy/dummyjson/products/1
method: GET
successMessage: Validation passed, project setup looks good
errorMessage: Validation failed, check your inputs
setContextData:
productTitle: title
productPrice: price
triggerAction:
type: string
title: Create Resource
ui:field: CustomField
ui:options:
fieldType: button
title: Create a Test Resource
description: POSTs to DummyJSON to simulate creating a resource
buttonText: Create Resource
buttonVariation: secondary
apiAction:
path: proxy/dummyjson/products/add
method: POST
body:
title: "{{ parameters.projectName }}"
category: "{{ parameters.productCategory }}"
successMessage: Resource created successfully
errorMessage: Failed to create resource
serviceConfig:
type: string
title: Service Configuration
ui:field: CustomField
ui:options:
fieldType: json
title: Service Configuration (JSON)
description: Paste or edit your service configuration object
language: json
editorHeight: 350px
defaultValue:
name: my-service
replicas: 3
port: 8080
env:
NODE_ENV: production
LOG_LEVEL: info
jsonSchema:
type: object
required:
- name
- port
setContextData:
parsedConfig: "name"
yamlSnippet:
type: string
title: YAML Snippet
ui:field: CustomField
ui:options:
fieldType: json
title: Pipeline YAML Snippet
description: Enter a YAML snippet (validated on submit)
language: yaml
editorHeight: 250px
defaultValue: |
stages:
- stage:
name: Build
type: CI
spec:
execution:
steps:
- step:
type: Run
name: Build App
spec:
command: npm run build
steps:
- id: log
name: Log Parameters
action: debug:log
input:
message: |-
Project: ${{ parameters.projectName }}
Email: ${{ parameters.userEmail }}
Env: ${{ parameters.environment }}
Repo: ${{ parameters.githubRepo }}
Category: ${{ parameters.productCategory }}
Config: ${{ parameters.serviceConfig }}

Migrate from SelectFieldFromApi and ValidateAndFetch

Existing workflows continue to work. When you move a field to CustomField, map the options as follows.

Existing configurationCustomField equivalent
ui:field: SelectFieldFromApiui:field: CustomField with fieldType: dropdown
path on the pickerapiOptions.path
valueSelectorapiOptions.valueSelector, plus apiOptions.labelSelector when the label differs from the value
request on the pickerapiOptions.method, apiOptions.headers, and apiOptions.body
ui:field: ValidateAndFetchui:field: CustomField with fieldType: button
button.titlebuttonText
path and request on the buttonapiAction.path, apiAction.method, apiAction.headers, and apiAction.body
setContextDatasetContextData, unchanged
ui:field: ContextViewer for read-only displayUnchanged, or a json field with contextKey and readOnly: true for structured values

Frequently asked questions

Do I have to replace my existing SelectFieldFromApi and ValidateAndFetch fields?

No. Workflows that use those components continue to work. Use CustomField for new fields, and migrate existing ones when you are already editing that part of the workflow.

Can I use more than one CustomField in the same workflow?

Yes. You can use as many CustomField properties as you need, across as many form pages as you need, and each one can use a different fieldType. The complete example on this page uses all four field types across three pages.

Which options are required?

Only fieldType. Every other option under ui:options is optional, and the set that applies depends on the fieldType you choose. Options that belong to a different field type are ignored.

Why does my dropdown show no options even though the API returns data?

Check arraySelector first. It has to point at the array inside the response body. If the response body is itself the array, set it to an empty string. If the array is nested, set it to the key that holds it, such as users. Then confirm that valueSelector and labelSelector match keys that exist on each object in that array.

Why does my API-backed field show an error and a Retry link?

The request through the backend proxy did not succeed. Confirm that the path is in the format proxy/<endpoint-name>/<api-path> and is not prefixed with /api/proxy/, that the endpoint name matches the one declared under Configure > Plugins > Configure Backend Proxies, and that any secret referenced in the proxy headers exists. Selecting Retry repeats the request without resetting the rest of the form.

What is the difference between validator, regex, and apiValidation on a text field?

validator applies a built-in named check such as kebabCase or email. regex applies a pattern you supply, with your own error message, and is the option to use when no named validator fits. Both run in the browser. apiValidation calls an external service, and is the right choice when the answer depends on state that only that service knows, such as whether a name is already taken.

Can I stop API validation from firing on every keystroke?

Yes. Set apiValidationTrigger: onClick so validation runs only when the user selects the validate button, and set validateButtonText to label that button. If you keep onChange, raise debounceMs to reduce the number of calls.

Does a button field block submission until it succeeds?

A button field runs its API call when selected and reports success or failure inline, and the button is marked Completed after a successful call. List the property under required for that form step if the workflow should not proceed without it.

Can the JSON field hold YAML?

Yes. Set language: yaml on a field with fieldType: json. The editor then highlights and parses YAML. Use editorHeight to size the editor to the expected content, and defaultValue with a block scalar to pre-fill it.

How do I make a field read-only?

For a json field, set readOnly: true under ui:options. For other field types, use the standard workflow property ui:readonly: true as described in Add read only fields.

How do I pass a CustomField value into a Harness pipeline?

The same way as any other input. Reference it with ${{ parameters.<propertyId> }} in the inputset of your trigger:harness-custom-pipeline step. See Setting up the backend with IDP pipeline.

Can I preview CustomField behavior in the Workflow Playground?

Only partially. The playground does not render form context or live API responses accurately. Test API-backed dropdowns, buttons, and context-driven fields in an actual workflow execution.