Meta Horizon TypeScript API 2.0: JSON PPVs & Versioning
Author: Laex05
Intermediate
Recommended Prerequisite Background Knowledge Some TypeScript experience is recommended as well as access to the desktop editor, and VS Code.
Learn how to create, store, and manage JSON Objects as Player Persistent Variables (PPVs) in Meta Horizon, including versioning for updates and expansions. Given Meta Horizon’s 10kb data limit for PPVs, we’ll show you how to evaluate and optimize your JSON Object’s size.
This knowledge enables the storage of hundreds of variables in a single JSON PPV, facilitating the creation of experiences that remember visitor progress and allow seamless continuation. Additionally, it supports the growth of player data in future updates without the need for new JSON PPVs.
By reading and reviewing this written guide you will be able to:
- Create and store JSON Objects as Player Persistent Variables
- Add versioning to JSON PPVs: allows for updating and adding new variables
- Check the variable’s max size
Step 1: Create a JSON PPV
Once the world is loaded, click the systems drop-down and select “Persistent Variables.”
Click the plus icon to create a variable.
In this example, we will name it “TestVar” making sure to select “{ } Object” from the drop-down.
Now that JSON Object PPV has been created, it can be used in TypeScript scripts by referencing it using the string name it was given: “TestVar.”
To begin, we will create two scripts with the specified names seen below. It’s important to verify that Meta Horizon’s 2.0 API is selected by accessing the settings via the gear icon.

