Register

Introduction to System Data Models

The Data Model changes made to Documents in version 10 that are described here are not solely for use with the core Documents defined by Foundry VTT, but also empower system developers to define such Data Models in order to represent data specific to their systems.

Data Models defined in this way receive many of the same benefits as their core API counterparts, including validation on initialization and update, and on-the-fly data migration and coercion. It also allows functionality to be delegated out to these models, reducing the burden on the system's Document implementation to solely house such logic.

System Data Models

This section focuses on how to define and implement Data Models for a Game System in Foundry Virtual Tabletop. For more detailed information on Data Models themselves, please see this article and view the documentation here.

Defining a Data Model

Data Models themselves are lightweight classes that can be defined very simply. Here we will take an example system template and construct a corresponding Data Model for it.

Data Model

class CharacterData extends foundry.abstract.DataModel {
  static _enableV10Validation = true;
  static defineSchema() {
    const fields = foundry.data.fields;
    return {
      biography: new fields.HTMLField(),
      health: new fields.SchemaField({
        value: new fields.NumberField({
          required: true,
          initial: 10,
          integer: true
        }),
        min: new fields.NumberField({
          required: true,
          initial: 0,
          integer: true
        }),
        max: new fields.NumberField({
          required: true,
          initial: 10,
          integer: true
        })
      }),
      proficiencies: new fields.SchemaField({
        weapons: new fields.ArrayField(new fields.StringField()),
        skills: new fields.ArrayField(new fields.StringField())
      })
    };
  }
}

template.json

{
  "Actor": {
    "types": ["character"],
    "htmlFields": ["biography"],
    "character": {
      "biography": "",
      "health": {
        "value": 10,
        "min": 0,
        "max": 10
      },
      "proficiencies": {
        "weapons": [],
        "skills": []
      }
    }
  }
}

Registering a Data Model

Once the Data Model is defined, the core API can be made aware of it and will automatically apply the data model to the system field of any registered types.

The following code snippet will register the example CharacterData model with all character-type Actors.

Hooks.on("init", () => {
  CONFIG.Actor.systemDataModels.character = CharacterData;
});

Migrations

Migrations and data coercion can be performed before your Data Model is instantiated, allowing legacy data to be converted into a format that the current version of your system understands. Migrations are run when the source data is first retrieved from disk, as well as run on any update deltas before they are applied to the Document.

class CharacterData extends foundry.abstract.DataModel {
  static defineSchema() {
    // Omitted for brevity.
  }

  static migrateData(source) {
    // Here, source will either be the raw data retrieved from disk,
    // or some update delta.
    if ( "weapons" in source ) {
      source.weapons = source.weapons.map(weapon => {
        if ( weapon === "bmr" ) return "boomerang";
        return weapon;
      });
    }
    return super.migrateData(source);
  }
}

Enhancing a Data Model

Data Models can have methods added to them that encapsulate logic relevent to the particular system-specific type of Document that they represent. This allows you to move logic out of the Document implementation and house it in a location that is much more specific to its functionality.

The parent Document instance is accessible from within the Data Model's instance methods via this.parent, allowing for more complex interactions and logic.

class CharacterData extends foundry.abstract.DataModel {
  static defineSchema() {
    // Omitted for brevity.
  }

  static migrateData() {
    // Omitted for brevity.
  }

  get dead() {
    const invulnerable = CONFIG.specialStatusEffects.INVULNERABLE;
    if ( this.parent.effects.some(e => e.getFlag("core", "statusId") === invulnerable) ) return false;
    return this.health.value <= this.health.min;
  }
}
// Determine if a character is dead.
game.actors.getName("Character").system.dead;