This is known to be up to date as of Foundry v12
NOTE: Unless you specifically want to add always-on code to one world only, you should instead add your scripts to a local module. There is a full guide for this here.
World scripts, like modules and macros, are a way for users to define additional functionality for FoundryVTT.
World scripts and modules are very similar. Both allow for 3rd party javascript files to be loaded and executed by Foundry. Because of their similarities, learning resources designed for learning to make modules are often applicable to world scripts.
The primary difference between world scripts and modules is that modules are designed to be easy to package and distribute to other users. This makes it easy to share complex functionality and collections of related other assets such as html templates, css styles, images, compendia, and so on. However, this ability also requires some additional steps by the module author to create a module manifest, set up a build pipeline (if applicable), and handle hosting of the package.
World scripts, on the other hand, are not as portable and simple to distribute, but require less setup by the author. Thus, they are more suited (as the name implies) to world-specific functionality and configuration, or for tasks that are not worth the initial setup time of a module.
One major downside as a result of this difference is that if a user distributes a world script for others to use, and that world script has a bug, or conflicts with a core or system update, there is little recourse available to distribute the fix, and thus users will need to manually update or fix the issue.
Another difference between world scripts and modules is that modules can be enabled and disabled through the Foundry module management UI, while world scripts are always active.
World scripts and macros fill quite different niches in terms of 3rd party code. Their main similarity is that both are relatively easy to set up when compared to a module.
Macros take the form of entities within a Foundry world or compendium. They are written in the built-in Foundry macro editor. Since macros are entities, they get the concept of user permissions for "free", they can be manipulated programmatically like other Foundry entities, and can be stored in compendia.
World scripts, on the other hand, are javscript files which are written and edited in an external editor like Visual Studio Code. They are statically served to clients and thus can't be mutated in the same ways as macros.
World scripts are loaded and executed as soon as the Foundry page loads, while Macros are latent until explicitly executed by a user (in core Foundry, at least). This is a massive difference that makes some very important concepts feasible in world scripts that are not feasible with macros, such as usage of hooks and sockets.
This also adds an additional consideration that developers must take into account compared to authoring macros: world scripts are executed on every client. Developers must be careful to ensure that updates are only called from one client to avoid doing duplicate work, and avoid permissions issues from trying to update entities that the client does not have access to update.
To summarize the difference between world scripts, modules, and macros:
In order to use a world script you must: (a) have a Javascript file you want to add to your world, and (b) edit the world's manifest to point to your Javscript code.
Your Javscript file will usually live within the world directory for the world in which it is used, but in reality, it could live anywhere in your Foundry userdata folder (see Where Is My Data Stored? on the Foundry KB for more information). This guide assumes that your Javscript is in the root of the world directory (i.e. Data/worlds/my-world/my-script.js
).
To include a JavaScript file in your world:
Data/worlds/my-world/
).my-script.js
. All of your code will (eventually) go in this file.world.json
file in the text editor. No JavaScript code will go in this file, but we need to tell it where to find your script file.world.json
file, look for a line with the esmodules
key. If there isn't one already, insert it after the id
like this:{
"title": "My World",
"id": "my-world",
"esmodules": ["my-script.js"],
"system": "crucible",
...
"esmodules"
line — the other lines in the example above should already be there, and should not be added again..js
extension on your file; it should look like this: "esmodules": ["my-script.js"],
(note the comma is also required at the end of the line, unless you've added this as the very last key in the JSON file).world.json
.world.json
file. If your world doesn't show up in Setup, you have a syntax error in your world.json
file — double-check that you aren't missing any punctuation, to start. The Warnings screen in Setup might help you find what's wrong, too.As mentioned above, world scripts are great for initial configuration tasks that would be a bit too simple for a whole module. This isn't intended to be a tutorial, but rather a set of examples of tasks that are well suited to world scripts.
// This example greets players with an on-screen notification once Foundry
// has finished its initial loading
Hooks.on("ready", () => ui.notifications.notify("Welcome to the World!"));
// The default Foundry cone angle is 53.13 degrees.
// This world script will set the default angle for this world to 90 degrees.
Hooks.on("setup", () => CONFIG.MeasuredTemplate.defaults.angle = 90);
// This world script will add a new status effect icon (a blue circle)
// that can be applied to tokens
Hooks.on("setup", () => {
CONFIG.statusEffects.push({ id: "bluecircle", label: "Blue Circle", icon: "path/to/blue-circle.png" })
});
// This script will change the default token configuration for newly created or imported actors
// (does not affect actors already in the world).
// It changes the following:
// - Show the token's name when an owner of the token hovers over it
// - Always show all resource bars for owners of the token
// - Sets the first bar to display the token's hit points (dnd5e)
Hooks.on("preCreateActor", (actorData, options, userId) => {
actorData.token = actorData.token ?? {};
mergeObject(actorData.token, {
displayName: CONST.TOKEN_DISPLAY_MODES.OWNER_HOVER,
displayBars: CONST.TOKEN_DISPLAY_MODES.OWNER,
bar1: { attribute: "attributes.hp" },
bar2: { attribute: null },
});
});
/// assumes you have an "audio" folder in your Data folder
Hooks.once("init", function(){
CONFIG.Wall.doorSounds.myCustom = {
label: "My Custom Sound",
open: "./audio/customOpenSound.ogg",
close: "./audio/customCloseSound.ogg",
lock: "./audio/customLockSound.ogg",
unlock: "./audio/customUnlockSound.ogg",
test: "./audio/customTestSound.ogg",
}
});
NOTE:
The dnd5e specific scripts that were previously listed here have been moved to the dnd5e repository's wiki