Use The JavaScript SDK From jsDelivr In One HTML File
Import @persistlyapp/sdk from jsDelivr and build a tiny no-build-step browser save demo with PersistlyGameSaves.
Most production web games should install Persistly with npm. For a quick prototype, tutorial, CodePen-style demo, or one-file HTML experiment, you can import the JavaScript SDK directly from jsDelivr.
This works because the public package ships as an ESM module. You do not need a global script tag or a build step.
When To Use This
Use the jsDelivr path when you want:
- one HTML file
- no npm install
- no Vite or bundler setup
- a small browser demo that proves save, load, and sync
Use npm when you are building a real application with TypeScript, bundling, tests, and deployment tooling.
Create The HTML File
Create an index.html file and replace ps_test_replace_me with a stage runtime key from your Persistly dashboard.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Persistly jsDelivr Save Demo</title>
</head>
<body>
<main>
<h1>Persistly Save Demo</h1>
<p>Coins: <span id="coins">0</span></p>
<p id="status">Loading...</p>
<button id="earn">Earn coin</button>
<button id="sync">Sync now</button>
</main>
<script type="module">
import {
PersistlyGameSaveStatus,
PersistlyGameSaves,
} from "https://cdn.jsdelivr.net/npm/@persistlyapp/sdk@1/+esm";
const coinsEl = document.querySelector("#coins");
const statusEl = document.querySelector("#status");
const earnButton = document.querySelector("#earn");
const syncButton = document.querySelector("#sync");
let gameState = {
schemaVersion: 1,
coins: 0,
updatedAt: new Date().toISOString(),
};
function render(status) {
coinsEl.textContent = String(gameState.coins);
statusEl.textContent = status;
}
await PersistlyGameSaves.configure({
runtimeKey: "ps_test_replace_me",
});
const loaded = await PersistlyGameSaves.shared.loadData();
if (loaded.status === PersistlyGameSaveStatus.LocalFound && loaded.data) {
gameState = loaded.data;
render("Loaded local save.");
} else {
render("Started a new save.");
}
earnButton.addEventListener("click", async () => {
gameState = {
...gameState,
coins: gameState.coins + 1,
updatedAt: new Date().toISOString(),
};
await PersistlyGameSaves.shared.saveData(gameState, {
slotInfo: {
label: "Autosave",
coins: gameState.coins,
updatedAt: gameState.updatedAt,
},
});
render("Saved locally.");
});
syncButton.addEventListener("click", async () => {
render("Syncing...");
const result = await PersistlyGameSaves.shared.forceSyncData();
if (result.status === PersistlyGameSaveStatus.Synced) {
render("Synced to Persistly.");
return;
}
if (result.status === PersistlyGameSaveStatus.Conflict) {
render("Conflict detected. Add recovery UI before shipping.");
return;
}
render("Saved locally. Cloud sync can retry later.");
});
</script>
</body>
</html>Test The Demo
Open the file in a local browser or serve it with any static file server. Click Earn coin, refresh the page, and confirm the coin count comes back from local storage.
Then click Sync now. The first successful sync creates the remote Persistly account and default autosave slot behind the scenes.
Why This Is Still Local-First
saveData writes to browser storage first. It does not wait for the network. forceSyncData is the explicit cloud sync step in this tiny demo.
For a real game, call syncDue or forceSyncData at safe moments such as checkpoint, pause, menu, level complete, or page visibility change. Do not sync every frame.
Next Step
When the prototype grows, move the same calls into a small save service module and install the package with npm:
npm install @persistlyapp/sdkThe runtime behavior stays the same. The import path changes from jsDelivr to @persistlyapp/sdk.