The JSON schema every law YAML file must conform to: its fields, operations and vocabulary, and how versioned schema URLs work.
The law format is defined by a JSON Schema. All law YAML files in the corpus must conform to this schema.
Everything below the version history is generated from the released schema itself, so this page and the contract cannot disagree. Where a field has a concept page, the table links to it.
The current schema version is v0.7.0.
Schema URLs use immutable git tags to guarantee reproducibility. The format is:
The tag schema-vX.Y.Z is created when a schema version is released. Using tags instead of refs/heads/main ensures that the schema a law file references can never change underneath it. See RFC-013 for the rationale.
This table is the single source of truth for which schema version introduced which construct. RFCs that add a construct reference this table rather than asserting a version independently.
| Version | Introduces | RFC |
|---|---|---|
| v0.7.0 | placement on articles: the chapter, title, division and paragraph that enclose it, with their headings. markings replaces untranslatables (a flag on an article that is otherwise worked out, with resolution, resolved_by and target), declares records a document property an article establishes, and enables is removed. open_terms gains expected_source, decided_per_case_by and a closed delegation_type. An override can state voids: true with a legal_text_excerpt. Operations DATE_PART and START_OF. Not additive: a file carrying untranslatables or enables does not validate under v0.7.0 and goes through law-migrate | RFC-031, RFC-032, RFC-027 |
| v0.6.0 | No new constructs. Fills in the description of every field that lacked one, rewrites 39 that were already there, and adds examples, so the schema documents itself and this page can render it. Both keywords are annotation-only, so a file valid under v0.5.9 is valid under v0.6.0 unchanged | RFC-040 |
| v0.5.9 | RECHTSPOSITIE added to produces.legal_character and to the hooks.applies_to filter: a legal position of a person that arises by operation of law, without a besluit and without an aanvraag | RFC-038 |
| v0.5.8 | nullable on parameters, inputs and outputs: whether null (absence) is a legitimate value of the field; defaults to false, and the validator and the engine hold a law to it. Also corrects the description of required on execution parameters: it defaults to true, and an omitted required: false parameter is an unknown value, not an error | RFC-036 |
| v0.5.7 | FOREACH operation (iteration over a variable-length collection) | RFC-016 |
| v0.5.6 | Quantity unit labels (euro, ratio, percentage) and structured definitions constants carrying type/type_spec | RFC-023 |
| v0.5.5 | ROUND, CEIL, FLOOR rounding operations (explicit statutory rounding) | RFC-024 |
| v0.5.4 | DATE_DIFF operation; date operands for the comparison operators | RFC-021 |
| v0.5.3 | valid_to (law end date) | RFC-019 |
| v0.5.2 | annotation-schema.json for stand-off notes | RFC-005, RFC-018 |
| v0.5.1 | Tag-based immutable schema URLs; refinements within the v0.5.x line | RFC-013 |
| v0.5.0 | hooks, overrides (reactive execution); procedure, procedure_id (Awb lifecycle); Woo support | RFC-007, RFC-008 |
| v0.4.0 | open_terms, implements (IoC); legal_character; date and array value types | RFC-003 |
| v0.3.2 | SUBTRACT_DATE split into its own date operation with a unit (days, months, years) | |
| v0.3.1 | Structured references on articles (bwb_id, artikel, lid, onderdeel, …) for runtime cross-reference resolution | |
| v0.3.0 | Typed operation definitions (arithmetic, logical, comparison, conditional, switch) with semantic operands and legal_basis, replacing the single untyped operation | RFC-004 |
| v0.2.0 | Initial public schema: regulatory_layer, competent_authority, execution.produces, cross-law references (source) | RFC-001, RFC-002 |
Multi-organization execution (RFC-009) reuses competent_authority (v0.2.0) and adds no schema construct of its own. Absent and unknown values (RFC-036) add one construct, the nullable flag of v0.5.8, and otherwise fix what the engine does with existing constructs over a missing value; v0.5.8 also corrects the wording of required, whose text said “defaults to false” while the engine has always read an absent flag as required.
The annotation schema (annotation-schema.json) versions independently of the law schema: it is republished only when it changes. It was introduced at schema/v0.5.2/ and last republished at schema/v0.5.3/, which is the version the validators embed.
Validate law files against the schema:
For a guided walkthrough of how a law is written, rather than a field list, see Law Format.
A law file is one regulation at one point in time. These 21 keys sit at the top level; articles carries the text and, where a provision has been translated, its machine_readable section.
namestringpublication_datestring^[0-9]{4}-[0-9]{2}-[0-9]{2}$.
valid_fromstring^([0-9]{4}-[0-9]{2}-[0-9]{2}|#.+)$.
valid_tostring^[0-9]{4}-[0-9]{2}-[0-9]{2}$.
regulatory_layerstringdelegation_type, not by this field alone.urlstringvalid_from.bwb_idstring^BWBR[0-9]{7}$.
celex_nummerstringelistringtractatenblad_idstringunts_nummerstringgemeente_codestring^GM[0-9]{4}$.
provincie_codestring^PV[0-9]{2}$.
waterschap_codestring^WS[0-9]{4}$.
officiele_titelstringstcrt_idstringorganisationstringlegal_basisarray of objectlegal_basis a field or action carries, which cites a provision far more precisely.preambleobjectarticlesarray of objectmachine_readable is what makes one executable.procedurearray of objectWhich identifier a file must carry depends on what kind of instrument it is. A national law is identified by its BWB number, an EU regulation by its CELEX number, a municipal ordinance by the CBS municipality code plus its official title. The schema states this as conditional requirements:
legal_basis[]The articles of higher law that authorize this regulation. Not the same shape as the legal basis a field or action carries: this one names a law by law_id and nothing finer than an article.
law_idstringarticlestringdescriptionstringpreambleThe aanhef, the passage before Article 1 that names the authority the regulation is made under. It carries no operative norm of its own, so it rarely computes anything.
textstringurlstringmachine_readableMachine readable sectionprocedure[]AWB-defined procedure lifecycles for administrative decisions (RFC-008).
idstringdefaultbooleanapplies_toobjectproduces.stagesarray of objectprocedure[].stages[]The lifecycle stages in order, from the application to the final decision. Each names itself and what it needs before it can complete.
namestringdescriptionstringrequiresarray of objectEach entry of articles is one provision: its number, its verbatim text, and the URL it was taken from. machine_readable is present only where the provision has been translated, which for most of the corpus is not yet the case. An article without it is carried as text and nothing more.
numberstringtextstringurlstringmachine_readableMachine readable sectionreferencesarray of objectplacementobjectarticles[].references[]Structured references to other laws and articles. Accepted by the schema, but the Rust law-model has no field for it and drops it on load, so nothing resolves these at runtime today.
idstringbwb_idstring^BWBR[0-9]{7}$.
artikelstringlidstringonderdeelstringhoofdstukstringparagraafstringafdelingstringarticles[].placementWhere this article sits in the document: the containers that enclose it, each with its number and its opschrift. The opschrift is condensed legal classification written by the legislator, and it decides questions the article text alone cannot answer. Awb 3:9 only makes sense once you can see it sits in 'Afdeling 3.3 Advisering', because the adviseur definition of 3:5 lid 1 is scoped to that division. Absent for an article that no container encloses, which is normal in a short law.
boekobjectdeelobjecthoofdstukobjecttiteldeelobjectafdelingobjectparagraafobjectAn article carries this when it has been translated. It holds the executable part (execution) and the annotations around it: what the article delegates, what it implements, which authority decides, and what could not be translated.
endpointstringcompetent_authoritystring or objectrequiresarray of objectopen_termsarray of objectimplementsarray of objectdefinitionsobjectnaam: 123) or the optionally-structured form carrying a type and type_spec so the constant can declare its quantity-kind (RFC-023): naam: {value: 123, type: amount, type_spec: {unit: eurocent}}.executionobjecthooksarray of objectoverridesarray of objectmarkingsarray of objectdeclaresarray of objectmachine_readable.requires[]Other provisions this article depends on. The schema and the Rust law-model disagree on the shape here: the model expects a list of names, so a law written to the structure below fails to load.
articlestringlawstringregelingstringkoninklijk_besluitstringvaluesarray of stringmachine_readable.open_terms[]Abstract values that this article leaves open and that an implementing regulation or implementing policy fills at a lower level. Any regulatory layer can declare open_terms; a law typically has required: true (no default), lower layers often provide defaults that can be refined further down. This is the single place for an open norm, whether the article names who fills it ('bij ministeriele regeling') or leaves that open ('redelijkerwijs'); in the second case delegated_to and delegation_type are simply absent and any competent authority may fill it through implementing policy. Whether the filling regulation is currently in the corpus is a state of the corpus and not a property of the law, so it is not recorded here.
idstringdescriptionstringtypestringrequiredbooleantrue.
delegated_tostringdelegation_typestringlegal_basisstringdefaultobjectimplements. It applies in two cases: no implementing regulation was found, and an implementation ran but returned null for this case.expected_sourcestringdecided_per_case_bystringmachine_readable.implements[]Declares that this article fills open terms from a higher-level law. Maps to the 'Gelet op' clause in Dutch legislation. One article can implement multiple open terms from different laws.
lawstringarticlestringopen_termstringgelet_opstringmachine_readable.hooks[]Hook declarations: this article fires when matching lifecycle events occur (RFC-007).
hook_pointstringapplies_toobjectlegal_character is required; adding decision_type or stage narrows it further.machine_readable.overrides[]Override declarations: this article replaces another article's output (RFC-007, lex specialis).
lawstring$id of the law whose output is being replaced: lex specialis written down. An override from another law fires only when that law is the one being executed, so with no such context it does not apply (RFC-007). An override on an output of its own law applies whatever the contextual law is, because an exclusion in the same law is not a claim about the asking law (RFC-041). The engine does not check that the override is legally valid.articlestringoutputstringvoidsbooleanlegal_text_excerptstringmarkings already do. A closed vocabulary was considered and rejected: it would make the agent translate the statute into a category, which is the interpretive step this format exists to avoid. A quotation only has to be copied, and a check can hold it against the article's own text.machine_readable.markings[]Constructs in this article that the format itself cannot express. A marking is a flag on an article that is otherwise worked out: it names the one thing that does not fit and leaves everything that does fit standing. A marking that empties an article is a defect, not a translation. Keep it strictly apart from open_terms, which say the opposite: the language can express this fine, the content is filled elsewhere. Replaces untranslatables and norm_gaps from earlier schema versions.
aboutstringreasonstringresolutionstringresolved_bystringtargetarray of stringaccepted and the untranslatable modes of RFC-012, which do not read this field; whether a marking should ever block only the values it names is an open question in RFC-031.legal_text_excerptstringacceptedbooleanfalse.
machine_readable.declares[]Document properties this article establishes. A citation title, a commencement date or a scope in time is not a calculation, and it is not nothing either: it fixes a value the rest of the corpus and every trace depend on. Article 51 of the Awir says what that law is called, and a trace that names the law is quoting that article. Recording it here makes the article accounted for and lets a check hold the document header against the provision that decides it.
propertystringvalueanyapplies_fromstringhooks[].applies_toWhich decisions of other articles this hook fires on. legal_character is required; adding decision_type or stage narrows it further.
legal_characterstringdecision_typestringstagestringThe executable core. Values come in as parameters andinput, actions compute, and outputdeclares what the article yields.
producesobjectlegal_character and decision_type select which hooks fire, and legal_character with procedure_id selects the Awb procedure, so an article's outputs and how it runs both depend on what is declared here.parametersarray of Parameter fieldinputarray of Input fieldsource.outputarray of Output fieldactionsarray of Actionexecution.producesWhat executing this article yields in legal terms: its legal character, the kind of decision it is, and the procedure that governs it. Not merely annotation: legal_character and decision_type select which hooks fire, and legal_character with procedure_id selects the Awb procedure, so an article's outputs and how it runs both depend on what is declared here.
legal_characterstringdecision_typestringGEEN_BESLUIT is the value for an article that establishes a fact rather than deciding anything.procedure_idstringOne assignment to one output. It either states a literal valueor computes one with an operation, never both.
outputstringvalueOperation valueoperation, that operation wins and this field is read as one of its operands.operationOperation typevalue, this wins and that field is read as an operand.valuesarray of Operation valueprecisionintegerresolveobjectlegal_basisLegal basisaction.resolveLook up the value in whichever regulation implements it. Accepted by the schema, but the Rust law-model has no field for it and drops it on load; no law in the corpus uses it.
typestringoutputstringmatchobjectParameters, inputs and outputs share one shape and add to it. Note the singular spelling: the keys are input and output, not their plurals.
Every parameter, input and output carries these. The three kinds below repeat them, so each section is the complete list for that kind.
namestring$name. This is the name another law asks for when it reads this value through source, so renaming an output is a change other laws can see.descriptionstringtypestringamount is counted in is declared separately, in type_spec.unit; units are opt-in per law, and an un-annotated value has unit unknown and is never checked (RFC-023).nullablebooleannull (absence: the register or the law says there is none) is a legitimate value of this field. Defaults to false: a non-nullable input may never receive null from a data source, another law or a caller, a non-nullable output may never evaluate to null, and a non-nullable parameter may never be passed null. On a required parameter the flag means the same at the top level and across laws: a nullable one is run with the null and decides on it, a non-nullable one is a caller error at the top level and skips the call across laws. Unknown (a value nobody has) is not governed by this flag. RFC-036.type_specobjecttemporalobjectlegal_basisLegal basisfield.type_specAdditional type specifications for numeric and temporal values.
unitstringratio (0-1) and percentage (0-100) are distinct labels; any division by 100 is an explicit value operation, never implied by the label.precisionintegerminnumbermaxnumberfield.temporalHow the value relates to time: whether it is measured at a moment or over a period, and which date it is anchored to (RFC-001). Declared in the schema but not yet read by the engine, and unused in the corpus.
typestringperiod_typestringreferencestring or Variable referenceA value the caller passes in, rather than one the law looks up or derives. Adds required, which defaults to true; an omitted required: false parameter resolves to an unknown value rather than an error (RFC-036).
namestring$name. This is the name another law asks for when it reads this value through source, so renaming an output is a change other laws can see.descriptionstringtypestringamount is counted in is declared separately, in type_spec.unit; units are opt-in per law, and an un-annotated value has unit unknown and is never checked (RFC-023).nullablebooleannull (absence: the register or the law says there is none) is a legitimate value of this field. Defaults to false: a non-nullable input may never receive null from a data source, another law or a caller, a non-nullable output may never evaluate to null, and a non-nullable parameter may never be passed null. On a required parameter the flag means the same at the top level and across laws: a nullable one is run with the null and decides on it, a non-nullable one is a caller error at the top level and skips the call across laws. Unknown (a value nobody has) is not governed by this flag. RFC-036.type_specobjecttemporalobjectlegal_basisLegal basisrequiredbooleanrequired: false that the caller omits resolves to an unknown value that names the parameter (RFC-036), so a law can declare application-form fields another law never passes.A value the law looks up elsewhere: another law's output, or a fact from a register. Adds source, which is required and says where the value comes from (RFC-007).
namestring$name. This is the name another law asks for when it reads this value through source, so renaming an output is a change other laws can see.descriptionstringtypestringamount is counted in is declared separately, in type_spec.unit; units are opt-in per law, and an un-annotated value has unit unknown and is never checked (RFC-023).nullablebooleannull (absence: the register or the law says there is none) is a legitimate value of this field. Defaults to false: a non-nullable input may never receive null from a data source, another law or a caller, a non-nullable output may never evaluate to null, and a non-nullable parameter may never be passed null. On a required parameter the flag means the same at the top level and across laws: a nullable one is run with the null and decides on it, a non-nullable one is a caller error at the top level and skips the call across laws. Unknown (a value nobody has) is not governed by this flag. RFC-036.type_specobjecttemporalobjectlegal_basisLegal basissourceobjectinput[].sourceReference to where this input comes from.
regulationstringoutputstringparametersobject$-reference to a value in scope, as in bsn: $bsn.descriptionstringendpointstringA value the article yields. Adds nothing to the common fields, and that is the point: every output is callable from another law through source, which asks for it by name (RFC-007). Callable is not the same as presentable, and what a portal offers a citizen is derived from produces, not from a flag on the output (RFC-038).
namestring$name. This is the name another law asks for when it reads this value through source, so renaming an output is a change other laws can see.descriptionstringtypestringamount is counted in is declared separately, in type_spec.unit; units are opt-in per law, and an un-annotated value has unit unknown and is never checked (RFC-023).nullablebooleannull (absence: the register or the law says there is none) is a legitimate value of this field. Defaults to false: a non-nullable input may never receive null from a data source, another law or a caller, a non-nullable output may never evaluate to null, and a non-nullable parameter may never be passed null. On a required parameter the flag means the same at the top level and across laws: a nullable one is run with the null and decides on it, a non-nullable one is a caller error at the top level and skips the call across laws. Unknown (a value nobody has) is not governed by this flag. RFC-036.type_specobjecttemporalobjectlegal_basisLegal basisA citation back to the provision a value or a step rests on. Any field, any action and any operation may carry one; it is what lets an execution trace name the article behind each number rather than only the number.
lawstringbwb_idstring^BWBR[0-9]{7}$.
articlestringparagraphstringsentencestringurlstringjuriconnectstringz=/g= parameters; most references in the corpus carry them, and the pattern does not require them.^jci1\.3:c:BWBR[0-9]{7}(&[a-zA-Z_]+=.+)*$.
explanationstringAn action either states a literal value or computes one with anoperation. There are 16 forms. Each is a closed object: the operation key picks the form and the schema rejects any other combination of keys, so a malformed action fails validation rather than executing as something unintended.
Wherever an operation below takes a value, three things may stand there:
Arithmetic operations that compute numeric values.
valuesarray of Operation valuelegal_basisLegal basisRounding operations (RFC-024). Round the single operand to precision decimal places, in the value's own unit (RFC-023). Rounding is never implicit - a law that rounds must say so here. ROUND is half-up (rekenkundig, the Hoge Raad default); CEIL rounds up (naar boven); FLOOR rounds down (naar beneden / afkapping).
valueOperation valueprecisionintegerlegal_basisLegal basisLogical operations that combine boolean conditions.
conditionsarray of Operation valuelegal_basisLegal basisComparison operations that compare values. Operands may be numbers or ISO 8601 dates (YYYY-MM-DD); when both operands are dates they are compared chronologically (RFC-021). value is required: all five of these compare two operands, and a comparison with only a subject compares against nothing. It was optional, and the Rust model's untagged fallback turned such an operation into a literal object without an error, a warning or a line in the trace, so the execution carried on and produced an answer that looked complete.
subjectOperation valuevalueOperation valuelegal_basisLegal basisLogical NOT - negates a boolean value.
valueOperation valuelegal_basisLegal basisConditional operation with multiple cases evaluated in order.
casesarray of objectwhen holds decides the value. Order is significant, so a narrower case belongs before a broader one.defaultOperation valuenullable. A case whose condition is unknown takes neither its branch nor the default.legal_basisLegal basisIF.cases[]Cases evaluated in order; the first whose when holds decides the value. Order is significant, so a narrower case belongs before a broader one.
whenOperation valuethenOperation valueMembership test - checks if subject is in a list.
subjectOperation valuevalueOperation valuevalues instead to write the list inline.valuesarray of Operation valuelegal_basisLegal basisConstruct an array from items.
itemsarray of Operation valuelegal_basisLegal basisIterate over a collection, evaluating an expression per element (RFC-016).
collectionOperation valueasstring^[a-z_][a-z0-9_]*$.
filterOperation valuebodyOperation valuecombinestringlegal_basisLegal basisCalculate age in complete years between two dates.
date_of_birthOperation valuereference_dateOperation valuelegal_basisLegal basisAdd years, months, weeks, and/or days to a date. Applied coarsest-to-finest: years → months → weeks → days. Month/year additions use the Dutch legal 'corresponding numbered day' rule: the day is clamped to the last day of the target month (e.g., Jan 31 + 1 month = Feb 28).
dateOperation valueyearsOperation valuemonthsOperation valueweeksOperation valuedaysOperation valuelegal_basisLegal basisConstruct a date from year, month, day components.
yearOperation valuemonthOperation valuedayOperation valuelegal_basisLegal basisGet the day of the week (0=Monday, 6=Sunday).
dateOperation valuelegal_basisLegal basisSigned difference between two dates, expressed in a chosen unit. Positive when 'to' is on or after 'from'. Months and years count complete calendar units (RFC-021).
fromOperation valuetoOperation valueinstring or Variable referencelegal_basisLegal basisRead one calendar component out of a date. The inverse of DATE, so 'in' ranges over exactly the components DATE takes. Returns an integer: the year number, the month number (1-12) or the day of the month (1-31). A month number is only meaningful within one year — comparing two month numbers across a year boundary is almost always wrong. For the weekday, use DAY_OF_WEEK.
dateOperation valueinstringlegal_basisLegal basisTruncate a date down to the start of the calendar unit it falls in: 1 January of its year, or the first day of its month. Returns a date (ISO 8601 YYYY-MM-DD). For 'the first day of the month following', truncate first and add afterwards — DATE_ADD over START_OF — so the day-clamping of DATE_ADD never comes into play and the first of the month yields the next first.
dateOperation valueinstringlegal_basisLegal basisThe closed value sets a law picks from. These are the terms the rest of the documentation uses, so each one links to where it is explained.
regulatory_layerType of legal instrument (regelgevingslaag). It fixes which identifier the file must carry, and it ranks candidates when several regulations implement the same open term. Which layer may implement a term is set by that term's delegation_type, not by this field alone.
machine_readable.execution.produces.legal_characterLegal character of the output. BESCHIKKING: a besluit not of general scope (Awb art. 1:3 lid 2). RECHTSPOSITIE: a legal position of a person that arises by operation of law, without a besluit and without an aanvraag (voting rights, majority, nationality).
machine_readable.execution.produces.decision_typeThe kind of decision this output is, within its legal character. GEEN_BESLUIT is the value for an article that establishes a fact rather than deciding anything.
type / type_spec.unitValue type. What an amount is counted in is declared separately, in type_spec.unit; units are opt-in per law, and an un-annotated value has unit unknown and is never checked (RFC-023).
type_spec.unitUnit of measurement for the value (RFC-023). A label, never a computational constraint: tagging a value never changes it. ratio (0-1) and percentage (0-100) are distinct labels; any division by 100 is an explicit value operation, never implied by the label.
The sections above are generated from the released schema at build time, so this page cannot describe a schema other than the released one. That guard is about the version, not the wording: a description that has fallen behind what the engine does is still a description this page will render faithfully. RFC-040 sets out why the schema carries its own documentation and how the generated page is held to it.
The version history is not generated: it records what a version introduced and why, which is a judgement no schema can state about itself.
The schema is JSON Schema draft-07. It uses none of the constructs that changed in later drafts, so it would migrate to 2020-12 mechanically; the one keyword that would be worth having is deprecated, which arrived in 2019-09. That is a decision for its own RFC, since the schema is a contract other engines read (RFC-013) and draft-07 is the most widely implemented dialect.
One consequence of draft-07 shows up here. Where a field is a $ref to another definition, a description written next to that $ref is formally ignored: draft-07 says a $ref replaces its siblings. This page reads those descriptions anyway, falling back to the target’s, so nothing is lost in what you see. An editor that follows the specification strictly will not show them.
An exploration by Bureau Architectuur of the Dutch Ministry of Economic Affairs and Climate Policy into the possibilities of transparent, executable legislation.
GitHub repository
How it works
Stay informed
Roadmap (Dutch)
Documentation
Research
Bureau Architectuur
Ministry of Economic Affairs and Climate Policy