formats
Timestamps are stored as unix epoch and numbers as plain numbers — good for sorting and filtering, unreadable on a page. A view's formats map turns them into text at render time (inside tables.<t>.views):
"listing_cards": {
"select": ["id", "title", "price", "created_at"],
"formats": {
"price": "num 1 000",
"created_at": "date dd.mm.yyyy"
},
"limit": 20
}The admin sections use the same mechanism — their menu items point at views too, so an admin list view with formats gives managers readable dates and prices. The edit form always shows raw values — a formatted number would not survive a round-trip. The built-in Users and Media lists format their timestamps as yyyy-mm-dd hh:nn out of the box.
Date masks — date plus tokens; everything else in the mask is printed literally. Time is UTC.
| token | meaning |
|---|---|
| yyyy | year |
| mm | month, 2 digits |
| dd | day, 2 digits |
| hh | hours 00–23 |
| nn | minutes |
| ss | seconds |
"date dd.mm.yyyy hh:nn" → 11.07.2026 14:32
Number masks — num plus a sample of how a thousand should look. The sample shows the group separator (space, comma, dot, apostrophe or none) and the decimal part; the engine dresses any value the same way. The group and decimal separators must differ — num 1.000.00 would be ambiguous to read and is rejected.
| mask | 1234567.891 → |
|---|---|
| num 1 000 | 1 234 568 |
| num 1,000 | 1,234,568 |
| num 1000.0 | 1234567.9 |
| num 1 000.00 | 1 234 567.89 |
| num 1.000,00 | 1.234.567,89 |
A broken mask never reaches production: cms check and server start reject it with the exact path (formats column must also be listed in select).
What formats are not: the JSON API always returns raw values — code consuming it does its own formatting. And there is no conditional logic in masks on purpose: mapping a value to a picture is a template job. Five stars from a rating of 5 is just a file name:
<img src="/static/img/stars-{{rating}}.png" alt="{{rating}} of 5">