🎯

hytopia-persisted-data

🎯Skill

from abstrucked/hytopia-skills

VibeIndex|
What it does

Enables persistent data storage and retrieval for HYTOPIA SDK games, supporting global game configurations and per-player progress tracking.

πŸ“¦

Part of

abstrucked/hytopia-skills(12 items)

hytopia-persisted-data

Installation

Quick InstallInstall with npx
npx skills add Abstrucked/hytopia-skills --skill='*'
Quick InstallInstall with npx
npx skills add Abstrucked/hytopia-skills --skill='*' -g
Quick InstallInstall with npx
npx skills add Abstrucked/hytopia-skills --skill='hytopia-entities'
Quick InstallInstall with npx
npx skills add Abstrucked/hytopia-skills --skill='hytopia-world'
πŸ“– Extracted from docs: abstrucked/hytopia-skills
1Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Helps save and load persistent data in HYTOPIA SDK games. Use when users need to save player progress, leaderboards, game state, or any data that persists across sessions. Covers PersistenceManager, global data, and player data.

Overview

# HYTOPIA Persisted Data

This skill helps you save and load persistent data in HYTOPIA SDK games.

Documentation: https://dev.hytopia.com/sdk-guides/persisted-data

When to Use This Skill

Use this skill when the user:

  • Wants to save player progress between sessions
  • Needs to create leaderboards or high scores
  • Asks about storing game configuration
  • Wants persistent inventories or unlocks
  • Needs to save world state
  • Asks about data persistence across server restarts

Data Types

| Type | Scope | Use Cases |

|------|-------|-----------|

| Global Data | All game instances | Leaderboards, game config, shared state |

| Player Data | Per player | Progress, inventory, stats, preferences |

Global Data

Shared across all running game instances.

Set Global Data

```typescript

import { PersistenceManager } from 'hytopia';

// Save global data

await PersistenceManager.instance.setGlobalData('leaderboard', [

{ name: 'Player1', score: 1000 },

{ name: 'Player2', score: 950 },

{ name: 'Player3', score: 900 }

]);

// Save game config

await PersistenceManager.instance.setGlobalData('game-config', {

maxPlayers: 20,

roundDuration: 300,

difficulty: 'hard'

});

```

Get Global Data

```typescript

import { PersistenceManager } from 'hytopia';

// Get leaderboard

const leaderboard = await PersistenceManager.instance.getGlobalData('leaderboard');

console.log('Top scores:', leaderboard);

// Get with default

const config = await PersistenceManager.instance.getGlobalData('game-config');

if (!config) {

// Use defaults

}

```

Player Data

Persisted per player across sessions.

Set Player Data

```typescript

import { Player } from 'hytopia';

// Save player progress

await player.setPersistedData('level', 15);

await player.setPersistedData('xp', 2500);

await player.setPersistedData('gold', 1000);

// Save complex data

await player.setPersistedData('inventory', [

{ id: 'sword', quantity: 1 },

{ id: 'potion', quantity: 5 },

{ id: 'key', quantity: 3 }

]);

await player.setPersistedData('unlocks', {

skins: ['default', 'warrior', 'mage'],

maps: ['forest', 'desert'],

achievements: ['first-kill', 'speedrun']

});

```

Get Player Data

```typescript

import { Player } from 'hytopia';

// Get player progress

const level = await player.getPersistedData('level') || 1;

const xp = await player.getPersistedData('xp') || 0;

const gold = await player.getPersistedData('gold') || 100;

// Get inventory

const inventory = await player.getPersistedData('inventory') || [];

// Load player on join

world.onPlayerJoin = async (player) => {

const savedData = await player.getPersistedData('progress');

if (savedData) {

player.setData('level', savedData.level);

player.setData('xp', savedData.xp);

player.setHealth(savedData.health);

console.log(Loaded ${player.username}'s progress);

} else {

// New player - set defaults

player.setData('level', 1);

player.setData('xp', 0);

console.log(New player: ${player.username});

}

};

```

Shallow Merging

When updating object data, HYTOPIA performs shallow merging at the root level.

