Model-Driven Option Set Definition
Schema id: model-driven-option-set@1
Machine-readable schema: model-driven-option-set.schema.json
The canonical representation of an Option Set — a reusable, named list of allowed values a field can take (Section 6.14). Dataverse calls the same concept a Choice. Validated identically whether it comes from the designer, an API call, an AI assistant, or a Solution import.
Shape
| Field | Type | Required | Notes |
|---|---|---|---|
logicalName | string | yes | Publisher-prefixed, lower snake-case (e.g. par_invoicestatus). |
displayName | string | yes | Human-readable name. |
description | string | no | What the set is for. |
publisherPrefix | string | no | The owning publisher's prefix. Carried so the definition names its own publisher rather than leaving an importer to infer one from the logical name. |
optionValueBase | integer | yes | The owning publisher's option-value base, and every option's value is absolute within it — 110000001, never 1. Required since 2026-09-12: a definition without one is refused at import, and the first product solution was written that way and passed every test. |
nextOptionValue | integer | no | The set's high-water mark — the next value it will hand out. Stored rather than derived from options, so a retired option's number is never reissued. See below. |
options | array | yes | The options, in display order. At least one — a set nobody can pick from is not a set. |
Option
| Field | Type | Required | Notes |
|---|---|---|---|
value | integer | yes | The stored value. Allocated by the platform from the owning publisher's base and immutable once assigned. Minimum 1 — see below. |
displayName | string | yes | What a user picks. |
description | string | no | What the option means. |
Why values are large numbers, never 0/1/2
The value is what the database actually stores. Each publisher owns a range of numbers
(e.g. 270150000–270159999), and a set's options are numbered upward from its base:
270150000, 270150001, 270150002, …
If every publisher started at 0, importing solutions from two publishers into one
environment would silently make one publisher's "Draft" mean the other's "Cancelled" —
because both would be stored as 0. The per-publisher range makes that impossible. It is
the numeric counterpart of the publisher prefix, which does the same job for names.
Two rules follow, and both are enforced:
- You do not choose values. The create API takes no value; the platform allocates the next one from the publisher's range. On update, an option that carries a value is one you are keeping — an unrecognised value is rejected, not accepted.
- A value never changes. Renaming an option is free and safe; renumbering it would re-point every record already storing it. A removed option's number is retired rather than reissued.
Import and export
The values travel with the definition; they are not re-allocated on import. The source
environment's records already hold them, so a solution imported into Test or Prod has to
agree with Dev about what 270150001 means.
nextOptionValue travels for the same reason, one step removed. It is the next value the
set will hand out, and it is stored rather than worked out from the options present. If an
option was removed, the mark stays above the number that option held — so the number is
retired rather than handed to the next new option, which matters because a record
somewhere may still hold it. An importing environment that re-derived the mark from the
options it received would undo exactly that protection. A definition without a
nextOptionValue falls back to the derived mark, which is the best answer available for
a document that never recorded one.
An import checks what allocation would otherwise have guaranteed: every value must sit inside the publisher's range, no two options may share a value or a label, and the logical name must be free in the target environment. A value from another publisher's range is rejected rather than stored — accepting it would plant the very collision the range scheme exists to prevent.
Binding a field to a set
An entity field references a set by name, in the entity definition:
{ "logicalName": "par_status", "displayName": "Status", "dataType": "OptionSet", "optionSet": "par_invoicestatus" }
The column is a bigint holding the chosen option's value. The server rejects a write
whose value is not one of the set's options.
Example
{
"logicalName": "par_invoicestatus",
"displayName": "Invoice Status",
"description": "The lifecycle of an invoice",
"publisherPrefix": "par",
"optionValueBase": 270150000,
"nextOptionValue": 270150003,
"options": [
{ "value": 270150000, "displayName": "Draft", "description": "Not yet sent to the customer" },
{ "value": 270150001, "displayName": "Sent" },
{ "value": 270150002, "displayName": "Paid" }
]
}