Required fields on forms with defaults¶
- Date: 2026-08-10
There is an intricate (but not critical) problem in pydantic-forms which has not been solved yet. It relates to using pydantic-forms with default values and how this changes the representation in a UI.
For lack of a straightforward solution I decided to document this to fall back to in discussions and to ensure everybody is on the same page.
The subsections will explain the problem step by step.
Required fields¶
A field is required when it does not have a default value:
class MyForm(FormPage):
my_field: str
When validating data with this form, my_field must be specified and contain a string value.
When a field does have a default value, it is no longer required:
class MyForm(FormPage):
my_field: str = "foo"
This means that when validating data with this form, if my_field is not in the data then it’s default value "foo" is used instead.
API vs UI¶
The implication of a field not being required depends on the context from which a form is submitted. We can identify 2 different ones:
- API: submitting form payload directly to an HTTP endpoint
- UI: submitting form data through a user interface, which in turn uses the API
When submitting to an API, an optional field can simply be left out of the payload.
However, when using a UI to submit a form this will typically contain all form data and not a subset. This is because it would require knowledge on the UI side to distinguish for each form field the difference between “no value” or an “empty value”.
A simple form¶
Let’s imagine a simple form that requires the user to provide all values for a new data object:
# Model of the data object
class MyObject(BaseModel):
x: str
# Form definition
class MyCreateForm(FormPage):
my_field: str
# User provides initial values which are validated through the form
initial_data = MyCreateForm(my_field="foo")
# Data object is created with the initial values
new_object = MyObject(x=initial_data.my_field)
This example is overly simplified and cuts out several steps so as to focus on the core problem.
A form with default values¶
A form can contain default values to improve the user experience, for example to modify existing data or to suggest sensible default values. Regardless of the usecase, the default values become part of the form’s JSON schema that is returned to the caller.
# Model of the data object
class MyObject(BaseModel):
x: str
# Data object already exists
existing_object = MyObject(x="bar")
# Form definition
class MyModifyForm(FormPage):
my_field: str = existing_object.x
# User provides (un)modified values which are validated through the form
modify_data = MyModifyForm(my_field="foo")
# Data object is updated with the (un)modified values
existing_object.x = modify_data.my_field
The problem¶
As stated before, a field with a default value is no longer required. This can be observed by rendering the JSON schema of this form:
from pydantic_forms.core import FormPage
from pprint import pprint
class MyModifyForm(FormPage):
str_a: str = "foo"
str_b: str
int_a: int = 1
int_b: int
The output shows str_a and int_a have a default value, while str_b and int_b are in required:
>>> pprint(MyModifyForm.model_json_schema())
{'additionalProperties': False,
'properties': {'int_a': {'default': 1, 'title': 'Int A', 'type': 'integer'},
'int_b': {'title': 'Int B', 'type': 'integer'},
'str_a': {'default': 'foo', 'title': 'Str A', 'type': 'string'},
'str_b': {'title': 'Str B', 'type': 'string'}},
'required': ['str_b', 'int_b'],
'title': 'unknown',
'type': 'object'}
In a UI, such as the orchestrator-ui-library, this form looks like this:
/// details | Form generator code type: info
def initial_input_form_generator() -> FormGenerator:
class MyModifyForm(FormPage):
str_a: str = "foo"
str_b: str
int_a: int = 1
int_b: int
form1 = yield MyModifyForm

Trying to make the fields empty shows an error for 3 out of 4 fields:

From this we can observe that:
int_acannot be empty: even if it has a default value on the backend, the UI must always submit all fieldsint_ais not shown as required, while it isstr_acan be empty, it will probably fallback to its default value?
So let’s fill in the bare minimum and see what the API receives:

/// details | Form generator code type: info
def initial_input_form_generator() -> FormGenerator:
class MyModifyForm(FormPage):
str_a: str = "foo"
str_b: str
int_a: int = 1
int_b: int
form1 = yield MyModifyForm
received = f"str_a: {form1.str_a}\nstr_b: {form1.str_b}\nint_a: {form1.int_a}\nint_b: {form1.int_b}"
class ReadonlyForm(FormPage):
values: LongText = received
yield ReadonlyForm
And on the next page it shows these values:
str_a:
str_b: b
int_a: 2
int_b: 3
str_a did not fallback to its default value, but it accepted the empty string as a value. An empty string is still
a string.
Question 1¶
What do we actually want to tell the user by showing an asterisk (*) near a form field in the UI?
Some possible answers:
1. That a field requires action before submitting¶
The current behavior as of 2026-08-10.
It tells the user that fields with a default value do not need to be changed by the user to reach the next page.
Conversely, it tells the user that fields without a default value need to be filled in to reach the next page. (This is currently not perfectly handled, as described in Question 3)
When choosing this, also see Question 2
2. That a field is required by the API¶
This means that we would show a * for all fields that we know require a value by the API.
It tells the user that fields with a default value should not be made empty.
For fields without a default value, this is identical to answer 1.
Question 2¶
If the user makes a field empty that had a default value, do we want to re-render it as required, or does it suffice to show the validation error from the backend?
Question 3¶
Should str_b be shown as a required field, if the type is just str with no other requirements?
We could choose to only show strings as required if they declare a minimum length or non-empty regex. It may be perfectly valid to accept empty strings as values, and this is now not easily possible.
The same holds for list fields without a minimal length requirement.
Discussion¶
If you want to weigh in on this discussion, feel free to leave a comment here!