Library Reference
The **Network Storage by sboxcool.com** library provides a clean C# API for s&box games.
# Library Reference
The **Network Storage by sboxcool.com** library provides a clean C# API for s&box games.
**Install:** [sbox.game/sboxcool/network-storage](https://sbox.game/sboxcool/network-storage)
**Source:** [github.com/sbox-cool/sbox-network-storage](https://github.com/sbox-cool/sbox-network-storage)
Or search for `Network Storage by sboxcool.com` in **Editor > Library Manager** in the s&box editor. For a step-by-step setup walkthrough, see the [Library Setup Guide](/wiki/network-storage-v3/library-setup).
## NetworkStorage
The main static class. Configure once, then call from anywhere.
### Configure
```csharp
// Required — call once at startup
NetworkStorage.Configure( "your-project-id", "sbox_ns_your_key" );
// Optional — custom base URL or API version
NetworkStorage.Configure( "project-id", "sbox_ns_key", "https://api.sboxcool.com", "v3" );
```
### Call an Endpoint
Endpoints run server-side logic (read → validate → write) and return the result.
```csharp
// Call with input
var result = await NetworkStorage.CallEndpoint( "mine-ore", new
{
ore_id = "moon_ore",
kg = 5.2f
} );
if ( result.HasValue )
{
int xp = result.Value.Int( "xp" );
int level = result.Value.Int( "level" );
Log.Info( $"Mined! XP: {xp}, Level: {level}" );
}
// Call without input
var player = await NetworkStorage.CallEndpoint( "load-player" );
```
### Call an Endpoint on Behalf of Another Player (Proxy Mode)
For dedicated servers where the host calls endpoints on behalf of connected clients:
```csharp
// Host calls endpoint for a specific client
var result = await NetworkStorage.CallEndpointAs(
targetSteamId: clientSteamId,
clientToken: clientToken,
slug: "report-kill",
input: new { target_type = "goblin" }
);
```
See [s&box Auth → Proxy Mode](/wiki/network-storage-v3/sbox-auth) for the full proxy mode documentation.
### Collection Documents: Create, Read, Edit, Delete
Read and write directly against collection rows/documents. Use this from trusted server code or for public reads; for player-facing economy/gameplay, prefer server-authoritative endpoints.
```csharp
// Read current player's document. If documentId is omitted, Game.SteamId is used.
var data = await NetworkStorage.GetDocument( "players" );
// Read a specific document by key.
var config = await NetworkStorage.GetDocument( "game_config", "settings" );
// Create or replace a full document.
await NetworkStorage.SaveDocument( "players", "76561198000000001", new
{
playerName = "Ada",
xp = 1200,
gold = 45
} );
// Alias for SaveDocument.
await NetworkStorage.SetDocument( "players", "76561198000000001", new { playerName = "Ada" } );
// Edit/patch an existing document with server-side operations.
await NetworkStorage.UpdateDocument( "players", "76561198000000001",
NetworkStorageOperation.Increment( "xp", 50, source: "server", reason: "Match reward" ),
NetworkStorageOperation.Set( "lastMatchId", "match_123" ) );
// Alias for UpdateDocument.
await NetworkStorage.PatchDocument( "players", "76561198000000001",
NetworkStorageOperation.Set( "playerName", "Ada Lovelace" ) );
// Delete one row/document. This does not delete the collection definition/schema.
await NetworkStorage.DeleteDocument( "players", "76561198000000001" );
```
Dedicated servers with `+network_storage_secret_key` and `collections: x` can use these helpers for endpoint-controlled collections and backoffice cleanup. Public/client delete calls require record deletion to be enabled on the collection.
### Save Slot Records
For multi-record per-player collections:
```csharp
var records = await NetworkStorage.ListRecords( "saves", steamId );
var created = await NetworkStorage.CreateRecord( "saves", steamId, "Slot 1" );
await NetworkStorage.RenameRecord( "saves", recordId, "Renamed Slot", steamId );
await NetworkStorage.DeleteRecord( "saves", recordId, steamId );
```
### Read a Document on Behalf of Another Player (Proxy Mode)
```csharp
// Host reads a client's document
var data = await NetworkStorage.GetDocumentAs(
targetSteamId: clientSteamId,
clientToken: clientToken,
collectionId: "players"
);
```
### Run a Query
Run a dashboard query by ID. Secret-gated queries require a dedicated-server secret key with `queries: x`.
```csharp
var leaderboard = await NetworkStorage.RunQuery( "top_players" );
// Alias
var same = await NetworkStorage.GetQuery( "top_players" );
```
See [Dedicated Server Runtime](/wiki/network-storage-v3/dedicated-server-runtime) for secret-key query calls.
### Get Game Values
Load server-authoritative constants and tables.
```csharp
var values = await NetworkStorage.GetGameValues();
if ( values.HasValue )
{
// Groups (key-value constants)
var progression = values.Value.GetProperty( "groups" ).GetProperty( "progression" );
int xpPerLevel = progression.GetProperty( "xp_per_level" ).GetInt32();
// Tables (structured rows)
var oreTable = values.Value.GetProperty( "tables" ).GetProperty( "ore_types" );
foreach ( var row in oreTable.GetProperty( "rows" ).EnumerateArray() )
{
string name = row.GetProperty( "name" ).GetString();
int tier = row.GetProperty( "tier" ).GetInt32();
}
}
```
### Properties
| Property | Type | Description |
|----------|------|-------------|
| `NetworkStorage.IsConfigured` | `bool` | True after Configure() is called |
| `NetworkStorage.ProjectId` | `string` | Current project ID |
| `NetworkStorage.ApiKey` | `string` | Current public API key |
| `NetworkStorage.BaseUrl` | `string` | Base URL (default: `https://api.sboxcool.com`) |
| `NetworkStorage.ApiVersion` | `string` | API version (default: `v3`) |
| `NetworkStorage.ApiRoot` | `string` | Full versioned URL (e.g. `https://api.sboxcool.com/v3`) |
| `NetworkStorage.IsHost` | `bool` | True if this is the network host or single-player |
| `NetworkStorage.ProxyEnabled` | `bool` | Enable/disable proxy mode (default: `true`) |
| `NetworkStorage.HasDedicatedServerSecretKey` | `bool` | True when a dedicated server host loaded a runtime secret key |
| `NetworkStorage.DedicatedServerSecretKeySource` | `string` | Where the dedicated-server secret key was loaded from, without exposing the key |
### Proxy Delegates
For advanced proxy mode customization, the library exposes delegates that handle routing proxy requests:
| Delegate | Signature | Description |
|----------|-----------|-------------|
| `NetworkStorage.RequestProxy` | `Func<string, string, string, string, Task<string>>` | Override how proxy endpoint calls are routed |
| `NetworkStorage.DocumentProxy` | `Func<string, string, string, string, Task<string>>` | Override how proxy document reads are routed |
By default, these delegates use `NetworkStorageProxyComponent` which routes requests through RPC. You can replace them with custom implementations for different networking architectures.
---
## JsonHelpers
Static helpers for parsing JSON responses with fallback defaults.
```csharp
int xp = JsonHelpers.GetInt( element, "xp", 0 );
float speed = JsonHelpers.GetFloat( element, "speed", 1.0f );
string name = JsonHelpers.GetString( element, "name", "Unknown" );
bool active = JsonHelpers.GetBool( element, "active", true );
```
## Extension Methods
Fluent shortcuts on `JsonElement`:
```csharp
// Short versions of JsonHelpers
int xp = data.Int( "xp" );
float kg = data.Float( "kg", 0f );
string name = data.Str( "name", "Unknown" );
// Parse arrays and dictionaries
List<string> upgrades = data.ReadStringList( "purchasedUpgrades" );
Dictionary<string, float> ores = data.ReadDictionary( "ores", v => (float)v.GetDouble() );
// Parse typed lists
var items = data.ReadList( "rows", row => new Item
{
Name = row.Str( "name" ),
Cost = row.Int( "cost" )
} );
```
---
## SaveStateTracker
Tracks endpoint call state for UI feedback (saving indicator, error display).
```csharp
public class PlayerDataManager : Component
{
private SaveStateTracker _tracker = new();
// Show state on HUD
public SaveStateTracker.SaveState State => _tracker.State;
public bool IsSaving => _tracker.IsBusy;
public string LastError => _tracker.LastError;
public async Task SellOre( string oreId, float kg )
{
var result = await _tracker.Call( "sell-ore", new { ore_id = oreId, kg } );
if ( result.HasValue )
Apply( result.Value );
}
}
```
### Optimistic Updates
The `CallAndApply` pattern handles optimistic update → server confirm → revert on failure:
```csharp
await _tracker.CallAndApply(
slug: "sell-ore",
input: new { ore_id = oreId, kg },
applyOptimistic: () => { Gold += estimatedValue; OreCount -= kg; },
applyServer: (data) => { Gold = data.Int( "gold" ); },
revert: () => { Gold -= estimatedValue; OreCount += kg; }
);
```
---
## NetLog
Debug event logger. All `NetworkStorage` calls automatically log to NetLog.
```csharp
// Access the log
foreach ( var entry in NetLog.Entries )
{
// entry.Time, entry.Kind (Request/Response/Error/Info), entry.Tag, entry.Message
}
// Manual logging
NetLog.Info( "my-system", "Something happened" );
NetLog.Error( "my-system", "Something broke" );
// Version counter (for UI change detection)
int version = NetLog.Version;
```
---
## Editor Windows
The library adds several windows to the s&box editor:
### Sync Tool (Editor → Network Storage → Sync Tool)
Manage your project data from the editor:
- **Push** local endpoints and collections to the server
- **Pull** server data into local YAML Source files
- **Check for Updates** to see what's different
- **View Diff** to compare local vs remote
- **Setup** to configure credentials and preferences
- **Merge View** — auto-opens after push to review server-added fields
### Settings (Editor → Network Storage → Settings)
Project-level settings:
- **Proxy Mode** toggle — enable for dedicated server setups
- Runtime config is generated automatically from the Setup tool
### Code Generator (Editor → Network Storage → Generate Code)
Generates strongly-typed C# from your project's schemas:
- `NSConfig` — static config class (used as fallback when credentials file is missing)
- `NSCollections` — collection schema classes
- `NSEndpoints` — endpoint slug constants and input classes
- `NSWorkflows` — workflow ID constants
### Setup (Editor → Network Storage → Setup)
Configure credentials:
- **Project ID** — identifies your project in URLs and library config
- **Public Key** — used with the Project ID for runtime endpoints, storage, and values
- **Secret Key** — used only by the Sync Tool / Management API to sync collections, endpoints, workflows, and settings
- Secret key field is masked by default (click Show/Hide to toggle)
- Test Connection shows key permissions
- Key prefix warnings for non-standard keys
YAML Source is the standard for endpoint, workflow, and collection definitions managed by the Sync Tool. Prefer `.yml` filenames for new YAML Source files. `.yaml` remains accepted and is treated the same. Use YAML Source for synced definitions.
### File Structure
```
Editor/Network Storage/
config/
public/projectConfig.json # Project ID, public key, base URL
secret/secret_key.json # Secret key only (gitignored)
endpoints/
mine-ore.endpoint.yml # One YAML Source file per endpoint
sell-ore.endpoint.yml
load-player.endpoint.yml
collections/
players.collection.yml # One YAML Source file per collection
game_values.collection.yml # Game config (constants + tables)
```
### Setup
1. Open Editor → Network Storage → Setup
2. Enter your Project ID, Public Key, and Secret Key
3. Click Save Configuration
4. Click Test Connection to verify
The secret key stays in `Editor/Network Storage/config/secret/`, which is **never published** with your game. Your runtime build uses the Project ID + Public Key instead.