Schema definitions in HTML

A schema definition is an HTML element marked up with Microdata that pagelove.mjs reads on load to learn the shape of a record type.

When to reach for it

Reach for a schema definition whenever a new kind of record needs to exist on the page — a Note, a Post, a Comment — and pagelove.mjs must know how to create, render, and bind instances of it client-side.

Anatomy of a schema definition

A schema definition is a <div> (or any element) carrying itemscope and itemtype="https://pagelove.org/Schema". #discoverSchemas() walks every such element on the page.

Child Required Purpose
<meta itemprop="type" content="…"> yes The URL that names this schema. Instances reference it via itemtype.
<meta itemprop="parent" content="…"> no URL of another schema to inherit properties from.
[itemprop="property"] children one or more Each declares a single property. See below.

A schema with no type child is skipped silently.

Property definitions

Each property is an inner itemscope of type https://pagelove.org/Property. A <li> is conventional, but any element works. Its children declare the four fields:

Field Required Accepts
<meta itemprop="name" content="…"> yes The property name. Properties without a name are dropped.
<meta itemprop="type" content="…"> optional A type URL such as https://pagelove.org/Text, Integer, DateTime, or a nested schema URL. Defaults to the empty string if absent.
<meta itemprop="cardinality" content="…"> optional 0..1, 1..1, 0..n, 1..n. Defaults to 0..1 if absent.
itemprop="default" child optional Either a <meta> with a content string, or an embedded client-side JavaScript/Module (see below).

An additional itemprop="@read" child may carry an embedded client-side JavaScript/Module that transforms the value on read.

Inheritance

A schema may declare <meta itemprop="parent" content="…"> pointing at another schema's type URL. #discoverSchemas() resolves inheritance in a second pass:

Only direct property-name collisions override; unrelated parent properties remain.

Static defaults

Place <meta itemprop="default" content="…"> directly inside a property's itemscope. The value is always a string and is used as-is when a new instance is created.

Client-side dynamic defaults

For defaults that must be computed, embed a JavaScript/Module inside the property:

<li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
  <meta itemprop="name" content="createdAt">
  <meta itemprop="type" content="https://pagelove.org/DateTime">
  <div itemprop="default" itemscope itemtype="https://pagelove.org/JavaScript/Module">
    <script itemprop="source" type="module">
      export default () => new Date().toISOString();
    </script>
  </div>
</li>

When pagelove.mjs loads the schema on a page, its loadModule helper finds the wrapper, reads the child <script itemprop="source">, wraps the script text in a Blob, creates a Blob URL with URL.createObjectURL, imports that URL as an ES module, and assigns the module's default export as the property's default. This happens in the browser, on page load, not on the server. The module is invoked whenever pagelove.mjs needs a default value client-side — typically when a new instance is about to be created before the POST.

The @read transform

Same embedding pattern, but the wrapper's itemprop is @read instead of default:

<div itemprop="@read" itemscope itemtype="https://pagelove.org/JavaScript/Module">
  <script itemprop="source" type="module">
    export default (value) => value.toUpperCase();
  </script>
</div>

pagelove.mjs loads this module the same way — Blob URL, dynamic import, default export — and calls it to transform the property value when a client-side consumer reads the property. Like dynamic defaults, @read runs in the browser.

Examples

A schema with static defaults

<div itemscope itemtype="https://pagelove.org/Schema">
  <meta itemprop="type" content="https://schema.host/Note">
  <ul>
    <li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
      <meta itemprop="name" content="title">
      <meta itemprop="type" content="https://pagelove.org/Text">
      <meta itemprop="cardinality" content="1..1">
      <meta itemprop="default" content="Untitled">
    </li>
    <li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
      <meta itemprop="name" content="body">
      <meta itemprop="type" content="https://pagelove.org/Text">
    </li>
    <li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
      <meta itemprop="name" content="color">
      <meta itemprop="type" content="https://pagelove.org/Text">
      <meta itemprop="default" content="#ffd166">
    </li>
  </ul>
</div>

A schema that inherits from a parent

<div itemscope itemtype="https://pagelove.org/Schema">
  <meta itemprop="type" content="https://schema.host/Content">
  <ul>
    <li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
      <meta itemprop="name" content="title">
      <meta itemprop="type" content="https://pagelove.org/Text">
      <meta itemprop="cardinality" content="1..1">
    </li>
    <li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
      <meta itemprop="name" content="body">
      <meta itemprop="type" content="https://pagelove.org/Text">
    </li>
  </ul>
</div>

<div itemscope itemtype="https://pagelove.org/Schema">
  <meta itemprop="type" content="https://schema.host/Post">
  <meta itemprop="parent" content="https://schema.host/Content">
  <ul>
    <li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
      <meta itemprop="name" content="publishedAt">
      <meta itemprop="type" content="https://pagelove.org/DateTime">
    </li>
  </ul>
</div>

Post inherits title and body from Content and adds publishedAt. A title property declared on Post would override the parent's.

A schema with a client-side dynamic default

<div itemscope itemtype="https://pagelove.org/Schema">
  <meta itemprop="type" content="https://schema.host/Event">
  <ul>
    <li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
      <meta itemprop="name" content="label">
      <meta itemprop="type" content="https://pagelove.org/Text">
    </li>
    <li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
      <meta itemprop="name" content="createdAt">
      <meta itemprop="type" content="https://pagelove.org/DateTime">
      <div itemprop="default" itemscope itemtype="https://pagelove.org/JavaScript/Module">
        <script itemprop="source" type="module">
          export default () => new Date().toISOString();
        </script>
      </div>
    </li>
  </ul>
</div>

When pagelove.mjs builds a new Event in the browser, it calls the module's default export and stamps createdAt with the current time in the user's browser before the instance is posted.

Not supported client-side

pagelove.mjs's schema discovery only reads name, type, cardinality, a static or dynamic default, and a @read transform from each property — it does not implement computed properties (@computed), methods (itemtype="https://pagelove.org/Method"), or schema-level @validate. Declaring any of these in a schema the client library discovers is inert: the extra markup is silently ignored.

All three are real, server-side features — see JavaScript in schemas for computed properties, methods, and schema-level @validate as core evaluates them.

See also