Skip to content

Understand Widget Mapping

Every widget is chosen from the field's schema. The rules below are the whole of it, so you can predict what a form will look like by reading the document, and adjust the document when you want something different.

One property per widget kind

The rules

Applied in order; the first match wins.

Schema Widget
enum with 3 or fewer values RadioButtonGroup
enum with more than 3 values Select
string, format: password PasswordInput
string, format: date DatePicker
string, format: date-time DatetimePicker
string TextInput
integer with both minimum and maximum IntSlider
integer with one bound or none IntInput
number with both minimum and maximum FloatSlider
number with one bound or none FloatInput
boolean Checkbox
array whose items have an enum MultiChoice
Anything else, including object and free-form arrays TextAreaInput holding JSON

enum is checked before type, so an enum of integers still gets a RadioButtonGroup or Select.

A form showing all of them

"""One property per widget kind, to show how schemas map to widgets."""

import panel as pn

from panel_openapi import OpenAPIForm

pn.extension()

SPEC = {
    "openapi": "3.0.0",
    "info": {"title": "Widget mapping", "version": "1.0.0"},
    "paths": {
        "/post": {
            "post": {
                "summary": "Every widget kind",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    # string -> TextInput
                                    "name": {"type": "string"},
                                    # format: password -> PasswordInput
                                    "token": {"type": "string", "format": "password"},
                                    # format: date -> DatePicker
                                    "due": {"type": "string", "format": "date"},
                                    # format: date-time -> DatetimePicker
                                    "starts_at": {"type": "string", "format": "date-time"},
                                    # enum, <= 3 options -> RadioButtonGroup
                                    "size": {"type": "string", "enum": ["s", "m", "l"]},
                                    # enum, > 3 options -> Select
                                    "colour": {
                                        "type": "string",
                                        "enum": ["red", "green", "blue", "black"],
                                    },
                                    # integer, both bounds -> IntSlider
                                    "quantity": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "maximum": 10,
                                    },
                                    # number, one bound -> FloatInput
                                    "weight": {"type": "number", "minimum": 0.0},
                                    # boolean -> Checkbox
                                    "gift": {"type": "boolean"},
                                    # array of enum -> MultiChoice
                                    "tags": {
                                        "type": "array",
                                        "items": {"type": "string", "enum": ["new", "sale"]},
                                    },
                                    # object, or anything unrecognised -> JSON TextAreaInput
                                    "metadata": {"type": "object"},
                                },
                            }
                        }
                    }
                },
                "responses": {"200": {"description": "ok"}},
            }
        }
    },
}

form = OpenAPIForm(SPEC, base_url="https://postman-echo.com", path="/post", method="post")

form.form.servable(title="Widget mapping")

What carries over from the schema

  • title becomes the widget label, falling back to the field name.
  • description becomes the widget's description, shown as a tooltip. For a parameter this is read from the parameter itself, which is where OpenAPI puts it; for a body property, from its schema.
  • default becomes the initial value.
  • minimum/maximum bound the numeric widgets, and decide slider or spinner.
  • required is recorded on the widget's tags, and drives submit validation.
  • readOnly disables the widget and excludes the field from the request.

Nullable fields

An optional value is often written as a union with null, which would otherwise land in the "anything else" row and get a JSON text area. A union with exactly one non-null branch is unwrapped to that branch, so it gets a real widget:

# OpenAPI 3.1, and what pydantic emits for `str | None`
{"anyOf": [{"type": "string"}, {"type": "null"}]}   # -> TextInput

# OpenAPI 3.0
{"type": "string", "nullable": True}                # -> TextInput

# A genuine union is left alone
{"anyOf": [{"type": "string"}, {"type": "integer"}]}  # -> JSON TextAreaInput

This matters most for PATCH request models, where nearly every field is optional.

Free-form arrays

An array of arbitrary strings becomes a JSON text area rather than a MultiChoice, because MultiChoice cannot accept a value outside its options. Give items an enum when you want the picker:

# JSON text area: type ["a", "b"] yourself
{"type": "array", "items": {"type": "string"}}

# MultiChoice with two options
{"type": "array", "items": {"type": "string", "enum": ["new", "sale"]}}

JSON text areas are parsed on submit. Invalid JSON is reported and nothing is sent.

Changing what you get

The widget follows the schema, so the way to change the widget is to change the schema, e.g. adding format: date to a string or bounds to an integer. When the document is not yours to change, or you want something the mapping does not offer, build the form yourself; schema_to_widget is public and usable field by field.