Validating uniqueness across two properties
This recipe shows how to enforce uniqueness across a combination of properties, so that individual values can repeat but the same pair cannot appear twice.
When to use this approach
Use composite uniqueness when a single property is not enough to identify a duplicate. A Membership where the same user can belong to many organizations and the same organization can have many users, but a given user-and-organization pair must be unique. A Schedule where the same room can be booked on many dates and the same date can have many rooms, but a given room-and-date pair must not repeat.
Individual vs composite uniqueness
Setting unique to "true" on a property enforces individual uniqueness — no two items of the governed type may share the same value for that property:
<meta itemprop="unique" content="true">
Setting unique to a shared group name instead of "true" creates a composite uniqueness constraint. Properties that share the same group name form a composite key: the combination of their values must be unique, even though each value alone may repeat.
A worked example
A Membership schema needs to prevent the same user from being added to the same organization twice. Neither user-id nor org-id should be individually unique — each value repeats across many memberships. The combination must be unique.
Give both properties the same unique group name:
<div itemscope itemtype="https://pagelove.org/Schema">
<meta itemprop="type" content="https://example.com/Membership">
<ul>
<li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
<meta itemprop="name" content="user-id">
<meta itemprop="type" content="https://schema.host/Text">
<meta itemprop="cardinality" content="1..1">
<meta itemprop="unique" content="membership-pair">
</li>
<li itemprop="property" itemscope itemtype="https://pagelove.org/Property">
<meta itemprop="name" content="org-id">
<meta itemprop="type" content="https://schema.host/Text">
<meta itemprop="cardinality" content="1..1">
<meta itemprop="unique" content="membership-pair">
</li>
</ul>
</div>
With this schema in place:
- A membership with
user-id="alice"andorg-id="acme"succeeds. - A second membership with
user-id="alice"andorg-id="globex"succeeds — same user, different org. - A third membership with
user-id="bob"andorg-id="acme"succeeds — different user, same org. - A fourth membership with
user-id="alice"andorg-id="acme"fails with 422 Unprocessable Entity — the combination already exists.
Uniqueness is enforced atomically with the write. There is no window in which a duplicate can slip through.