```typescript

// Initial data

await player.setPersistedData('stats', {

kills: 10,

deaths: 5,

playtime: 3600

});

// Update only kills - other fields preserved

await player.setPersistedData('stats', {

kills: 15

});

// Result: { kills: 15, deaths: 5, playtime: 3600 }

// WARNING: Nested objects are replaced entirely

await player.setPersistedData('settings', {

audio: { music: 0.5, sfx: 1.0 },

video: { quality: 'high' }

});

await player.setPersistedData('settings', {

audio: { music: 0.3 } // sfx is LOST!

});

// Result: { audio: { music: 0.3 }, video: { quality: 'high' } }

```

Safe Nested Updates

```typescript

// Always fetch, modify, and save for nested data

async function updateNestedSetting(player: Player, path: string, value: any) {

const settings = await player.getPersistedData('settings') || {};

// Deep update

const keys = path.split('.');

let obj = settings;

for (let i = 0; i < keys.length - 1; i++) {

obj[keys[i]] = obj[keys[i]] || {};

obj = obj[keys[i]];

}

obj[keys[keys.length - 1]] = value;

await player.setPersistedData('settings', settings);

}

// Usage

await updateNestedSetting(player, 'audio.music', 0.3);

```

Common Patterns

Leaderboard System

```typescript

import { PersistenceManager } from 'hytopia';

async function updateLeaderboard(playerName: string, score: number) {

// Get current leaderboard

const leaderboard = await PersistenceManager.instance.getGlobalData('leaderboard') || [];

// Check if player already on board

const existingIndex = leaderboard.findIndex(e => e.name === playerName);

if (existingIndex !== -1) {

// Update if new score is higher

if (score > leaderboard[existingIndex].score) {

leaderboard[existingIndex].score = score;

}

} else {

// Add new entry

leaderboard.push({ name: playerName, score });

}

// Sort and keep top 100

leaderboard.sort((a, b) => b.score - a.score);

const top100 = leaderboard.slice(0, 100);

await PersistenceManager.instance.setGlobalData('leaderboard', top100);

return top100;

}

async function getLeaderboard(limit: number = 10) {

const leaderboard = await PersistenceManager.instance.getGlobalData('leaderboard') || [];

return leaderboard.slice(0, limit);

}

```

Auto-Save System

```typescript

class AutoSave {

private saveInterval: number = 60000; // 1 minute

private dirty: Set = new Set();

constructor() {

setInterval(() => this.saveAll(), this.saveInterval);

}

markDirty(playerId: string) {

this.dirty.add(playerId);

}

async saveAll() {

for (const playerId of this.dirty) {

const player = world.getPlayer(playerId);

if (player) {

await this.savePlayer(player);

}

}

this.dirty.clear();

console.log(Auto-saved ${this.dirty.size} players);

}

async savePlayer(player: Player) {

await player.setPersistedData('progress', {

level: player.getData('level'),

xp: player.getData('xp'),

gold: player.getData('gold'),

inventory: player.getData('inventory'),

lastSaved: Date.now()

});

}

}

const autoSave = new AutoSave();

// Mark player dirty when they change

function addXP(player: Player, amount: number) {

const currentXP = player.getData('xp') || 0;

player.setData('xp', currentXP + amount);

autoSave.markDirty(player.id);

}

```

Save on Disconnect

```typescript

world.onPlayerLeave = async (player) => {

// Save all player data before they leave

await player.setPersistedData('progress', {

level: player.getData('level'),

xp: player.getData('xp'),

position: player.position,

inventory: player.getData('inventory'),

lastPlayed: Date.now()

});

console.log(Saved ${player.username}'s progress);

};

```

Unlockables System

```typescript

async function unlockItem(player: Player, category: string, itemId: string) {

const unlocks = await player.getPersistedData('unlocks') || {};

if (!unlocks[category]) {

unlocks[category] = [];

}

if (!unlocks[category].includes(itemId)) {

unlocks[category].push(itemId);

await player.setPersistedData('unlocks', unlocks);

player.sendMessage(Unlocked: ${itemId}!);

return true;

}

return false; // Already unlocked

}

async function hasUnlock(player: Player, category: string, itemId: string) {

const unlocks = await player.getPersistedData('unlocks') || {};

return unlocks[category]?.includes(itemId) || false;

}

// Usage

await unlockItem(player, 'skins', 'golden-armor');

const hasSkin = await hasUnlock(player, 'skins', 'golden-armor');

```

