Register

Introduction to System Data Models

The TypeDataModel class is a powerful tool for defining data schema, granting system developers access to the powerful and flexible Data Model functionality that is used throughout Foundry Virtual Tabletop. it is provided as foundry.abstract.TypeDataModel and full API documentation is available here TypeDataModel.

This article focuses on how to utilize Type Data Models for a Game System in Foundry Virtual Tabletop. For more information on Data Models themselves, please refer to the following resources:

Note: Modules have the ability to define data sub-types. For more information about module-level data sub types, please see the module sub-type article.

Defining a Type Data Model

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

Type Data Model

class CharacterData extends foundry.abstract.TypeDataModel {
  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())
      })
    };
  }
}

With the full schema of your TypeDataModel defined, it becomes unnecessary to define the schema using the template.json file. The only fields necessary in that file are to confirm the document types that your system uses, and which fields require additional server-side sanitization as htmlFields, for example:

template.json

{
  "Actor": {
    "types": ["character"],
    "htmlFields": ["biography"],
    "character": {}
  }
}

Registering a Type Data Model

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

The following code snippet will register the example CharacterData model to be automatically applied as the system data for every Actor of the character type.

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

Migrations

Migrations and data coercion can be performed before your Type 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.TypeDataModel {
  static defineSchema() {
    // Omitted for brevity.
  }

  /**
   * Migrate source data from some prior format into a new specification.
   * The source parameter is either original data retrieved from disk or provided by an update operation.
   * @inheritDoc
   */
  static migrateData(source) {
    if ( "weapons" in source ) {
      source.weapons = source.weapons.map(weapon => {
        return weapon === "bmr" ? "boomerang" : weapon;
      });
    }
    return super.migrateData(source);
  }
}

Enhancing a Type Data Model

Type Data Models can have methods added to them that encapsulate logic relevant 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 Type Data Model's instance methods via this.parent, allowing for more complex interactions and logic.

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

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

  /**
   * Determine whether the character is dead.
   * @type {boolean}
   */
  get dead() {
    const invulnerable = CONFIG.specialStatusEffects.INVULNERABLE;
    if ( this.parent.effects.some(e => e.statuses.has("invulnerable") ) return false;
    return this.health.value <= this.health.min;
  }
}

The defined dead property could then be accessed on any Actor document of the character type as follows:

// Determine if a character is dead.
game.actors.getName("Character").system.dead;

Token Resources

When using the System template.json, the properties that can be used for a Token's resource bar are inferred from the template. This works well enough, but it can also include things like derived properties, properties that were intended to be hidden, or otherwise properties that are not suitable or ever useful as a resource bar, making it difficult for a user to locate the actual properties they want.

When using a Type Data Model for your system data, the core software will no longer attempt to infer which properties can be used as Token resource bars. Instead, you are given full control to tailor this list to whatever makes sense for your System. To do so, you need to modify the CONFIG.Actor.trackableAttributes configuration variable. The below example shows how to configure one resource as a bar attribute, and another as a value attribute.

Hooks.on("init", () => {
  CONFIG.Actor.trackableAttributes = {
    character: {
      bar: ["attributes.hp"],
      value: ["attributes.ac.value"]
    }
  };
});

For bar attributes, the property supplied must point to some object with both value and max properties, and these properties must both be numbers. For value attributes, the property supplied must simply point to any number. The attributes do not need to exist in your Type Data Model, they can be properties that are later derived as part of data preparation. If the attribute does not exist in the Type Data Model or is not a NumericField, then it will not be editable in the Token HUD.

In line with this feature, the signature of TokenDocument.getTrackedAttributes has changed. It will now accept a string value of the type of Actor that the resources should be retrieved for. If the argument is not supplied, and tracked attributes are configured via trackableAttributes, then getTrackedAttributes will return the union of all resources across all configured Actor types. This is useful in cases where the Actor type is not known, such as default token settings.

If your System makes use of Data Models, but you would rather have the core software infer the tracked attributes from your schema, you may opt to not configure any trackableAttributes. Bear in mind that the core API does not know the semantics of any custom DataFields you may be making use of in your Type Data Model, and so will not be able to inspect it for potential trackable attributes. Additionally, it will be unable to include any properties derived during data preparation, as they will not have corresponding fields in your schema. If you want to tailor the list of trackable attributes in those cases, you must override TokenDocument.getTrackedAttributes yourself. In the majority of cases, we expect that using the trackableAttributes configuration should be a lot simpler.