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.
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.loadGamewaits for it.loadGame(gameId, options)resolves to aGameInstancewithdestroy,reload,on, andoff.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 toready,start,finish,share, anderroracross every game on the page.onMessagereceives the type of every message the game sends (for examplelil-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
urlis blank, the SDK substituteswindow.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. PassautoScrollWhenShared: falsetoinit()to turn that off. - Taps on the in-game Share button fire the
lil-snack-game-sharemessage (see Events) plus theonSharecallback. 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-writeto the iframeallowattribute sonavigator.shareand the clipboard fallback both work. Any customiframe.allowyou 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
| Prop | Type | Required | Description |
|---|---|---|---|
gameId | string | Yes | The game to load |
clientKey | string | Yes | Your partner key, used for init(...) before loading the game |
apiHost | string | No | Host override for the game iframe, e.g. https://staging.play.lilsnack.co |
userId | string | No | Partner user ID passed through identify(...) before each mount |
token | string | No | Signed identity token passed through setToken(...) before each mount |
autoInit | boolean | No | Defaults to true; when false, LilSnackGame will not call init(...) |
ready | boolean | No | Defaults to true; while false, the game waits to load (e.g. until you know the user) |
iframe | IframeOptions | No | Mount-time iframe attributes (changes after mount are ignored) |
targetRef | RefObject<HTMLElement> | No | Render the iframe into an external element instead of the built-in div |
onReady | (instance, data) => void | No | Fired when the game is loaded and playable |
onStart | (instance, data) => void | No | Fired when gameplay starts |
onFinish | (instance, data) => void | No | Fired when gameplay finishes |
onShare | (instance, data) => void | No | Fired when the player taps the in-game Share button |
onError | (error, instance?) => void | No | Fired on setup or iframe errors (error.code, error.message) |
onMessage | (type, instance?) => void | No | Fired with the type of every message the game sends |
share | true | ShareConfig | No | Enables the in-game Share button; true uses the current URL (mount-time) |
autoScrollWhenShared | boolean | No | Defaults to true; scrolls a shared game into view when its link is opened |
debug | boolean | No | Enables SDK debug logging |
className | string | No | Class name for the container div |
style | CSSProperties | No | Inline 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.