Schema
A Schema declares a governed type — the itemtype URL it applies to and the properties, validators, and constraints that are enforced when items of that type are written or read.
When to reach for it
Declare a schema for any itemtype that needs cardinality enforcement, type validation, defaults, resolvers, uniqueness, referential integrity, or @validate rules. Items whose itemtype has no matching schema are stored unchanged, without any of those checks.
Shape
A schema is an HTML element with itemscope itemtype="https://pagelove.org/Schema" and a type child that names the governed URL. Everything else is optional.
<div itemscope itemtype="https://pagelove.org/Schema">
<meta itemprop="type" content="https://example.com/Person">
<meta itemprop="parent" content="https://example.com/Entity">
<div itemprop="property" itemscope itemtype="https://pagelove.org/Property">
<!-- See the Property reference -->
</div>
<div itemprop="constraint" itemscope itemtype="https://pagelove.org/GroupConstraint">
<!-- See the Required combinations reference -->
</div>
<script type="text/sessel" itemprop="@validate">
<!-- Schema-level validator -->
</script>
</div>
Fields
| Field | Cardinality | Accepts | Purpose |
|---|---|---|---|
type |
1..1 |
URL | The itemtype URL this schema governs. A schema with no type is silently skipped. |
name |
0..1 |
Text | Human-readable name. Used in error messages and tooling. |
description |
0..1 |
Text | Human-readable description of what the type represents. |
parent |
0..1 |
URL | Governed type URL of a parent schema to inherit from. |
property |
0..n |
Nested Property item | A property declaration. |
constraint |
0..n |
Nested Required combinations item | A group-level cardinality rule across several properties. |
@validate |
0..1 |
Sessel or JavaScript binding | Schema-level validator. Must return truthy. See below. |
Inheritance
A schema with a parent field inherits everything the parent declares — properties, group constraints, resolvers, and validators. The chain is walked from root to leaf; a child property with the same name as a parent property overrides the parent's declaration for that property.
When parent is omitted, user-defined schemas implicitly inherit from https://pagelove.org/Instance. Every user schema participates in the Instance type hierarchy unless it declares a different explicit parent.
Cycles are detected and rejected — a cyclic chain produces a 422 on every write to items of any schema in the cycle. An unknown parent URL is not rejected: the inheritance chain silently stops at the last resolvable ancestor, and the schema behaves as if it had no further parent. This is different from a cycle, which is always an error.
How inherited members combine
| Inherited member | Merge rule |
|---|---|
| Properties | All parent properties are included. Child declarations override parent declarations by name. |
| Group constraints | Parent constraints apply unless the child declares a constraint under the same group name. |
@read resolvers |
Run ancestor-first — parents transform before children. |
@write resolvers |
Run child-first — children transform before parents. |
@validate expressions |
Every validator in the chain runs, ancestor-first. All must return true. |
unique |
If any schema in the chain marks a property as unique, it is unique for the leaf type. |
Schema-level @validate
Use schema-level @validate for rules that span more than one property — "start date must be before end date", "total must equal the sum of line items".
Schema-level @validate accepts either a Sessel expression or a JavaScript module. As with the other binding slots, the language is selected by the wrapper's itemtype URL, not by the <script type> attribute.
Sessel
The expression's self binding is the [itemscope] element being written, so self.microdata() gives read access to every property on the item.
<script type="text/sessel" itemprop="@validate">
let md = self.microdata();
md["start-date"].first() < md["end-date"].first()
</script>
JavaScript
Wrap an ES module in a JavaScript/Module item. The module's default export must be a function, and the schema-level slot binds the instance to this as a serialized element string (it is not passed as a positional argument; the positional context argument is null for this slot). A function form is required so this is observable; an arrow function in a strict-mode ES module does not bind its own this. Return truthy to accept, falsy or thrown to reject.
<div itemprop="@validate" itemscope itemtype="https://pagelove.org/JavaScript/Module">
<script itemprop="source" type="module">
export default function () {
// `this` is the serialized instance HTML.
return this.includes('itemprop="start-date"');
}
</script>
</div>
Note: the schema-level slot's
this-as-serialized-HTML convention differs from a property-level JavaScript@validate, where the property's value is passed as the first positional argument. See JavaScript bindings for the full contract, supported language features, errors, and resource limits.
Schema-level @validate runs only after cardinality, type, and property-level @validate have all passed, and before group constraints are checked. This guarantees per-property structural correctness before the cross-property rule evaluates against it; group constraints (which span properties, the same category of check) run last.
Examples
Minimal
<div itemscope itemtype="https://pagelove.org/Schema">
<meta itemprop="type" content="https://example.com/Tag">
<div itemprop="property" itemscope itemtype="https://pagelove.org/Property">
<meta itemprop="name" content="label">
<meta itemprop="type" content="https://schema.host/Text">
<meta itemprop="cardinality" content="1..1">
<meta itemprop="unique" content="true">
</div>
</div>
With inheritance and a cross-property validator
<div itemscope itemtype="https://pagelove.org/Schema">
<meta itemprop="type" content="https://example.com/Entity">
<div itemprop="property" itemscope itemtype="https://pagelove.org/Property">
<meta itemprop="name" content="identifier">
<meta itemprop="type" content="https://schema.host/Text">
<meta itemprop="cardinality" content="1..1">
<meta itemprop="unique" content="true">
</div>
</div>
<div itemscope itemtype="https://pagelove.org/Schema">
<meta itemprop="type" content="https://example.com/Person">
<meta itemprop="parent" content="https://example.com/Entity">
<div itemprop="property" itemscope itemtype="https://pagelove.org/Property">
<meta itemprop="name" content="email">
<meta itemprop="type" content="https://schema.host/Text">
<meta itemprop="cardinality" content="1..1">
</div>
<script type="text/sessel" itemprop="@validate">
let md = self.microdata();
md["email"].first().matches("^[^@]+@[^@]+\\.[^@]+$")
</script>
</div>
Error cases
| Condition | Result |
|---|---|
Schema with no type |
Silently skipped during parsing. No writes are validated against it. |
Unknown parent URL |
Not an error — the inheritance chain silently stops at the last resolvable ancestor. |
| Circular inheritance chain | 422 on every write to items of any schema in the cycle. |
@validate fails to compile |
422 on every write to items of the governed type. |
@validate returns anything other than true |
422 with check: "@validate" in the violation. |
The 422 response body is HTML with Microdata, using the https://pagelove.org/SchemaViolation itemtype.
See also
- Property — the fields of a single property declaration
- Required combinations — group-level constraints across several properties
- Types — primitive types a property can use
- Resolvers —
@readand@writetransforms