Skip to main content

JavaScript SDK

Use the Lil Snack SDK to load any game into an iframe on your page and receive typed callbacks as the player moves through it. The SDK builds the game URL, manages the player's anonymous ID, checks every incoming message, and answers the game's storage requests, so there's very little for you to wire up.

tip

Want to see it before you write any code? The Playground runs real games through this SDK and generates the code for whatever settings you pick.

Installation​

CDN snippet​

<script>
!(function(g,l,h,f){if(!g[f]){g[f]=[];var q=g[f];function e(n){q[n]=function(){q.push([n].concat(Array.prototype.slice.call(arguments,0)));};}var methods="init loadGame allowAnalytics identify setToken destroy on off".split(" ");for(var i=0;i<methods.length;i++)e(methods[i]);q.l=+new Date();var t=l.getElementsByTagName(h)[0],j=l.createElement(h);j.async=1;j.src="https://cdn.lilsnack.co/lil-sdk.min.js";t.parentNode.insertBefore(j,t);}})(window,document,"script","lilsnack");
</script>

The snippet defines window.lilsnack right away and queues any calls you make until the SDK file has loaded, so you can call it immediately.

npm install​

npm install @lil-snack-git/lil-js

# React bindings. The lil-js API is re-exported from here too.
npm install @lil-snack-git/lil-sdk-react

Quick start​

CDN​

<div
id="game-container"
style="max-width: 420px; aspect-ratio: 9/16; background: #f4f2ea;"
></div>

<script>
lilsnack.init({
clientKey: "PARTNER_KEY",
apiHost: "https://staging.play.lilsnack.co",
});
lilsnack.loadGame("GAME_ID", { target: "#game-container" });
</script>

Vanilla JS with a bundler​

import lilsnack from "@lil-snack-git/lil-js";

async function mountGame() {
await lilsnack.init({
clientKey: "PARTNER_KEY",
apiHost: "https://staging.play.lilsnack.co",
});

// Optional identity inputs. See "Identity and Persistence".
lilsnack.identify("partner-user-123");

await lilsnack.loadGame("GAME_ID", {
target: "#game-container",
onFinish: (instance, data) => console.log("finished", data),
});
}

void mountGame();

React​

import { LilSnackGame } from "@lil-snack-git/lil-sdk-react";

export function GameCard({ gameId }: { gameId: string }) {
return (
<LilSnackGame
clientKey={process.env.NEXT_PUBLIC_LILSNACK_KEY!}
apiHost="https://staging.play.lilsnack.co"
gameId={gameId}
userId="partner-user-123"
style={{ maxWidth: 420, aspectRatio: "9 / 16", background: "#f4f2ea" }}
/>
);
}

clientKey is your partner key, and GAME_ID is the ID of the game to load. Your Lil Snack contact will share the game IDs scheduled for you.

Set a background on the container​

The game iframe is transparent while it loads. Whatever is behind it is what players see for that moment, so give the element you mount into a background color:

<div
id="game-container"
style="max-width: 420px; aspect-ratio: 9/16; background: #f4f2ea;"
></div>

This is deliberate, and it's the one piece of styling we ask for. A game's visual theme isn't known until its content has loaded, so rather than flash a color that turns out to be wrong, the iframe shows your page and then fades the game in over it. Your page's background is instant, needs no network round trip, and is never the wrong brand.

Use the same color your Lil Snack contact configured as your loading-screen background (the logo, loading bar, and font on that screen come from the same place). If the two differ, players see a seam when the game loads.

The container's background is all that's needed. There's no SDK option to set, and nothing to update per game.

The SDK sizes the iframe to fill its container, so the container decides the game's size. Mini snacks are designed for a portrait, phone-shaped frame.

Core API​

await lilsnack.init({
clientKey: "PARTNER_KEY",
apiHost: "https://staging.play.lilsnack.co", // optional, defaults to production
allowAnalytics: true, // optional
debug: false, // optional, logs SDK activity to the console
share: true, // optional, enables in-game Share on supported games
autoScrollWhenShared: true, // optional, see "Share"
});

const instance = await lilsnack.loadGame("GAME_ID", {
target: "#game-container", // HTMLElement | selector
iframe: {
loading: "lazy",
sandbox: "allow-scripts allow-same-origin",
},
share: { title: "Try today's puzzle" }, // optional, overrides the init default for this load
onReady(instance, data) {},
onStart(instance, data) {},
onFinish(instance, data) {},
onShare(instance, data) {},
onError(instance, error) {},
onMessage(type, instance) {},
});

