Data Modelling

How to design template schemas, property hierarchies, and relationships in Dual.

Template Property Schema

Every Template defines a properties object that acts as the schema for all Objects minted from it. Properties are stored as a flat JSON object, no nested documents, which keeps indexing fast and queries simple.

Code
// Example: Event Ticket template properties
{
"event_name": "string",
"event_date": "string", // ISO-8601
"seat": "string",
"tier": "string", // "vip" | "general" | "standing"
"redeemed": "boolean"
}

Naming Conventions

Use snake_case for property keys. Prefix domain-specific fields to avoid collisions when templates are shared across organizations:

  • ticket_tier instead of tier
  • wine_vintage instead of year
  • property_address instead of address

Immutable vs. Mutable Properties

By convention, properties set at mint time are treated as immutable (e.g. serial_number, origin_vineyard). Properties that change over the object's lifecycle (e.g. redeemed, current_owner_name) should be updated via Actions so every change is recorded in the audit trail.

Relationships Between Objects

Dual doesn't enforce foreign keys between objects, but you can model relationships with property references:

Code
// Parent → Child: store the parent object ID
{
"parent_object_id": "obj_abc123",
"position_in_set": 3
}
// Collection: store a shared collection identifier
{
"collection_id": "col_wine2024",
"edition": "42/500"
}

Use the Objects API search endpoint to query objects by these reference fields.

Template Variations

When you need the same base schema with minor differences, for example, a "VIP Ticket" vs. "General Ticket", use template variations rather than creating separate templates. Variations inherit the parent template's schema and faces, overriding only the fields that differ.

Code
// TypeScript SDK
const vipVariation = await client.templates.createVariation(
"tmpl_base_ticket",
{
name: "VIP Ticket",
properties: { tier: "vip", lounge_access: true }
}
);

The Public API (Indexer) supports filtering on any top-level property. To keep searches performant:

  • Keep property values short, avoid storing large blobs inline
  • Use the Storage API for images, PDFs, and other binary assets; store the returned URL as a property
  • Add a status or state property if you need to filter by lifecycle stage

Further Reading