Ask fans for consent

The consent banner every game shows, the object it stores, and how an app that embeds a game passes its own answer in.

Every Gamestage game asks a fan before anything records them. The Gamestage client draws the banner for you, stores the answer, and holds every analytics and storage call until the fan says yes. gamestage deploy refuses a game that would record fans without asking.

Quickstart

Name who is asking and link your privacy policy, in gamestage.settings.json beside gamestage.yaml. A deploy writes both into Studio:

{
  "consent_brand_name": "Arsenal",
  "privacy_policy_url": "https://www.arsenal.com/privacy-policy"
}

Then load the client and start the game. The banner appears on its own the first time a fan opens the game:

const game = await gamestage.start({
  consentBanner: { settingsIn: document.querySelector("footer") },
});

settingsIn puts a "Privacy settings" button in your footer, so a fan can change their mind later. Leave it out and the banner pins a small "Privacy settings" pill in the bottom corner of the screen, outside the page's layout, so a game sized to fit the screen does not gain a scrollbar. Pass null only when your own control calls game.consent.openSettings().

Check it the way a new fan would meet it:

npx gamestage@latest verify

The consent line passes when a consent control appears before anything is sent, and nothing is sent after the fan says no. The consent-owner line passes when the brand name and the privacy policy link are both set.

What a fan sees

A compact card floating near the foot of the screen, clear of the game's own pill nav and pinned main button: two lines naming who is asking and why, an underlined Privacy policy link, Accept (filled in the game's accent) and Reject (outlined) at the same size side by side, a small Manage preferences link for choosing one category at a time, and a × in the corner. It takes the game's colours and font from its Studio palette when they are readable together. The game stays playable underneath it.

× closes the card without answering. Nothing is recorded, nothing is stored, and the card comes back on the next visit.

The fan is asked once per game, on each device, until the producer changes the privacy policy version in Studio.

CategoryWhat it allowsOn by default
necessaryPlaying the game: a fan's session and their round in progress.Always on
analyticsMonterosa Analytics, and PostHog or Google if the creator turns them on.No
marketingOffers and news from the game's owner.No
functionalRemembering a fan's preferences on the device.No

These are the Monterosa SDK's own category names, so an app's answer needs no translating on the way in.

Change the words

Every word on the banner is the producer's, in Studio under Wording. An empty field shows the default.

Studio settingDefault
consent_brand_nameNone. Required: verify fails and deploy refuses while it is empty. Name your own brand, the one fans know: the banner reads "Arsenal and Monterosa, who run this game…". Only Monterosa's own sample games name Monterosa, and the banner then reads "Monterosa, who runs this game…".
consent_titleYour privacy
consent_detail{brand} would like to record analytics on how you play, so we can improve the game. We only do this if you agree. Data needed to run the game, such as your scores, is processed on the basis of legitimate interests.
consent_accept_labelAccept
consent_reject_labelReject
consent_choose_labelManage preferences
consent_save_labelSave my choices
consent_analytics_labelMeasure how the game is played
consent_marketing_labelOffers and news from the game's owner
consent_functional_labelRemember my preferences
consent_settings_labelPrivacy settings
privacy_policy_urlNone. Required, and must be https: verify fails and deploy refuses while it is empty.
consent_policy_version1. Change it when your privacy policy changes, and every fan is asked again.

A club app that embeds a game has usually asked its fan already. Pass that answer in and the game never shows its own banner. A host's answer always beats the game's.

With the Monterosa SDK, set it in the host page. It reaches the game on its own:

import { setConsentState } from "@monterosa/sdk-consent-kit";

setConsentState({ necessary: true, analytics: true, marketing: false, functional: true });

Without the SDK, post it into the game's frame:

frame.contentWindow.postMessage(
  { type: "gamestage:consent", categories: { analytics: true, marketing: false, functional: true } },
  new URL(frame.src).origin,
);

Only a message from the frame's own parent is read. A missing category is a no.

Read the answer in your game

game.consent holds the answer. You rarely need it: the client's gates already read it.

MemberReturnsWhat it is for
record()ConsentRecord or nullThe full answer: categories, source (host, fan or default), decidedAt, policyVersion, jurisdiction. null while nobody has answered.
needed()booleanTrue when the game must still ask.
hostDecides()booleanTrue when the embedding app answered. Hide your own privacy link when it is.
decide(categories)booleanRecords the fan's answer. False where the host decides.
openSettings()nothingOpens the choices again.
onChange(listener)an unsubscribe functionCalled whenever any authority changes the answer.

A Gamestage UI game uses the consent component, which reads and writes the same record. Turn the client's banner off so there are not two:

const game = await gamestage.start({ consentBanner: false });

Any screen of your own must carry data-gs-component="consent", offer reject as plainly as accept, and call game.consent.decide. verify and deploy check it by loading the game as a new fan, so a page that turns the banner off and asks nobody is refused.

When the check fails

verify or deploy saysWhat to do
The page sent analytics before the fan answeredSend events through game.track, which waits for consent. Remove any tracking script loaded directly in the page.
The banner would not say who is askingAdd consent_brand_name and privacy_policy_url to gamestage.settings.json.
No consent banner appearedLoad the Gamestage client and leave consentBanner on, or draw the consent component. Check the console for an error that stopped gamestage.start().
The fan said no and the page still sent analyticsSomething in the page tracks without the client. Remove it.
Consent checked by reading the page, not in a browserNo Chrome on this machine. Install Chrome, or set GAMESTAGE_CHROME, for the full check.

If your game records nothing about fans at all, set identity.consent.analytics_requires_consent: false in gamestage.yaml and no banner is required. That is your declaration to answer for.