Skip to content

How a value is edited — twenty-nine controls, and the inference that means you rarely name one.

Widget is the write half of the display pair; Format is the read half.

Field("body", widget=Widget.markdown(height=400))
Field("tags", widget=Widget.relation(display="name", multiple=True))

You rarely name one. The resolver reads the model’s column and picks:

ColumnWidget
CharFieldtext — or textarea past 255 characters
TextFieldtextarea
SlugFieldslug, tracking its source field
PasswordFieldpassword
BooleanFieldswitch
IntFieldnumber
DecimalField / FloatFieldnumber(step=0.01, precision=2)
JSONFieldjson
DatetimeField / DateField / TimeFieldthe matching picker
a field with choices or an enumselect, clearable when nullable
ForeignKeyFieldrelation, searching over the wire
ManyToManyFieldrelation(multiple=True)
UUIDField / BinaryFieldtext(mono=True)

That is read off the schema, which states these things, and never off an annotation.

Texttext, textarea, markdown, rich, code, password, slug, email, url, phone

Numbersnumber, money, range

Choicesselect, radio, checkbox, switch, tags

Timedate, datetime, time, duration

Datafile, image, json, keyvalue, color

Relationsrelation, hidden

Each has a Field shorthand: Field.markdown("body") is Field("body", widget=Widget.markdown()).

select becomes searchable past ten options on its own, because a ten-item list is faster to read than to type into and a forty-item one is the reverse.

password is never populated from the stored value and never sent back. An empty box on an edit form means “leave the existing hash alone”, which is the only behaviour that makes an edit form usable. Writing goes through sillo.hashing.

keyvalue edits a JSON object as rows, which is what most JSONFields actually hold.

relation searches over the wire rather than loading every row into a <select> — the difference between a foreign key to Country and one to Customer. See Relations.

slug follows its source field until somebody types in it.

Field.slug("slug", source="title")

A widget is a kind and its options, and the React side is a generic renderer for that shape. Adding an option is a prop the front end already reads:

>>> Widget.select(["draft", "live"])
Widget('select', choices=(('draft', 'Draft'), ('live', 'Live')), clearable=True)

Choices are accepted in all three ways people write them — a flat list, pairs, or a mapping — and stored one way.