HypercasualGaming SDK
Guide

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

Functions

Every function returns a promise and never throws.

FunctionWhat it doesAnswer 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
ArgumentWhat it must be
kindA text of 1 to 32 lowercase letters, numbers or hyphens.
valueAnything JSON can store: an object, an array, a text, a number, true or false. Up to 12,000 characters once turned into JSON text.
idThe number of an entry, as given by add or list.
fromA whole number, 0 or more.
countA 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).

errorWhat happened
login_requiredThe player isn't signed in. Show whatever fits your game, for example a button that sends them to sign in.
email_not_verifiedThe player hasn't verified their email, and writing public entries needs it.
rate_limitedToo many calls in a short time: the account reached the limit you chose. Wait a moment.
limit_reachedThe account reached the maximum number of entries it can have in your game.
forbiddenOnly the author of that entry can change or remove it (it is private for writing).
blockedYou or an administrator blocked this account from the data of your game.
not_foundThat entry doesn't exist: maybe another player removed it, or it is private for reading.
changedIn set: the entry changed since your game last read it. Read it again and retry.
bad_kindkind isn't a text of 1 to 32 lowercase letters, numbers or hyphens.
bad_valuevalue is missing or JSON can't store it.
too_largevalue is over 12,000 characters.
bad_idid isn't the number of an entry.
bad_fromfrom isn't a whole number, 0 or more.
bad_countcount is missing or isn't a whole number from 1 to 50.
bad_orderorder isn't 'new' or 'old'.
bad_optionspublicWriting and publicReading must be true or false.
bad_origin, bad_keyThe call didn't come from the page of the game, or the game was open for too long: reload it.
no_gameThe game isn't on the website.
rejectedThe server rejected the call.
networkThe 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

  1. Decide what an entry means. One entry per offer: kind 'offer', value { item, qty, price }. Save facts, not conclusions.
  2. Save with add. The entry has to be public for writing so another player can remove it when buying: that is the default.
  3. Read and check. list the newest offers and pass them through filter with the 'market' template.
  4. Buy. remove the offer: whoever removes it first gets ok and the goods; the rest get not_found and refresh.
  5. Handle the errors from the table above, at least login_required and rate_limited.
  6. 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.

RuleValueIt ignores…
minSecondsBetweena numberan entry written less than that many seconds after the previous accepted one of the same player
maxPerMinute, maxPerHour, maxPerDaya numberthe entries of a player beyond that many in a minute, an hour or a day
maxPerPlayera numberthe entries of a player beyond the first ones
onePerPlayer'first' or 'last'all but the first (or the last) entry of each player
maxAgeSecondsa numberentries 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
ignoreEditedByOtherstrueentries 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:

TemplateRules it containsFor
'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);

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 functionWhat 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.

What happens to the data