Environment Configuration

Local Development

Data persists in auto-generated dev/ directory between restarts.

Tip: Use single browser tabs during local testing - player IDs are assigned sequentially starting at 1 after server restarts.

Production

Set environment variables:

  • NODE_ENV=production
  • HYTOPIA_API_KEY - Your API key
  • HYTOPIA_GAME_ID - Your game ID
  • HYTOPIA_LOBBY_ID - Your lobby ID

Upon deployment, HYTOPIA automatically configures persistence services.

Best Practices

  1. Save on important actions - Don't wait for disconnect
  2. Use auto-save - Backup every few minutes
  3. Handle missing data - Always provide defaults
  4. Validate loaded data - Check for corruption/outdated formats
  5. Batch updates - Don't save on every tiny change
  6. Version your data - Include version number for migrations
  7. Test with fresh data - Delete dev/ folder to test new player flow

Data Migration Example

```typescript

const CURRENT_VERSION = 2;

async function loadPlayerData(player: Player) {

const data = await player.getPersistedData('progress');

if (!data) {

return getDefaultData();

}

// Migrate old data formats

if (!data.version || data.version < CURRENT_VERSION) {

const migrated = migrateData(data);

await player.setPersistedData('progress', migrated);

return migrated;

}

return data;

}

function migrateData(data: any) {

let migrated = { ...data };

// v1 -> v2: inventory format changed

if (!data.version || data.version < 2) {

if (Array.isArray(data.inventory)) {

migrated.inventory = data.inventory.map(item =>

typeof item === 'string'

? { id: item, quantity: 1 }

: item

);

}

}

migrated.version = CURRENT_VERSION;

return migrated;

}

```

Common Mistakes

  • Not handling null/undefined when data doesn't exist
  • Saving too frequently (causes performance issues)
  • Not saving before player disconnects
  • Forgetting shallow merge behavior for nested objects
  • Not testing with fresh player data

More from this repository10

🎯
hytopia-multiplayer🎯Skill

Enables seamless multiplayer game development in HYTOPIA SDK by providing comprehensive player management, networking, and state synchronization tools.

🎯
hytopia-world🎯Skill

Programmatically builds, generates, and manages worlds in HYTOPIA SDK with advanced block placement, terrain generation, and world editor integration.

🎯
hytopia-entities🎯Skill

Generates and manages game entities in HYTOPIA SDK, enabling creation of interactive objects, NPCs, and game elements with advanced lifecycle and component management.

🎯
hytopia-assets🎯Skill

I apologize, but I cannot generate a description without seeing the actual repository or skill details. Could you provide more context about the "hytopia-assets" skill from the "abstrucked/hytopia-...

🎯
hytopia-physics🎯Skill

I apologize, but I cannot generate a description without seeing the actual context or details about the "hytopia-physics" skill. Could you provide more information about what this specific skill do...

🎯
hytopia-events🎯Skill

Handles game events and input in HYTOPIA SDK, enabling responsive player interactions, chat commands, and game lifecycle management.

🎯
hytopia-mobile🎯Skill

Enables mobile game support in HYTOPIA SDK by providing touch controls, device detection, responsive UI, and cross-platform compatibility.

🎯
hytopia-debugging🎯Skill

Debugs HYTOPIA SDK games by providing performance metrics, error tracking, client debugging tools, and comprehensive logging across client and server environments.

🎯
hytopia-plugins🎯Skill

I apologize, but I cannot generate a description without seeing the actual repository or having details about the "hytopia-plugins" skill. Could you provide more context or share the repository con...

🎯
hytopia-camera🎯Skill

I apologize, but I cannot generate a description without seeing the actual code or context of the "hytopia-camera" skill. Could you provide more details about the skill's functionality or share the...