Skip to content
Data

Number formatting

A field’s format block is a display hint. It changes how the admin renders a value in lists and detail views, and nothing else — storage, the API, sorting and filtering always use the raw value.

{
"name": "discount_pct",
"type": "integer",
"validation": { "min": 0, "max": 100 },
"format": { "style": "percent100" }
}
KeyApplies toMeaning
styleinteger / numberplain, decimal, currency, percent, percent100
precisioninteger / numberFixed fraction digits
currencystyle: "currency"ISO 4217 code
thousandSeparatorstyle: "decimal"Group thousands (default on)
dateStyletimestamprelative, date, datetime, time
prefix / suffixanyWrap the rendered value

A money field ignores style entirely: the currency is carried on the value, which is strictly better information than a display hint someone typed. A phone field renders through its own display for the same reason.

The two percent styles

percent and percent100 differ by what the column holds, and picking the wrong one is off by a factor of a hundred:

StyleColumn holdsRenders as
percent1002020%
percent0.220%

percent100 is almost certainly the one you want. It is what every schema template means: all seventeen percentage columns validate {min: 0, max: 100}, which says the stored number is 20, not 0.2.

percent exists because it is the older token and follows Intl.NumberFormat’s own convention. It keeps that meaning so that a workspace already storing fractions is not silently re-rendered. But it is a trap on a 0–100 column — two template fields carried exactly that combination and printed 20 as 2,000% until percent100 was added.

The admin’s format editor names both by what the column holds rather than offering one ambiguous “Percent”, because the label was the whole problem.

Locale placement is not hand-assembled

percent100 divides by a hundred and hands the result to Intl, rather than formatting a plain number and appending %. That is deliberate: Turkish writes the sign first, so the same value renders 20% in English and %20 in Turkish. Appending the sign would have been wrong in every locale that does not put it last.

What is deliberately not a percentage

The conversion of the templates went by meaning, not by matching {min: 0, max: 100}. A lead score of 80 is not “80%” — that range is a scale, not a proportion — so crm.leads.score, hr.training_attendance.score and lms.quiz_attempts.score keep printing as plain numbers.

lms.quizzes.passing_score is the exception, and a deliberate one: its own label is “Passing score (%)”, so the template already meant a percentage.

A test pins both halves of that rule, because “it has a 0–100 range” is exactly the shortcut that would quietly put a percent sign on a score.