This is the data contract. Software packages have their own release versions and schema support.
Schema 0.3.0 · 7 September 2026
Vazen TOML Specification
Introduction
A Vazen TOML file describes one or more retail fixtures and the products laid
out on them. It is plain text in TOML 1.0 (opens in a new tab), UTF-8 encoded,
with the extension .vazen.toml, and is parsed by the standard TOML library of
any language.
A file represents one of two stages of a plan, declared by its schema field:
vazen/layout: fully laid out. Every physical element carries a complete position and dimensions, so the file can be rendered, measured or analysed directly.vazen/spec: intent. The same structure, but any position or dimensions may be omitted. A layout engine fills in the missing numbers to produce a layout.
This document defines the shape of the data. How a layout engine turns a spec into a layout, and how a renderer draws one, are left to their implementers.
Structure
A file holds one project. A project holds a flat list of products and a list of fixtures. A fixture holds a tree of equipment, and equipment holds the sites where products are placed:
Project
├── Product (list) products available to this plan
└── Fixture (list) a physical unit, e.g. one gondola or end of aisle
└── Equipment (tree) typed elements: bay, shelf, peg, ...
├── Equipment equipment can nest, e.g. shelves in a bay
└── Site (list) a placement of one product, with facings
- Product: something that can be placed. Defined once, referenced from any number of sites.
- Fixture: a self-contained physical unit of shelving.
- Equipment: the parts a fixture is built from. Each has a
type, for examplebayorshelf. Equipment may contain further equipment or sites. The set of types is open: consumers must accept values they do not recognise. - Site: an instance of a product placed on equipment, with facing counts and, in a layout, a position and dimensions.
Fields
A layout is complete. Every field a layout engine could supply is written, defaults included, so a consumer of a layout never fills anything in. A spec may omit any of them. A few fields are required at both stages because nothing can supply them: the schema, a product’s name, an equipment’s type, and the product a site places. The Required column in each table says which; a blank cell is optional at both stages. Unique means unique among its kind within the file.
A list field (products, fixtures, equipment, sites) may be absent or
empty, and the two mean the same.
Any key not listed in the tables below is a descriptive or namespaced attribute; see Attributes.
Project
| Field | Type | Required | Notes |
|---|---|---|---|
schema |
string | always | "vazen/spec" or "vazen/layout" |
schema_version |
string | always | The version of this specification the file follows, as a Semantic Versioning string, e.g. "0.3.0" |
name |
string | Project name | |
products |
array | See Product | |
fixtures |
array | See Fixture |
Product
| Field | Type | Required | Notes |
|---|---|---|---|
ref |
string | layout | Short for reference: an opaque string for use in selectors. Unique. See Product selectors |
name |
string | always | The short human-readable label, as on a shelf edge label (GS1 descriptionShort) |
description |
string | The long description, where one exists (GS1 tradeItemDescription) |
|
gtin |
string | A string of 8, 12, 13 or 14 digits. Not required to be unique within a file: a shelf test can hold two variants of one GTIN | |
dimensions |
table | layout | Physical dimensions of the product in mm: { width, height, depth }. Not the pack size. Required in a layout only for a product a site selects |
form |
string | What kind of object this is, e.g. "tray", "case", "display". Open vocabulary, descriptive only. See Packaging levels |
|
images |
table | Images of the product, keyed by face: front, back, left, right, top, base, as defined under Orientation. Each value is either a URL string or a table with a url key and any further keys, e.g. images = { front = "https://…" } or images = { front = { url = "https://…", crop = { x = 200 } } }. An image shows its face head-on and upright |
Fixture
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | layout | A stable identifier, e.g. a plan reference or a retailer’s fixture code. Unique. A layout engine assigns one to any fixture that arrives without |
name |
string | Display name | |
dimensions |
table | layout | { width, height, depth }, mm |
sort_order |
number | See Ordering | |
equipment |
array | See Equipment |
Equipment
| Field | Type | Required | Notes |
|---|---|---|---|
type |
string | always | e.g. "bay", "shelf", "peg". Open set; do not reject unknown values |
sort_order |
number | See Ordering | |
position |
table | layout | { x, y, z } in the fixture’s frame, mm |
dimensions |
table | layout | { width, height, depth }, mm |
equipment |
array | Child equipment | |
sites |
array | Product placements |
Site
| Field | Type | Required | Notes |
|---|---|---|---|
product |
table | always | A selector naming the product placed. See Product selectors |
orientation |
string | layout | How the product faces the shopper. "F" is front-facing and upright. Defaults to "F" in a spec. See Orientation |
facings |
table | layout | { wide, high, deep }: how many facings across, up and back. Each defaults to 1 in a spec. Must be greater than zero; fractional values are legal |
sort_order |
number | See Ordering | |
position |
table | layout | { x, y, z } in the fixture’s frame, mm |
dimensions |
table | layout | { width, height, depth } of the space allocated to the site, mm. Never smaller than the facings block; see below |
A site’s dimensions is the space allocated to the placement. The facings
occupy the facings block defined under Orientation; the allocation is at least
that block on every axis, and is often exactly it. It may be larger: planning
tools commonly spread a product’s facings evenly across a wider space, or hold
space open beside them, and in files converted from those tools the allocation
records that decision while the facings record the count. How facings are
distributed within a larger allocation is not yet specified; until it is,
consumers should treat the block as contiguous and aligned to the allocation’s
front-bottom-left corner unless a namespaced attribute says otherwise. Treat
dimensions as authoritative for space and the facings as authoritative for
count; a consumer that needs only one should prefer dimensions.
Product selectors
A site names its product with a selector: a small table of fields that the product must match. To resolve a selector, find the product in the file’s product list whose fields match every key given. A selector must resolve to exactly one product; zero or several matches is an error, not a fallback.
Any product field can appear in a selector. Any key on the product may be used,
standard or descriptive, provided its value is a scalar. Values match by
equality; a table-valued field such as dimensions cannot be selected on. This
is how specs are naturally written, naming products by identifiers the author
already holds, and adding a field wherever one is not enough on its own:
product = { gtin = "5000000000012" }
product = { gtin = "5000000000012", form = "tray" }
ref (short for reference) is the special case. It is an opaque string a
producer attaches to a product, unique within the file, so that a selector on it
always resolves without further thought:
product = { ref = "p_9f3k2v" }
A selector on ref is resolved by the same matching rule as any other; nothing
about it is different to a consumer except that it is guaranteed to be unique.
Files produced by software commonly use it, since the producer has already
resolved the product and need not rebuild a meaningful selector. A ref is valid
only within the file that carries it and may differ between two files describing
the same plan. For identity across files, match on durable fields. Where a file
contains products sharing a GTIN, a producer ensures they differ in another
field, such as name or form.
A ref carries no meaning: it is a string the producer made up, from a hash, a counter or anything else, and nothing about the product can be read from it. Nor does it last: a file written again may carry different refs. So a ref serves selectors in its own file and nothing else; a tool that needs to find a product later keeps the product’s durable fields, and matches on those.
Attributes
Every object in a file (project, product, fixture, equipment, site) carries its keys at the top level, and those keys are of three kinds.
Standard keys are the ones the field tables above define. They carry the semantics a validator or layout engine acts on, and the specification is their only source.
Descriptive keys are any other plain key. They belong to the producer of the
file and mean whatever the producer says: brand = "Example Brand",
supplier_code = "SUP-4471", store_cluster = "urban-small". They carry no
semantics in this specification, and there is deliberately no list of them:
product and fixture master data is unbounded and different in every
organisation. Write them in lowercase snake_case, as the standard keys are.
Values are TOML scalars or arrays of scalars.
Namespaced keys follow the pattern _namespace__key: a single leading
underscore, the namespace, a double underscore, the key. They are for data whose
meaning a tool owns and depends on, and that must not be confused with anyone
else’s: values derived by a layout engine, fields preserved for a round trip to
another format, conventions shared between tools. The test is simple: if another
tool has to understand this key exactly, namespace it; if it is simply your
data, do not.
A namespace is the full domain you control, including the top-level domain, in
lowercase, with dots replaced by underscores: vazen.com gives _vazen_com,
example.org gives _example_org, so a supplier code owned by example.org is
_example_org__supplier_code. A department of a large organisation may add
itself as another segment, for example _example_org_retail_lab. This keeps DNS
uniqueness without a central register. Never a generic word: _data, _custom,
_ext and _x collide immediately and say nothing.
Two short namespaces are reserved and do not follow the domain rule: _vazen
for this specification’s own tooling, and _psa for the .psa importer
published here. A source format whose importer this specification publishes
takes the format’s name. _vazen__unit_count, _psa__custom_attribute_9.
Consumers honour standard keys, may ignore descriptive keys, ignore namespaced keys they do not recognise, and, if they write a file back out, preserve every key they do not understand, plain or namespaced. That last rule is what makes round trips work.
There is no registry. A list of namespaces that published tools emit, with links to what their keys mean, will be kept on this site; publishing yours there is how others learn what your keys mean.
Descriptive keys in wide and consistent use may be promoted to standard keys in a later version; see Versioning.
Packaging levels and related products
A product is often placed as something other than a loose unit: a shelf-ready
tray, a case, a display unit. Each is a different physical object with its own
dimensions and appearance, so each is its own entry in products. A tray and
the unit inside it are two products, and a site references whichever actually
sits on the shelf. Every product is self-describing: its name, identifiers,
dimensions and images are on the entry itself, so a consumer never resolves one
product in order to draw or list another.
form says what kind of object a product is: "tray", "case", "display",
or any other name. The vocabulary is open and the field is descriptive only; no
rules attach to it. It also makes a clean selector where a tray shares its
unit’s GTIN: { gtin = "5000000000012", form = "tray" }.
Relationships between products are not first-class fields in this version.
Grouping works as it does for any attribute: products that belong together carry
a common value, a brand or a family key, and consumers group by equality.
Quantitative relationships, such as how many units a tray holds, are expressed
by producers in their own namespace until a convention proves broadly useful and
is promoted to a field. Vazen’s producers write _vazen__group on every product
in a family and _vazen__unit_count on each packaging level, giving the number
of units inside.
How the units within a site are arranged, loosely piled or stacked like logs, is likewise not yet specified. A later version will add fill rules at the site level.
Units and coordinates
All dimensions and coordinates are millimetres, written as numbers. There is no unit setting: a number is always a millimetre. Geometry appears as two inline tables on each physical element:
position = { x = 0, y = 0, z = 0 }
dimensions = { width = 0, height = 0, depth = 0 }
Every position, equipment and sites alike, is absolute in its fixture’s frame:
a single origin at the fixture’s front-bottom-left corner.
xincreases left to rightyincreases bottom to topzincreases front to back
width spans x, height spans y, depth spans z. Every dimension is
strictly positive: a zero or negative width, height or depth is invalid.
Directions are given for a shopper standing in front of the fixture: x runs to
their right, y up, z away from them into the fixture. These axes are
left-handed. A right-handed engine (three.js, Blender, Unity) given the values
verbatim will show the scene mirrored, as though viewed from behind. To view
from the front, negate z on import (z_engine = -z_file) and leave x and
y unchanged.
An element’s dimensions is its bounding box, including the physical element
itself. A shelf’s box spans from beneath its steel up to the space it governs,
so sibling shelves tile their bay exactly, with no gap between them. A site’s
position.y is the height of its product-bearing surface.
In a vazen/spec file, any position or dimensions may be omitted. In a
vazen/layout file, the bounding boxes of sibling elements never overlap.
Ordering
Sibling fixtures, equipment and sites each carry an optional sort_order
number. Siblings read in ascending sort_order: left to right along a shelf,
bottom to top within a bay. Values need not be consecutive; inserting between 1
and 2 at 1.5 is legal and expected. Producers write arrays already in
sort_order.
In a spec, sort_order is authoritative for reading order. In a layout,
geometry is authoritative, and any sort_order present must agree with it.
Disagreement is a validation error, not a tie to be broken.
Orientation
orientation names the face of the product cuboid turned towards the shopper,
and a rotation. It is one of 24 codes: F, Fl, Fr, Fu, B, Bl, Br,
Bu, L, Ll, Lr, Lu, R, Rl, Rr, Ru, U, Ul, Ur, Uu, D,
Dl, Dr, Du. The capital letter is the face shown: Front, Back, Left,
Right, Up (the top) and Down (the base). The lowercase letter is a twist of that
face as the shopper sees it: l turns the top of the shown face to the
shopper’s left, r to their right, u turns it upside down. A product’s faces
are named as it stands front-facing and upright, so its left side is the side on
the shopper’s left. For "F" the twist is the side the product comes to rest
on; the Name column gives what it rests on for every code. "F" is a
front-facing upright product. In a spec it is the default and may be omitted; in
a layout it is written like any other value.
The codes read like the moves of a Rubik’s Cube: a cuboid has exactly 24 distinct orientations, each named by the face it shows and the twist applied to it.
A product’s dimensions gives its width, height and depth as it stands
front-facing and upright ("F"): width along x, height along y, depth along
z. Every other orientation is a rotation of that cuboid, so each product
dimension ends up along a different axis. The table gives each code’s name and
which product dimension lies along x, y and z. Names follow one rule: the
face towards the shopper, then how the product rests. A product rests on its
base (upright), its top (upside down), its front (face down), its back (on its
back), or its left or right side. Faces and sides are the product’s own, named
from the front-facing upright position, and travel with it as it turns. The
facings block is the mapped x dimension × facings.wide, the mapped y
dimension × facings.high, the mapped z dimension × facings.deep.
Rotating a product onto its left or right side (l, r) swaps the dimensions
along x and y; the two codes differ only in which way the artwork turns, not
in the space occupied. Turning it upside-down (u) leaves the dimensions
unchanged. For the U and D faces the product lies flat, so its depth runs
along y: with U shown, upright means the product’s front is toward the
bottom; with D shown, its back is.
| Code | Name | Along x |
Along y |
Along z |
|---|---|---|---|---|
F |
Front out, upright | width | height | depth |
Fl |
Front out, on its left side | height | width | depth |
Fr |
Front out, on its right side | height | width | depth |
Fu |
Front out, upside down | width | height | depth |
B |
Back out, upright | width | height | depth |
Bl |
Back out, on its right side | height | width | depth |
Br |
Back out, on its left side | height | width | depth |
Bu |
Back out, upside down | width | height | depth |
L |
Left side out, upright | depth | height | width |
Ll |
Left side out, on its back | height | depth | width |
Lr |
Left side out, face down | height | depth | width |
Lu |
Left side out, upside down | depth | height | width |
R |
Right side out, upright | depth | height | width |
Rl |
Right side out, face down | height | depth | width |
Rr |
Right side out, on its back | height | depth | width |
Ru |
Right side out, upside down | depth | height | width |
U |
Top out, face down | width | depth | height |
Ul |
Top out, on its left side | depth | width | height |
Ur |
Top out, on its right side | depth | width | height |
Uu |
Top out, on its back | width | depth | height |
D |
Base out, on its back | width | depth | height |
Dl |
Base out, on its left side | depth | width | height |
Dr |
Base out, on its right side | depth | width | height |
Du |
Base out, face down | width | depth | height |
Example
A layout-stage file describing an end of aisle with two shelves. Deleting the
sites’ position and dimensions lines and setting schema = "vazen/spec"
yields the corresponding spec.
schema = "vazen/layout"
schema_version = "0.3.0"
name = "Dishwash End of Aisle"
[[products]]
ref = "p_9f3k2v"
name = "Dishwasher Tablets 50 Pack"
gtin = "5000000000012"
dimensions = { width = 190, height = 240, depth = 120 }
_vazen__group = "dishwasher-tablets-50"
[[products]]
ref = "p_4t8rbn"
name = "Dishwasher Tablets 50 Pack, tray of 4"
gtin = "15000000000019"
form = "tray"
dimensions = { width = 385, height = 250, depth = 250 }
_vazen__group = "dishwasher-tablets-50"
_vazen__unit_count = 4
[[products]]
ref = "p_2m8qxz"
name = "Dishwasher Tablets 70 Pack"
gtin = "5000000000029"
dimensions = { width = 210, height = 260, depth = 130 }
[[fixtures]]
id = "AISLE-12-END"
name = "Aisle 12 End"
dimensions = { width = 1250, height = 2000, depth = 570 }
[[fixtures.equipment]]
type = "bay"
sort_order = 1
position = { x = 0, y = 0, z = 0 }
dimensions = { width = 1250, height = 2000, depth = 570 }
[[fixtures.equipment.equipment]]
type = "shelf"
sort_order = 1
position = { x = 0, y = 260, z = 0 }
dimensions = { width = 1250, height = 400, depth = 570 }
[[fixtures.equipment.equipment.sites]]
product = { ref = "p_4t8rbn" }
facings = { wide = 2, high = 1, deep = 2 }
orientation = "F"
sort_order = 1
position = { x = 25, y = 300, z = 0 }
dimensions = { width = 770, height = 250, depth = 500 }
[[fixtures.equipment.equipment]]
type = "shelf"
sort_order = 2
position = { x = 0, y = 660, z = 0 }
dimensions = { width = 1250, height = 400, depth = 570 }
[[fixtures.equipment.equipment.sites]]
product = { ref = "p_2m8qxz" }
facings = { wide = 3, high = 1, deep = 4 }
orientation = "F"
sort_order = 1
position = { x = 25, y = 700, z = 0 }
dimensions = { width = 630, height = 260, depth = 520 }
Reading it: one bay fills the fixture. Two adjacent shelves occupy y = 260 to
y = 1060; each shelf’s box starts beneath its steel, and the product surface
sits 40mm above that. The first shelf holds two trays of the 50 pack, two deep:
four trays, sixteen units. The second holds three facings of the 70 pack, four
deep.
JSON
A file’s tables may be written as JSON, with the extension .vazen.json. The
JSON has the same keys and the same values. Each table is an object, and each
array is an array. JSON suits a program that uses the data, or an API that
passes it on. TOML suits a file that people keep and edit, because JSON has no
comments and no dates. A date is written as RFC 3339 text with milliseconds:
"2026-09-27T09:30:00.000Z" or "2026-09-27T09:30:00.000+01:00" for an offset
date-time, "2026-09-27T09:30:00.000" for a local date-time, "2026-09-27" for
a local date, and "09:30:00.000" for a local time. Text in exactly one of
these forms reads as a date where a date can be: in an attribute or a product
selector. Any other text is text, and so is the text of a standard key. JSON’s
null has no TOML equivalent, so a file that holds one is invalid. TOML’s nan
and inf have no JSON equivalent, so a file that holds either cannot be written
as JSON.
Versioning
Every file declares the schema_version it follows. Versions follow Semantic
Versioning. While the major version is 0, a minor bump may change field names
and structure. Changes are recorded against each version, and the version field
lets a consumer handle files from different versions explicitly.
Standard keys are added only at a version bump. A key promoted from descriptive
use takes the meaning already prevailing in real files; where usage conflicts,
the specification chooses a different name rather than redefining one in use. A
file’s schema_version therefore fixes which of its keys are standard: a
descriptive key in a 0.3.0 file stays descriptive whatever a later version
defines.
Version history:
- 0.3.0: presentations removed, packaging levels are products with a
formfield; three kinds of key (standard, descriptive, namespaced) and a Required column in the field tables;facingsanddimensionsas tables;imageson products; facing counts and orientation written in layouts; site dimensions may exceed the facings block; single-fixture shorthand removed; orientation dimension table; ordering rule betweensort_orderand geometry; ref validity clarified. - 0.2.0: spec and layout stages; presentations; site orientation; explicit
ordering; fixture identifiers;
schema_versionreplacesvazen_version. - 0.1.0: initial specification.
Feedback
This specification is shaped by the people using it. If you are working with planogram data and something here is unclear, missing or awkward for your pipeline, write to toml@vazen.com. We are particularly keen to hear from engineers integrating shelf data, researchers working with space and range data, and anyone building tools that read or write the format.