DataStore APIs allow you to read, write, and manage persistent data for your Roblox experiences from external services.
Overview
RoZod provides access to both v1 and v2 DataStore endpoints:
- v1: Legacy endpoints with basic CRUD operations
- v2: Modern endpoints with scopes, revisions, and advanced filtering
Prefer v2 endpoints for new projects as they offer better features and performance.
Import
import { fetchApi } from 'rozod';
import { v1, v2 } from 'rozod/lib/opencloud';
// Or import specific functions
import {
getCloudV2UniversesUniverseIdDataStoresDataStoreIdEntriesEntryId
} from 'rozod/lib/opencloud/v2/cloud';
Authentication
DataStore operations require specific scopes in your API key:
universe-datastores.objects:read - Read entries
universe-datastores.objects:write - Create/update entries
universe-datastores.objects:create - Create new entries
universe-datastores.objects:update - Update existing entries
universe-datastores.objects:delete - Delete entries
universe-datastores.objects:list - List entries and data stores
universe-datastores.versions:read - Read entry versions
universe-datastores.versions:list - List entry versions
universe-datastores.control:list - List data stores
universe-datastores.control:delete - Delete data stores
import { configureServer } from 'rozod';
configureServer({
cloudKey: 'your_api_key_with_datastore_scopes'
});
List data stores
Get a list of all data stores in your universe:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
const response = await fetchApi(v2.getCloudV2UniversesUniverseIdDataStores, {
universe_id: '123456789',
maxPageSize: 50,
filter: 'id.startsWith("player_")',
});
for (const dataStore of response.dataStores) {
console.log(dataStore.id);
console.log(dataStore.createTime);
}
Get an entry
Retrieve a specific entry from a data store:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
const entry = await fetchApi(
v2.getCloudV2UniversesUniverseIdDataStoresDataStoreIdEntriesEntryId,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
entry_id: 'player_156',
}
);
console.log(entry.value); // The stored data
console.log(entry.revisionId); // Current revision ID
console.log(entry.users); // Associated user IDs
console.log(entry.attributes); // Entry attributes
Get entry at specific revision
v2 supports retrieving historical versions:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
// Get specific revision
const entry = await fetchApi(
v2.getCloudV2UniversesUniverseIdDataStoresDataStoreIdEntriesEntryId,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
entry_id: '[email protected]',
}
);
// Get entry as it was at a specific time
const historicalEntry = await fetchApi(
v2.getCloudV2UniversesUniverseIdDataStoresDataStoreIdEntriesEntryId,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
entry_id: 'player_156@latest:2024-12-02T06:00:00Z',
}
);
Create an entry
Create a new entry in a data store:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
const newEntry = await fetchApi(
v2.postCloudV2UniversesUniverseIdDataStoresDataStoreIdEntries,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
id: 'player_156',
},
{
value: {
coins: 1000,
level: 5,
inventory: ['sword', 'shield'],
},
users: ['156'],
attributes: {
lastLogin: '2024-01-15',
},
}
);
Update an entry
Update an existing entry:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
const updated = await fetchApi(
v2.patchCloudV2UniversesUniverseIdDataStoresDataStoreIdEntriesEntryId,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
entry_id: 'player_156',
allowMissing: false, // If true, creates entry if it doesn't exist
},
{
value: {
coins: 1500,
level: 6,
inventory: ['sword', 'shield', 'potion'],
},
users: ['156'],
}
);
v2 PATCH operations do not support partial updates. If you don’t include attributes or users, they will be cleared.
Increment an entry
Atomically increment a numeric value:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
const result = await fetchApi(
v2.postCloudV2UniversesUniverseIdDataStoresDataStoreIdEntriesEntryIdIncrement,
{
universe_id: '123456789',
data_store_id: 'Leaderboard',
entry_id: 'player_156_score',
},
{
amount: 10,
users: ['156'],
attributes: {
lastUpdated: new Date().toISOString(),
},
}
);
console.log('New value:', result.value);
Delete an entry
Mark an entry for deletion (permanent deletion after 30 days):
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
await fetchApi(
v2.deleteCloudV2UniversesUniverseIdDataStoresDataStoreIdEntriesEntryId,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
entry_id: 'player_156',
}
);
List entries
List all entries in a data store:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
// List from all scopes
const response = await fetchApi(
v2.getCloudV2UniversesUniverseIdDataStoresDataStoreIdEntries,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
maxPageSize: 100,
filter: 'id.startsWith("player_")',
}
);
for (const entry of response.dataStoreEntries) {
console.log(entry.id, entry.path);
}
// Continue with pagination
if (response.nextPageToken) {
const nextPage = await fetchApi(
v2.getCloudV2UniversesUniverseIdDataStoresDataStoreIdEntries,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
pageToken: response.nextPageToken,
}
);
}
Working with scopes
Scopes allow you to organize entries within a data store:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
// Create entry in a specific scope
const entry = await fetchApi(
v2.postCloudV2UniversesUniverseIdDataStoresDataStoreIdScopesScopeIdEntries,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
scope_id: 'production',
id: 'player_156',
},
{
value: { coins: 1000 },
}
);
// Get entry from specific scope
const scopedEntry = await fetchApi(
v2.getCloudV2UniversesUniverseIdDataStoresDataStoreIdScopesScopeIdEntriesEntryId,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
scope_id: 'production',
entry_id: 'player_156',
}
);
List entry revisions
Retrieve version history for an entry:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
const revisions = await fetchApi(
v2.getCloudV2UniversesUniverseIdDataStoresDataStoreIdEntriesEntryIdListRevisions,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
entry_id: 'player_156',
maxPageSize: 50,
filter: 'revision_create_time >= 2024-01-01T00:00:00Z && revision_create_time <= 2024-12-31T23:59:59Z',
}
);
for (const revision of revisions.dataStoreEntries) {
console.log(`Revision ${revision.revisionId}`);
console.log(`Created: ${revision.revisionCreateTime}`);
console.log(`State: ${revision.state}`);
}
Snapshot data stores
Create a snapshot to preserve current data for 30 days:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
const snapshot = await fetchApi(
v2.postCloudV2UniversesUniverseIdDataStoresSnapshot,
{
universe_id: '123456789',
},
{}
);
console.log('New snapshot taken:', snapshot.newSnapshotTaken);
console.log('Latest snapshot time:', snapshot.latestSnapshotTime);
Snapshots can be taken once per UTC day per experience. Subsequent calls on the same day return the existing snapshot.
Delete a data store
Schedule a data store for permanent deletion:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
// Delete (30 day grace period)
const deleted = await fetchApi(
v2.deleteCloudV2UniversesUniverseIdDataStoresDataStoreId,
{
universe_id: '123456789',
data_store_id: 'OldPlayerData',
}
);
console.log('Expires on:', deleted.expireTime);
// Restore before expiry
const restored = await fetchApi(
v2.postCloudV2UniversesUniverseIdDataStoresDataStoreIdUndelete,
{
universe_id: '123456789',
data_store_id: 'OldPlayerData',
},
{}
);
Best practices
Use descriptive keys
// Good
const entry = { entry_id: 'player_156_inventory' };
// Bad
const entry = { entry_id: 'p156i' };
Handle versioning
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
// Store version info in attributes
const entry = await fetchApi(
v2.postCloudV2UniversesUniverseIdDataStoresDataStoreIdEntries,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
id: 'player_156',
},
{
value: { coins: 1000, level: 5 },
attributes: {
version: '2.0',
migrated: 'true',
migratedAt: new Date().toISOString(),
},
}
);
Implement retry logic
import { fetchApi, isAnyErrorResponse } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
async function getEntryWithRetry(entryId: string, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const result = await fetchApi(
v2.getCloudV2UniversesUniverseIdDataStoresDataStoreIdEntriesEntryId,
{
universe_id: '123456789',
data_store_id: 'PlayerData',
entry_id: entryId,
}
);
if (!isAnyErrorResponse(result)) {
return result;
}
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
}
}
throw new Error('Failed to get entry after retries');
}