Building a Simple Foundry Module
- 9 minutes read - 1814 wordsContinuing our Foundry VTT module development journey. Having set up our documentation system we can now start developing the module itself.
Foundry modules come in many shapes and sizes. At the most basic a module might simple provide images (character artwork, token, icons, maps, etc.) for use in your world. One step up, the module may provide new compendiums containing anything from new characters, scenes, macro, all the way up to complete adventures. Ultimately, your module can extend FoundryVTT adding completely new features or altering the way existing features work.
In this series we are aiming to create one consistent system for developing modules of any type. This approach means that our system may appear over complicated for the simplest modules, which is unfortunate but ultimately having one consistent approach makes it much simpler to maintain a suite of modules.
What is a module?
In FoundryVTT terms a module is an extension to the core FoundryVTT. A module consists of at least one file module.json
held in a directory under Data/modules
. If we create an essentially empty (but valid) module called ‘minimal’ we get a new file Data/modules/minimal/module.json
containing:
{
"id": "minimal",
"title": "minimal",
"version": "1.0.0",
"compatibility": {
"minimum": "12",
"verified": "12"
}
}
The id
field is a machine name for the module. It must not contain spaces and is all in lowercase alphanueric characters (and, by convention, -
replacing spaces). This id
is used as the name of the directory under Data/modules
that contains the module.
The title
is a human readable name for the module as it appears in the FoundryVTT user interface. It can contain spaces and other non-alphanumeric characters.
version
is the current version of the module and must follow the semver convention.
The compatibility
dictionary declares the minimum
version of FoundryVTT with which this module is compatible and the last version of FoundryVTT on which this module was verified
.
Although this module.json
is valid, it is also not very useful if the module is to be made available for others to use (we will build on this base as we build our module).
Other than module.json
our module contains nothing. A common question now is “how should I structure my module?”. The short answer is “however you like”, FoundryVTT does not care about the internal structure of a module (the module.json
provides all the details that FoundryVTT needs to use your module). That said, there is a conventional structure used by most module authors and we will follow this.
Data/modules/
minimal/
module.json
artwork/
lang/
packs/
scripts/
styles/
templates/
artwork/
- All images etc. used in the module.
lang/
- Any user user interface strings should be translatable, this directory contains the required mapping for supported languages.
packs/
- All compendiums.
scripts/
- Any Javascript files.
styles/
- Any cascade style sheets.
templates/
- Any HTML templates.
All this said, any structure is okay. Following common convention simply makes it easier for developers coming to your module for the first time to find things easily.
(Of course we will provide suitable documentation for developers anyway, so the precise structure of our module can vary when needed.)
Development workflow
When developing a module we want to be able to run the module in an isolated development environment in order to avoid introducing complications from other modules. This does not mean we will have no other modules installed though as our module may depend on other modules to work (much more on this later).
We aim to always develop from ‘source’ to final distribution but generally FoundryVTT will always want to run the distribution. Our approach is simple:
- Build our module into a distributable form.
- Run a Foundry instance to serve this ‘as-built’ version of our module.
In our project so far we do not have any module ‘code’ so let’s create some.
cd theatre-of-the-mind
mkdir src
cd src
vi module.json
To this module.json
add the following:
{
"id": "theatre-of-the-mind",
"title": "Theatre of the Mind Manager",
"version": "1.0.0",
"compatibility": {
"minimum": "12",
"verified": "12"
}
}
We have started a src/
directory. This will contain all the source files we will create for our module. Note that doing this separates the docs/
documentation from the src/
, this is because although the docs/
‘belong’, in a sense, with the module they are not delivered with the module when a user adds the module to their FoundryVTT install. Similarly, all the other files in our project (e.g. package.json
) are not part of the module, they support the development process but are not delivered to the end user.
This is our module’s source established. Now we need to ‘build’ the distributable version. At this stage the source is the distributable version, but this will change very quickly so we will set things up anticipating more complex modules content.
We create a new npm
script command add the following to package.json
under "scripts"
:
"build": "mkdir -p _dist && cp -r src/* _dist/",
Running this ‘build’ is as simple as npm run build
.
We now have our _dist
directory. This will be our module distribution.
Hopefully it is obvious that this ‘build’ script is inadequate for anything but the simplest module, but it establishes the idea that npm run build
will create our distribution under _dist/
.
Next we need to run a FoundryVTT instance with our module.
A Digression About Testing
Being able to start a FoundryVTT server and run our new module is cool but not the final goal. What we want is a repeatable way to create a system for testing our module. We are aiming at several different types of testing.
- Unit Testing
- This does not require a running FoundryVTT server. By definition unit tests should isolate the code under test. Unit tests are relevant when we start writing code.
- Integration Testing
- Integration testing does require a FoundryVTT server. We may partially isolate our module under test but generally we are interested in how it performs with the surrounding system.
- End-to-end Testing
- These tests most closely simulate actual use by a user. They tend to be more fragile than other tests because they rely on features in the user interface that may change independent of our code.
Not all modules require all of these types of test.
In the following sections we:
- investigate setting up a consistent FoundryVTT server and adding our module;
- package this into a form that is easy to develop with.
Running a development FoundryVTT with our module
For this we will use docker
and add another npm run
script to make it easy to set up.
Major callout to Mark Feldhousen for his excellent docker image
Before getting into specifics we will set up a new FoundryVTT instance. To do this we need a FoundryVTT license. If you already have a license you use to play then you can reuse this for module development (check the Foundry website for details on reusing a license), otherwise you will need to buy a license. In the process of buying a license you will set up an account on fountryvtt.com, we’ll be using this alongside your license.
Finally, we need somewhere to store the data associated with our development FoundryVTT. This instance will be used solely for developing our module, as such I prefer to keep all its run-time data within my module environment but I don’t want to clutter my directory so we will make the directory to hold the FoundryVTT data invisible.
cd theatre-of-the-mind
mkdir .foundry
docker run \
-e FOUNDRY_USERNAME='<your username>' \
-e FOUNDRY_PASSWORD='<your password>' \
-e FOUNDRY_LICENSE_KEY='<your license key>' \
-e FOUNDRY_GID="$(id -g)" \
-e FOUNDRY_UID="$(id -u)" \
-e CONTAINER_PRESERVE_CONFIG=true \
-p 30000:30000/tcp \
-v "$(pwd)/.foundry:/data" \
-v "$(pwd)/_dist:/data/Data/modules/theatre-of-the-mind" \
--hostname=foundrydev \
felddy/foundryvtt:12
First, go to the root of your module project and create a hidden directory to hold the FoundryVTT data. Then run the FoundryVTT docker container (obviously you need to provide your foundryvtt.com
username, password, and license key—it is best to provide the key explicitly to avoid problems with validation later).
This will take a few seconds the first time you run it as it downloads the docker image, then the docker container downloads the FoundryVTT system and installs your license.
Once ready you can access your development FoundryVTT at http://localhost:30000
. You will the go through the acceptance of the license. (Use ctrl-c
to stop the running FoundryVTT server.)
To add our module we mount the _dist
directory in to the Data/modules
under our .foundry
directory.
We also fix the container’s hostname (with --hostname=foundrydev
). This ensures that when we recreate the container the license verification remains valid (it does not matter what the hostname is, but it does have to remain constant). We will be adding more setup to our running container as we build out more testing.
Progress, but…
We now have a functioning Foundry instance running in isolation with our module in place. There are a few issues that hinder our development though.
- Although the module is in the correct location it is not ‘installed’ because any required dependencies have not been installed. (Not a problem with a new ’empty’ module, but this will become a problem in future.)
- We have no game system installed.
- We have no world installed for testing.
- Our module (and its dependencies) will need to be activated in our world.
- We have nothing but default configuration of packages available at the moment.
- Any updates to our module will not reload into our Foundry instance (we need to restart Foundry ever time we change code).
In addition we may need other things available to test our module.
- We have no players (other than the default Gamemaster).
- We have no characters or tokens.
Looking ahead, when we set up for continuous integration (CI) we may want some additional facilities; off the top of my head…
- Installing other modules that we know to conflict with our module.
- Installing specific versions of packages (to test combinations).
- Installing specific versions of Foundry (a ’test matrix’).
There are also a few common scenarios we can anticipate during development.
We upload an asset (e.g. an image) to our module inside Foundry.
In this situation the image will end up in our ‘as-built’ module (into
.foundry/Data/modules/theatre-of-the-mind/artwork/
for example) but we really want to account for it in our source.We add items to one or more compendium.
Again, this will end up in our ‘as-built’ module (under
.foundry/Data/modules/theatre-of-the-mind/packs/
) when we want it in our source.We modify sources for our scripts.
These are in our source, but we want them to appear ’live’ in our development FoundryVTT (under
src/scripts/
) and any client needs to reload updated scripts.We modify our
module.json
adding, for example, new module dependencies.This could be added in source or in ‘as-built’, either way we need to ensure our development FoundryVTT is updated and that any changes end up in our source so that subsequent builds will include them.
In other words, we have a bit of work ahead.
Choose Your Own Adventure
Follow (P)layer, (GM)Game Master, or (T)echnical thread.