lilsnack.identify("partner-user-123");
lilsnack.setToken("SIGNED_IDENTITY_TOKEN");
  • init(options) must run before the first game mounts. loadGame waits for it.
  • loadGame(gameId, options) resolves to a GameInstance with destroy, reload, on, and off.
  • identify(userId) sets the user ID for games loaded after the call.
  • setToken(token) sets a signed identity token for games loaded after the call.
  • allowAnalytics(enabled) changes the analytics setting for games loaded after the call.
  • destroy(instanceId?) removes one game, or every game when called without an ID.
  • on(event, handler) / off(event, handler) subscribe to ready, start, finish, share, and error across every game on the page.
  • onMessage receives the type of every message the game sends (for example lil-snack-view-how-to), including the ones without a dedicated callback.

Identity and analytics settings are captured when a game loads. Changing them later doesn't affect a game that's already on the page; load it again to apply them. See Events for what each callback receives, and Identity and Persistence for identify and setToken.

Share​

Partners opt into the in-game Share button by passing share on init() or per call on loadGame(). Only games that support sharing render a button. Others ignore the setting.

// Shorthand: enable share using your current page URL
await lilsnack.init({ clientKey: "PARTNER_KEY", share: true });

// Object form: override url/title/text
await lilsnack.loadGame("GAME_ID", {
target: "#game-container",
share: {
url: "https://partner.example/puzzle", // optional, defaults to the current URL
title: "Try today's puzzle", // optional
text: "I got 95%. Beat me?", // optional
},
onShare: (instance, data) => {
// Forward to your own analytics if you want
},
});

How it works:

  • The SDK encodes the share payload into the iframe URL, and the game reads it from there. If url is blank, the SDK substitutes window.location.href.
  • The SDK appends ?ls-game=<gameId> to the shared URL. When someone opens that link, the SDK on the receiving page scrolls the matching game into view once it is ready. Pass autoScrollWhenShared: false to init() to turn that off.
  • Taps on the in-game Share button fire the lil-snack-game-share message (see Events) plus the onShare callback. The SDK doesn't report shares to you any other way; the callback exists so you can record them in your own systems.
  • The SDK adds web-share; clipboard-write to the iframe allow attribute so navigator.share and the clipboard fallback both work. Any custom iframe.allow you pass is kept.

React API​

Manual init (autoInit={false})​

import { LilSnackGame, lilsnack } from "@lil-snack-git/lil-sdk-react";

await lilsnack.init({
clientKey: process.env.NEXT_PUBLIC_LILSNACK_KEY!,
apiHost: "https://staging.play.lilsnack.co",
debug: true,
});

lilsnack.identify("partner-user-123");
lilsnack.setToken("SIGNED_IDENTITY_TOKEN");

<LilSnackGame
autoInit={false}
clientKey={process.env.NEXT_PUBLIC_LILSNACK_KEY!}
gameId="GAME_ID"
/>;

Mount into an external element (targetRef)​

import { useRef } from "react";
import { LilSnackGame } from "@lil-snack-git/lil-sdk-react";

export function GameSlot() {
const targetRef = useRef<HTMLDivElement>(null);

return (
<>
<div ref={targetRef} style={{ maxWidth: 420, aspectRatio: "9 / 16" }} />
<LilSnackGame
clientKey={process.env.NEXT_PUBLIC_LILSNACK_KEY!}
gameId="GAME_ID"
targetRef={targetRef}
/>
</>
);
}

LilSnackGame props​

PropTypeRequiredDescription
gameIdstringYesThe game to load
clientKeystringYesYour partner key, used for init(...) before loading the game
apiHoststringNoHost override for the game iframe, e.g. https://staging.play.lilsnack.co
userIdstringNoPartner user ID passed through identify(...) before each mount
tokenstringNoSigned identity token passed through setToken(...) before each mount
autoInitbooleanNoDefaults to true; when false, LilSnackGame will not call init(...)
readybooleanNoDefaults to true; while false, the game waits to load (e.g. until you know the user)
iframeIframeOptionsNoMount-time iframe attributes (changes after mount are ignored)
targetRefRefObject<HTMLElement>NoRender the iframe into an external element instead of the built-in div
onReady(instance, data) => voidNoFired when the game is loaded and playable
onStart(instance, data) => voidNoFired when gameplay starts
onFinish(instance, data) => voidNoFired when gameplay finishes
onShare(instance, data) => voidNoFired when the player taps the in-game Share button
onError(error, instance?) => voidNoFired on setup or iframe errors (error.code, error.message)
onMessage(type, instance?) => voidNoFired with the type of every message the game sends
sharetrue | ShareConfigNoEnables the in-game Share button; true uses the current URL (mount-time)
autoScrollWhenSharedbooleanNoDefaults to true; scrolls a shared game into view when its link is opened
debugbooleanNoEnables SDK debug logging
classNamestringNoClass name for the container div
styleCSSPropertiesNoInline styles for the container div

When targetRef is provided, the component renders nothing and mounts the iframe into the referenced element instead.

iframe options and share are read once, at mount. Change the component key to force a remount if you need them reapplied.

onError receives both errors from the game and setup or validation failures from loadGame(...).

userId and token are forwarded before each mount. If either value changes, the component remounts the iframe so the new identity is applied.