- PlayerVar_Defs
- PlayerVar_Manager
Defs and _Manager are two of several naming conventions I use in my scripts to help organize and plan my code. There are no right or wrong approaches, so feel free to adopt one that works well for you.
_Defs store type declarations, classes, and enums.
_Manager handles logic and typically has a Component that attaches to a single object in the world.
Other naming conventions I use include, _Data, _Entity, and _Func.
As of the current Meta Horizon desktop build (March 2024), creating a new world requires the initial creation of a 1.0 script prior to switching the script mode to 2.0. Once the switch is made, the 1.0 script can be deleted.
Access to this feature is found under the script tab: select the gear icon, followed by Script Settings to view the API Version drop-down:
To get started delete all the prefilled code from the _Defs file, and adjust the _Manager to explicitly import Component, rather than * as “hz” importing all.
Note: This is a personal preference, please choose the approach that works best for you.
I personally like the explicit approach as you will know all items that have been imported from specific APIs, and you can do less typing, which is always nice!
JSON objects are powerful variables that can store various types of data. JSON objects can even nest inside of each other. These may be referred to simply as objects, variable folders, or more informally “bags of stuff.”
This is a simple example storing just two variables for tracking a player’s name and number of visits:
const playerInfo = { name: ‘playerName’, visits: 0 };
In TypeScript it is often necessary to define a type to describe the object, for instance, in this case we would have:
type PlayerInfo = {name: string; visits: number};
We then need to declare the Type when creating the playerInfo variable:
const playerInfo: PlayerInfo = { name: ‘playerName’, visits: 0 };
Notice that the variable is in camelCase and the type is in PascalCase. This differentiation shows that the playerInfo is the object variable storing the data. And, PlayerInfo is the type we are declaring it to be.
Next let’s look at a complex example, with nested objects and multiple variable types. In this example, notice that when the type is larger the variable is broken onto its own line:
type PlayerInfo = {
name: string,
visits: PlayerVisits,
scores: PlayerScores,
};
type PlayerVisits = {
totalVisits: number,
uniqueDays: number,
totalTimeMins: number,
lastVisitTimeSinceEpochMs: number,
};
type PlayerScores = {
fastestTime: number,
highScore: number,
};
const playerInfo: PlayerInfo = {
name: ‘playerName’,
visits: {
totalVisits: 0,
uniqueDays: 0,
totalTimeMins: 0,
lastVisitTimeSinceEpochMs: 0,
},
scores: {
fastestTime: 0,
highScore: 0,
},
};
Next, we will connect the CodeBlockEvents for player enter and exit world to local methods. We have also added a console log to the methods to confirm the script is working.
Ensure that the PlayerVar_Manager script is attached to an object in your world. In this example, I used a text object:
Before we can get and set a JSON Object PPV, we need to define it. Because this type is stored in a _Defs script, we need to be able to access it from our other script files. To do this we add the word export to the front.
Now that we have exported the type, we can import it into any of our other scripts by typing “PlayerVar” and clicking “enter” or “return” on our keyboard to import the type.
Now that our _Defs file is setup, we need a place to store all of our player variable data. We will do this in a _Data script.
Create a new script in Meta Horizon named “PlayerVar_Data,” and then we will delete all of the prefilled code as we did with the _Defs script.
We will only need to write one line of code, and as we do, VS Code will write the import lines seen in the screenshot on lines 1 and 2.
export const allPlayerVarData = new Map<Player, PlayerVar>();
Note: You will need to press enter after typing “Player” and “PlayerVar” for these two types to be imported. If your IDE, in this case VS Code, doesn’t support automatic importing you may need to write lines 1 and 2.
A map is a data type that is similar to a list, but does not use indexes. Instead you can have one of the first type (the key), with the second type “mapped” to that key.
In this example (seen in the image above) we have a player as our key. Because a player cannot be in the world twice, only unique players can exist in our world, it makes for a perfect key. Then the data we are mapping to that player is the PlayerVar type we defined in the _Defs file.
Exporting this map as a constant prevents overwriting it with a new map. Instead, modifications must be made using the map’s set and delete methods, which will be discussed later in this document.
We also export this data, because it is very likely you will need to use it across multiple scripts. Since we will not be setting the PPV until the player leaves the world, this means that the map will store the player’s JSON Object PPV for the duration of their stay. In other words it is the source of truth for the most up-to-date variable values.
Step 7: PlayerVar_Manager
In this step, we will start to write some code. On the next few pages, we will provide in-depth explanations for each line:
At the beginning of the script, you’ll encounter the variables. These variables are globally scoped variables, which means they are accessible from anywhere inside this script. These variables are constants; for instance, the playerVariableVersion should only be updated manually when introducing new variables—a process detailed later in the document (Step 9). Similarly, the playerVarName remains unchanged as it serves as a reference to the PPV variable.
In playerEnterWorld we first get the PPV. Notice that it could be null, we handle this in the initializePlayerVar method (seen at the end of this step). Which we use to create a newPlayerVar. We have to create a newPlayerVar because the player may have last played in an older version with less variables stored in the PlayerVar type.
Then we update the visits to be visits + 1, using the shorthand visits++. To make sure this is working we add a console log.
The last step is to add the player to the map we created in the previous step. We do this by using the .set method, which will replace the previous value if it has already been set. You can use the .has method, if you want to see if the player has already been mapped.
In playerExitWorld we get the playerVar from the map, which if you hover over the const playerVar will show that it is of type PlayerVar or Undefined. This is because it is possible that a player has not yet been mapped. We can check for this with if (playerVar) which checks if it is “true,” meaning not undefined , null , false or sort of false (i.e., 0, empty string). We can use an else statement that calls on console log if it is undefined; this can help locate bugs now and in the future. It is recommended to use checks like this in your code.
Now that we know playerVar is defined, we can set the player’s JSON Object PPV, which saves their JSON Object to the world’s persistent storage.
At the very end, we delete the player from the map.
Outside of our PlayerVar_Manager class, we can create functions. Note that these are similar to methods, but methods are stored inside classes. In this case, we are creating a function called initializePlayerVar, which will return a PlayerVar.
This function is important because it allows us to take a potentially null/undefined value, or partially defined value, and create a new object variable.
Please note: There is an error as of the time of writing in Meta Horizon’s API, which says the variable could be defined or null, but is actually defined or undefined. This distinction caused an error with the code seen at the beginning of this step, below the code is corrected, checking the truthiness of (prevPlayerVar) rather than (prevPlayerVar !== null).
The first thing we do in this function is create a brand new object variable with values we would assign to a first-time visitor. That way if the prevPlayerVar is not truthy we return those values. Otherwise, we can check that the version contains values we want to recall, and save them to the new object.
Now you can compile your newly saved code and test it. If all goes well, every time you preview the world, your number of visits will go up by one, and log to the console!
Step 8: Test PlayerVar Size
Now that we have our JSON Object PPV, we need to make sure we don’t run it over the 10,000 character limit. To test this we can create an artificial maxed variable and stringify it to see how long it is.
In our testPlayerVarSize function, we have created a testPlayerVar variable with larger-than-possible values. We then calculate the length using JSON.stringify, and the .length property of strings. We then simply log that number to the console.
All that is left, is to call this in the start method. When we are done, we can delete this line of code from start, and write it in anytime we need to check the max length.
Step 9: Add More Variables
Start by updating the type to include additional variables. In this case we added isAFK and afkCount:
We then add these to the initialize and test size functions:
We also need to update the version number from 1 to 2:

Then we can use this new version number to get the afkCount for players who have visited our world since the variable was added:
Step 10: Use the Variables
To get started using our new afkCount variable, we will connect the CodeBlockEvents for entering AFK to our local method:
Then we can get the playerVar and check the truthiness, updating the count to be +1. For extra credit try adding the playerExitAFK method and updating the boolean we added to the playerVar. Setting it to true when AFK, and false when they return from AFK.
When a player arrives to your world, I have experienced in build mode this causing the AFK events to fire before the player enter world event does, so you will need to make sure the map has the player before setting the value, or check the truthiness as we did above.
Thank you for following along! This guide aims to unlock your TypeScript super powers, and add awesome new features to your worlds!
If you need additional support feel free to book me (Vidyuu / Laex05) for a 1:1 lesson.
Consider trying the extra credit tasks below to solidify your learning and take it to the next level!
Below we have provided some challenges for you to try implementing on your own. These do require some outside knowledge, and we encourage you to ask questions in Discord if you get stuck or are unsure how to complete these. And as always I’d be happy to help you get unstuck, I’m just a Mentor Session away.
Track player visits and time spent in your world.
Track unique daily visits a player has had to your world.
Build a streak system to track how many days in a row each player has visited your world.
Give the player a reward for visiting your world once per day, and or when they reach time spent thresholds.