Skip to main content

Custom plugin form builder

Create user-facing configuration fields with YAML.

Ryan Kulp avatar
Written by Ryan Kulp
Updated this week

If you're new to building plugins, see Private Plugins and Public Plugins API Docs.

Below is a guide to adding user-facing form fields inside a Private Plugin or Recipe.

How it works

Step 1 - Plugin author (you) creates custom fields

Step 2 - Plugin user (including you) can interact with rendered fields

Step 3 - Provided values are accessible inside the Markup Editor

{{ trmnl.plugin_settings.custom_fields_values }}

# => { "password": "secret", "lookback_period": "7" }

These values are also accessible from the settings themselves, for example this field:

- keyname: api_key
field_type: string
name: API Key
description: Find this at my-server.com/settings


Can be leveraged in a Polling Header or Polling URL with just the keyname:

https://my-server.com/api/stats?access_token={{ api_key }}

For real-life examples, fork a few published Recipes:
https://usetrmnl.com/recipes

Supported field types

Where possible, TRMNL follows HTML5 input types to leverage native browser validations and UX features. Our current selection includes:

  • author_bio

  • url

  • string

  • text (textarea)

  • code (like textarea, but monospace font)

  • number

  • password

  • date (M Y D)

  • time (HH:SS)

  • select, xhrSelect (single or multi)

  • xhrSelectSearch

  • time_zone

  • copyable (produces 'click to copy' button)

  • copyable_webhook_url (produces 'click to copy' with a plugin's UUID)

Coming soon:

  • radio (binary toggle)

  • email

Anatomy of a form field

The following key/value pairs are available to all field types:

  • keyname - parameterized identifier, not user-facing, ex user_email

  • field_type - one of the above "type" values

  • name - field label

  • description - secondary field label, HTML-friendly

  • description-LOCALE - provides option to localize a description field in 1 or more supported locales (ex: de, es-ES, ja, etc)

  • help_text - tertiary field label, HTML-friendly (optional)

  • optional - boolean; unless set to true, required will be added to HTML input (omit if field should be required)

  • default - value if input is left blank; must be a lowercase, parameterized version of one of the provided options values or will not be pre-chosen on select field type. example: "Upcoming Movies" option => upcoming_movies as default

The following key/value pairs are available for some field types:

  • rows - number of visible rows on a text or code field

  • options - collection of option values for the select field

  • value - string to be added to a user's clipboard via copyable field

  • placeholder - example value if input is cleared; supported by url, string, text, number, code

  • endpoint - URL from which to return options that populate an xhrSelect or xhrSelectSearch field

When building a form field, only the following values are required:

  • keyname, name, field_type

Building form fields

Below are examples of every supported field type, which you may modify with optional parameters described above.

Note that every field must be indented 2 spaces and be preceded with a -, also called a block sequence.

author_bio

this field type has specialized properties such as github_url, which produce clickable icons on your plugin, versus plain text links.

- keyname: doesnt_matter
name: About This Plugin
field_type: author_bio
description: Dad Jokes Daily™ was created by Abraham, father to many.
github_url: https://github.com/father-abraham
learn_more_url: https://www.bible.com/
email_address: fatherabe@hotmail.net

url

- keyname: url
field_type: url
name: Web Address
description: URL of RSS Feed
placeholder: https://www.site.com/rss
help_text: Please include http:// or https://

string

- keyname: api_key
field_type: string
name: API Key
description: Account API Key
placeholder: sa_api_key_qwerty

text (textarea)

- keyname: prompt
field_type: text
name: Prompt
description: Enter your prompt for Chat GPT to respond.
placeholder: Tell me a Dad Joke under 200 words.

With multiple description translations:

- keyname: prompt
field_type: text
name: Prompt
description: Enter your prompt.
description-es-ES: Ingrese su mensaje
description-ko: 프롬프트를 입력하세요
placeholder: Tell me a Dad Joke under 200 words.

number

- keyname: retirement_age
field_type: number
placeholder: '65'
name: Retirement Age
description: At what age do you plan to retire?

password

- keyname: password
field_type: password
name: Password
description: Mobile app login password
placeholder: s3cret!

code

- keyname: json_query
field_type: code
rows: 8
name: JSON Query
description: Provide a raw query
placeholder: |
{ "cannot": "spare a square" }
help_text: Learn how to do this <a href="https://jquery.com/" class="underline">here</a>.

date

- keyname: start_date
field_type: date
default: "2025-07-04" # YYYY-MM-DD; must be wrapped in quotes
name: Start Date
description: Provide your start date

When using the date field, rendered form will have a M/D/Y picker. However the value inside {{ trmnl.plugin_settings.custom_fields_values }} will be YYYY-MM-DD.

You may also leverage default: "today" to pre-populate the current day.

time

- keyname: scroll_time
field_type: time
name: Fixed start time?
description: By default, the week's earliest event time will be used.
help_text: Optional. Applies to week view only.

select (single, unified label/value option tags)

- keyname: filter_by
field_type: select
options:
- Upcoming # value will be 'upcoming'
- Now Playing # value will be 'now_playing'
default: now_playing
name: Filter By
description: Select a movie type


select (single, with separate label/value option tags)

- keyname: lookback_period
field_type: select
options:
- One Week: 7
- Two weeks: 14
default: 14 # should be set to the value, not the label
name: Lookback Period

select (multi)

- keyname: sales_pipeline
field_type: select
name: Pipeline Statuses
description: Select up to 3 statuses
options:
- 'Discovery'
- 'Pending'
- 'Closed-lost'
- 'Closed-won'
multiple: true

Note: if you create a select field that has options "Yes" and "No", and you want the default option to be "No," then you must wrap the default in quotes as "no" or it will be considered truthy. Example:

- keyname: include_description
field_type: select
name: Include Description
description: Select up to 3 statuses
options:
- "Yes"
- "No"
default: "no" # unless wrapped in quotes, value will be true

xhrSelect (select field that fetches <option> values from external URL)

- keyname: team
name: Baseball Team
field_type: xhrSelect
endpoint: https://usetrmnl.com/custom_plugin_example_xhr_select.json

The URL assigned to the endpoint key should respond to POST requests and return an array of options like so:

# key/value, where key is user-facing label and value is stored in db
[{ 'Braves' => 123 }, { 'Yankees' => 456 }]

Note: endpoint will be sent a POST request. Payload will include sibling settings (default + custom fields), which may be useful for providing a conditional dropdown user experience.

xhrSelectSearch (xhrSelect field that fetches <option> values from external URL, then makes those options searchable)

- keyname: project
name: Project
field_type: xhrSelectSearch
endpoint: https://usetrmnl.com/custom_plugin_example_xhr_select_search.json​

Note: endpoint will be sent a POST request. Search query will be inside a query body parameter, for filtering on your server. Example responses from our test endpoint:

# query=null

[
{ 'id' => 'db-123', 'name' => 'Project Tasks' },
{ 'id' => 'db-456', 'name' => 'Team Goals' },
{ 'id' => 'db-789', 'name' => 'Family Goals' }
]

# query=goa

[
{ 'id' => 'db-456', 'name' => 'Team Goals' },
{ 'id' => 'db-789', 'name' => 'Family Goals' }
]

time_zone

- keyname: time_zone
field_type: time_zone
name: Time Zone
description: Where are you located?

copyable

- keyname: shared_api_key
field_type: copyable
name: API Token
value: qwerty-fdsa-1234
description: Paste this into your Vandelay Industries account.

copyable_webhook_url

- keyname: webhook_url
field_type: copyable_webhook_url
name: Webhook URL
description: Paste this into your Apple Shortcut
help_text: This will populate once the plugin has been created/saved.

Conditional field validations

As described above, all fields are required unless optional: true is included in the field's YAML definition. Further, all fields are visible.

But there may be cases where Field B should only be required or visible if Field A has a given value, and vice versa. For this scenario we offer conditional validations.

To get started, attach conditional_validation to the (parent) field whose value (or lack thereof) should dictate the behavior of one or more other fields. Then nest one or more of the strategies below.

Conditional visibility

Use case: show field B unless field A's value is xyz, but show field C (and hide field B) if field A's value is abc. Any combination is possible.

Here's an example from our native Private Plugin architecture:

- keyname: strategy
field_type: select
options:
- Polling
- Webhook
- Static
default: polling
conditional_validation:
- when: polling
hidden:
- static_data
- webhook_url
- when: webhook
hidden:
- polling_url
- polling_verb
- polling_headers
- polling_body
- static_data
- when: static
hidden:
- polling_url
- polling_verb
- polling_headers
- polling_body
- webhook_url

The logic above will show or hide several fields based on the selected value of the "Strategy" dropdown.

Note that when: xxx means, "when this field has value xxx". Thus, when the Polling dropdown value is set to webhook, 4x polling_* fields are hidden in addition to the static_data field.

Conditional "required" state

Use case: make field B's value dependent on the value of field A.

Here's an example from our native Weather plugin, which has 2x data providers (WeatherAPI, Tempest) and 2x location-related fields. If a user selects the Tempest data provider, lat_long is required. But if a user selects the WeatherAPI data provider, the location field is required.

  - keyname: data_provider
field_type: select
options:
- Tempest
- WeatherAPI
default: tempest
name: Data Provider
description: Provides underlying metrics and layout design.
conditional_validation:
- when: "tempest"
required: ["lat_lon"]
- when: "weatherapi"
required: ["location"]
- keyname: lat_lon
field_type: string
name: Latitude/Longitude (Tempest only)
placeholder: -28.001499,153.428467
description: Provide your location.
optional: true
- keyname: location
field_type: string
name: Location (WeatherAPI only)
placeholder: Atlanta, GA, USA
description: Choose a location
optional: true

This treatment produces the following user experience:

Troubleshooting

Form fields don't appear

If your YAML cannot be parsed for any reason, it will be ignored and no errors will be returned.

Check your YAML syntax inline with our syntax checker, located above the form fields input box. This will save you from losing your work due to incorrect formatting.

If you don't want to use our inline verifier, here's another free tool that you can use to parse your fields before click Save.

Did this answer your question?