Data system
A space on the website that your game shares between all its players: what one player's game saves, the other players' games can read, change or remove. It's what you need for a market between players, levels made by players, a world boss, a ranking… Read the warnings first.
Read this before you use it
- It is not cheat-proof. Anyone can open the browser's tools and call the functions with any value. The website limits and records what happens, but it cannot know what a value means in your game. Don't rely on it alone for competitions or for anything that has money value.
- Check what you read. Ignore the entries that break your game's rules. Save facts (“the player won match 12 at this time”) instead of results (“score: 99999”) whenever you can, and use
HGSDK.data.filter(below) to drop the rest. - Set the limits of your game. Each account has a maximum of writes, reads and entries in your game. The starting values are generous: choose your own in the Data tab of your game (see “Limits and your Data tab”).
- Don't save personal data. Every entry carries the account that wrote it, and public entries are shown to other players. Never put emails, real names, addresses or anything sensitive inside an entry.
- The website shows no notices. If the player isn't signed in, hasn't verified their email or has hit a limit, the call fails with an error code and your game decides what to show (see “Errors”).
How it works
- Each thing you save is an entry: it has a
kind(a label you choose:'offer','score','map'…) and a value (anything JSON can store). The entries belong to your game, not to the players: the same entry is seen by every player of the game. - The player has to be signed in for every call. A guest gets the error
login_required. - Each entry is public or private for writing and public or private for reading. Both are public unless you say otherwise when you create it, and they can't be changed afterwards:
- Public reading: any signed-in player can read it. Private reading: only its author.
- Public writing: any signed-in player whose email is verified can change or remove it. Private writing: only its author.
- The website stamps each entry itself, the game can't fake it: the author (
by), the account that changed it last (editedBy), the dates (createdAt,updatedAt) and whether it is yours (mine). - Every change is kept in a history, so a mistake or an abuse can be undone from your Data tab.
- It only works in a game that is on the website. Anywhere else (your own computer, the preview of a submission), every function answers
no_game.
Functions
Every function returns a promise and never throws.
| Function | What it does | Answer when it goes well |
|---|---|---|
HGSDK.data.add(kind, value, options) | Saves a new entry. options is optional: { publicWriting: true, publicReading: true }. Both are true by default; use false to make the entry private for writing or for reading. | { ok: true, id } |
HGSDK.data.get(id) | Reads one entry. | { ok: true, entry } |
HGSDK.data.list(kind, from, count, order) | Reads count entries of a kind, starting at position from (0 is the first one). order is optional: 'new' (newest first, the default) or 'old' (oldest first). | { ok: true, entries } |
HGSDK.data.set(id, value) | Replaces the value of an entry. | { ok: true } |
HGSDK.data.remove(id) | Removes an entry. Only the first one to remove it gets ok. | { ok: true } |
HGSDK.data.filter(entries, rules) | Keeps the entries that follow your rules (see “Check what you read”). | An array |
| Argument | What it must be |
|---|---|
kind | A text of 1 to 32 lowercase letters, numbers or hyphens. |
value | Anything JSON can store: an object, an array, a text, a number, true or false. Up to 12,000 characters once turned into JSON text. |
id | The number of an entry, as given by add or list. |
from | A whole number, 0 or more. |
count | A whole number from 1 to 50. |
An entry looks like this:
{
id: 4821, kind: 'offer', data: { item: 'strawberry', qty: 12, price: 30 },
by: 'ana', editedBy: null, mine: false,
createdAt: '2026-10-15T10:21:07Z', updatedAt: '2026-10-15T10:21:07Z',
publicWriting: true, publicReading: true
}
data is the value that was saved. by is null when the account that wrote it was deleted afterwards (see “What happens to the data”).
Errors
When something goes wrong the answer is { ok: false, error, message }: error is a short code you can compare, and message a sentence in English that explains it (also written in the browser console when the mistake is yours).
error | What happened |
|---|---|
login_required | The player isn't signed in. Show whatever fits your game, for example a button that sends them to sign in. |
email_not_verified | The player hasn't verified their email, and writing public entries needs it. |
rate_limited | Too many calls in a short time: the account reached the limit you chose. Wait a moment. |
limit_reached | The account reached the maximum number of entries it can have in your game. |
forbidden | Only the author of that entry can change or remove it (it is private for writing). |
blocked | You or an administrator blocked this account from the data of your game. |
not_found | That entry doesn't exist: maybe another player removed it, or it is private for reading. |
changed | In set: the entry changed since your game last read it. Read it again and retry. |
bad_kind | kind isn't a text of 1 to 32 lowercase letters, numbers or hyphens. |
bad_value | value is missing or JSON can't store it. |
too_large | value is over 12,000 characters. |
bad_id | id isn't the number of an entry. |
bad_from | from isn't a whole number, 0 or more. |
bad_count | count is missing or isn't a whole number from 1 to 50. |
bad_order | order isn't 'new' or 'old'. |
bad_options | publicWriting and publicReading must be true or false. |
bad_origin, bad_key | The call didn't come from the page of the game, or the game was open for too long: reload it. |
no_game | The game isn't on the website. |
rejected | The server rejected the call. |
network | The server couldn't be reached. |
const result = await HGSDK.data.add('offer', { item: 'strawberry', qty: 12, price: 30 });
if (!result.ok) {
if (result.error === 'login_required') showSignInButton();
else if (result.error === 'email_not_verified') showMessage('Verify your email to sell');
else if (result.error === 'rate_limited') showMessage('Slow down a little');
else showMessage('Something went wrong');
}
Recipe: a market between players
- Decide what an entry means. One entry per offer:
kind'offer', value{ item, qty, price }. Save facts, not conclusions. - Save with
add. The entry has to be public for writing so another player can remove it when buying: that is the default. - Read and check.
listthe newest offers and pass them throughfilterwith the'market'template. - Buy.
removethe offer: whoever removes it first getsokand the goods; the rest getnot_foundand refresh. - Handle the errors from the table above, at least
login_requiredandrate_limited. - Choose your limits in the Data tab and test with two accounts (two browsers) before you publish.
// sell
const sold = await HGSDK.data.add('offer', { item: 'strawberry', qty: 12, price: 30 });
if (!sold.ok) handleError(sold);
// show the market: the 50 newest offers that follow the rules
const read = await HGSDK.data.list('offer', 0, 50);
if (read.ok) HGSDK.data.filter(read.entries, 'market').forEach(drawOffer);
// buy: whoever removes the offer first gets it
const bought = await HGSDK.data.remove(offer.id);
if (bought.ok) giveItems(offer.data);
else refreshMarket();
Check what you read
HGSDK.data.filter(entries, rules) returns an array with the entries that follow the rules, in the order they were written. The entries that break a rule are not thrown away: they are listed in result.marked as { entry, rule }, in case you want to show or log them. Nobody is punished: you decide what to do. rules is an object with the rules you want, or the name of a template.
| Rule | Value | It ignores… |
|---|---|---|
minSecondsBetween | a number | an entry written less than that many seconds after the previous accepted one of the same player |
maxPerMinute, maxPerHour, maxPerDay | a number | the entries of a player beyond that many in a minute, an hour or a day |
maxPerPlayer | a number | the entries of a player beyond the first ones |
onePerPlayer | 'first' or 'last' | all but the first (or the last) entry of each player |
maxAgeSeconds | a number | entries older than that |
between | ['2026-01-01T00:00:00Z', '2026-01-31T23:59:59Z'] | entries written outside that period |
valueRange | { field: 'points', min: 0, max: 5000 } | entries whose data.points isn't a number inside that range |
requireFields | ['item', 'price'] | entries whose data lacks any of those fields |
ignoreEditedByOthers | true | entries whose last change was made by an account other than their author |
The templates are ready-made sets of rules. Use them by name, or copy their values and change the ones you want:
| Template | Rules it contains | For |
|---|---|---|
'counter' | { minSecondsBetween: 1, maxPerMinute: 60 } | Counters and votes |
'ranking' | { onePerPlayer: 'last' } | Rankings: one entry per player |
'market' | { ignoreEditedByOthers: true, requireFields: ['item', 'price'] } | Offers between players |
'chat' | { minSecondsBetween: 2, maxPerMinute: 20 } | Messages |
// a template by name
const clean = HGSDK.data.filter(entries, 'ranking');
// your own rules (the values of 'chat', made stricter)
const calm = HGSDK.data.filter(entries, { minSecondsBetween: 5, maxPerMinute: 6 });
// what was ignored, and why
calm.marked.forEach((m) => console.log(m.entry.id, m.rule));
filter works with the entries you give it, so give it enough of them (up to 50 per list call) for the rules that count per player to make sense.
More examples
// a ranking where each player has one entry that only they can change
const mine = { name: playerName, points: 340 };
await HGSDK.data.add('score', mine, { publicWriting: false });
const board = await HGSDK.data.list('score', 0, 50);
if (board.ok) {
HGSDK.data.filter(board.entries, 'ranking')
.sort((a, b) => b.data.points - a.data.points)
.forEach(drawScore);
}
// a save that follows the account to any device: private for writing and for reading
await HGSDK.data.add('save', gameState, { publicWriting: false, publicReading: false });
// a counter shared by everybody: read it, change it, save it
const read = await HGSDK.data.get(counterId);
if (read.ok) {
read.entry.data.hits += 1;
const saved = await HGSDK.data.set(counterId, read.entry.data);
if (saved.error === 'changed') tryAgain(); // another player changed it at the same time
}
// a chat: the 20 newest messages that follow the rules
const chat = await HGSDK.data.list('message', 0, 20);
if (chat.ok) HGSDK.data.filter(chat.entries, 'chat').forEach(drawMessage);
- Two players at the same time.
setonly changes an entry if it is still the way your game last saw it (inadd,get,listor its ownset); otherwise it answerschanged.removeanswersokonly to the first one. - Position in
list. If entries are added or removed while players page through a list, a few can repeat or be skipped. For rankings and markets that is fine.
GameMaker (GML)
With the HGSDK extension (version 1.1 or later) the same functions exist in GML. Each call returns a request number, and the answer arrives at the Asynchronous → Social event, in async_load, with type equal to "hg_data". Values travel as JSON text: json_stringify to send them and json_parse to read them.
| GML function | What it does |
|---|---|
hg_data_add(kind, json, flags) | Saves a new entry. flags: 0 public for writing and reading, 1 private for writing, 2 private for reading, 3 private for both. |
hg_data_get(id) | Reads one entry. |
hg_data_list(kind, from, count, order) | Reads entries of a kind. order is "new" or "old". |
hg_data_set(id, json) | Replaces the value of an entry. |
hg_data_remove(id) | Removes an entry. |
hg_data_filter(entries, rules) | Answers at once (no event) the JSON text of the entries that follow the rules. entries is the "entries" text of a list answer; rules is the name of a template or a JSON text such as {"minSecondsBetween":5}. |
The map that arrives at the event has these keys: type, call ("add", "get", "list", "set" or "remove"), request, ok (1 or 0), error (the code of the Errors table, empty when everything went well), message, id (in add), entry (in get, JSON text) and entries (in list, JSON text). The extra error no_sdk means the SDK script is not loaded.
global.request_sell = hg_data_add("offer", json_stringify({ item: "strawberry", qty: 12, price: 30 }), 0);
global.request_market = hg_data_list("offer", 0, 50, "new");
global.request_buy = hg_data_remove(offer_id);
In the Asynchronous → Social event:
if (async_load[? "type"] != "hg_data") exit;
var request = async_load[? "request"];
var ok = async_load[? "ok"];
if (ok == 0)
{
var error = async_load[? "error"];
if (error == "login_required") show_sign_in_button();
exit;
}
if (request == global.request_market)
{
var clean = json_parse(hg_data_filter(async_load[? "entries"], "market"));
for (var i = 0; i < array_length(clean); i++)
{
var offer = clean[i];
draw_offer(offer.id, offer.data.item, offer.data.price);
}
}
Limits and your Data tab
Open your game from My games: its Data tab is the control panel of the data system. Only you, the administrators and the founder see it.
- Limits per account. You choose how many writes (add, change, remove), reads and removals of other players' entries each account can make in your game, with a number and a unit of time (per second, minute, hour or day), and how many entries each account can have. The website sets a maximum for each one, shown next to the field. At first: 120 writes per minute, 600 reads per minute, 300 removals per day and 1,000 entries per account. A limit that is too low makes honest players fail; one that is too high lets a bot do more damage: the choice is yours.
- Alerts. The website marks accounts that do unusual things in your game (for example, too many writes) and lists them with a level from 1 to 5 and an explanation. An alert only marks: nothing is blocked by itself. For each one you can mark it as a false positive (similar alerts will then start lower), block the account from the data of your game for some hours or for ever, or undo what it did.
- History. Every change is kept with the old value, the date, the account and its IP for a limited time (90 days at first). You can search it and put an entry back as it was, or undo everything an account did in a period of time.
What happens to the data
- When a player deletes their account, the entries that are private for writing and for reading are deleted. The rest stay with their content intact but without any link to the account (
bybecomesnull), so the other players' games keep working. You and the administrators can remove those entries from the Data tab. - Because of that, anything personal inside the value of a public entry is your responsibility: don't put it there.
- The rules about accounts and personal data are in the terms of use and the privacy policy.
HypercasualGaming