PlaycademyPlaycademy

@playcademy/sandbox

API reference for the Playcademy Sandbox

@playcademy/sandbox

Local development server for isolated Playcademy game development.

The Playcademy sandbox provides a complete local simulation of the Playcademy platform API for development and testing.

What is the Sandbox?

The sandbox is a local development server that lets you:

  • Develop games locally with full platform integration.
  • Test SDK functionality without needing the live platform.
  • Simulate user accounts and game data in a controlled environment.
  • Work offline without an internet connection.

It's a "mock Playcademy platform" running on your machine that behaves just like the real thing.

Key Benefits

  • Isolated Environment: Completely local execution with no external dependencies.
  • Zero Configuration: Automatic database setup and seeding.
  • Full API Compatibility: All Playcademy APIs available locally.
  • Fast Development Cycle: Quick startup with persistent local database.

How It Works

The sandbox runs a local API server to provide a complete development environment. When using our Vite templates, this is handled automatically.

graph TD
    subgraph "Your Machine"
        A["Your Game (in browser)"] --> B["@playcademy/sdk"];
        B --> C["API Server (localhost:4321)"];
        C --> D["Local PGlite Database"];
    end

Usage

The easiest way to use the sandbox is with our official Vite templates, which handle everything automatically via the @playcademy/vite-plugin. You can also run it manually as a standalone CLI for advanced use cases.

When you start your development server with bun dev, the Vite plugin will launch the sandbox in the background.

::: code-group

bun dev
pnpm dev
yarn dev
npm run dev

:::

You'll see output confirming that the sandbox is running:

VITE v6.3.5

➜  Local:   http://localhost:5173/
➜  Network: use --host to expose

PLAYCADEMY v0.1.0

➜  Game:      playground
➜  Sandbox:   http://localhost:4321/api
// vite.config.ts
import { defineConfig } from 'vite'

import { playcademy } from '@playcademy/vite-plugin'

export default defineConfig({
    plugins: [
        playcademy({
            sandbox: {
                autoStart: true,
            },
        }),
    ],
})

Manual Setup

You can run the sandbox as a standalone process from the command line.

::: code-group

# Run with defaults
bunx @playcademy/sandbox

# Run with custom port and verbose logging
bunx @playcademy/sandbox --port 8080 --verbose
pnpm dlx @playcademy/sandbox --port 8080 --verbose
yarn dlx @playcademy/sandbox --port 8080 --verbose
npx @playcademy/sandbox --port 8080 --verbose

:::

Command-Line Options

OptionAliasDescriptionDefault
--port <number>-pPort for the API server.4321
--verbose-vEnables verbose logging for debugging.false
--quiet-qQuiet mode (suppress interactive output)false
--no-seedDisables seeding of demo data.seeds
--recreate-dbRecreate the on-disk database on start.false
--memoryUse in-memory database (no persistence).false
--db-path <path>Custom path for the database file.undefined
--config-path <path>Path to playcademy.config.json.cwd
--project-name <name>Sets the project name for game seeding.undefined
--project-slug <slug>Sets the project slug for game seeding.undefined

Manual SDK Configuration

If you run the sandbox manually, you'll need to configure the SDK client to point to it.

import { PlaycademyClient } from '@playcademy/sdk'

const client = await PlaycademyClient.init({
    baseUrl: 'http://localhost:4321/api',
    // In manual mode, you must provide a mock token
    token: 'mock-dev-token',
})

Sandbox Features

FeatureDescription
API SimulationEmulates the entire Playcademy backend API for users, games, inventory, etc.
Data PersistencePersists data to disk by default (survives restarts). Use --recreate-db to reset, --memory for in-memory only, or --db-path for custom location.
Mock DataAutomatically seeds with demo users and data on first run or when database is empty.
Game ContextWhen run via the Vite plugin, it automatically registers your current project as a mock game.

Server Endpoints

Once running, the sandbox provides several key endpoints:

  • Health Check: GET /health
  • API Base URL: /api

All production Playcademy APIs are available under the /api prefix, including /users, /games, /inventory, and more.

TimeBack Integration Testing

The sandbox supports TimeBack integration testing in three modes: mock (offline), local (Docker), and remote (real TimeBack API).

TimeBack Modes

ModeDescription
MockNo external dependencies, works offline. Uses generated mock data.
LocalConnect to a local TimeBack instance (Docker).
RemoteReal course IDs + API credentials. Tests against actual TimeBack API.
DisabledNo TimeBack config. TimeBack routes return errors.

CLI Options

When running the sandbox standalone, configure TimeBack via CLI flags:

# Local TimeBack (Docker)
bunx @playcademy/sandbox \
  --timeback-local \
  --timeback-oneroster-url http://localhost:8080/ims/oneroster \
  --timeback-caliper-url http://localhost:8080/caliper \
  --timeback-student-id your-student-id

# Remote TimeBack (requires env vars for credentials)
bunx @playcademy/sandbox \
  --timeback-student-id your-student-sourcedId
OptionDescription
--timeback-localEnable local TimeBack mode (Docker)
--timeback-oneroster-url <url>OneRoster API URL
--timeback-caliper-url <url>Caliper API URL
--timeback-course-id <id>Course ID for enrollments
--timeback-student-id <id>Student ID to link to demo user
--timeback-role <role>User role (student, parent, teacher, administrator)
--timeback-org-id <id>Organization ID
--timeback-org-name <name>Organization display name
--timeback-org-type <type>Organization type (school, district, department, etc.)

Environment Variables

For remote TimeBack (or as alternative to CLI flags):

# TimeBack API credentials (required for remote mode)
TIMEBACK_ONEROSTER_API_URL=https://api.timeback.org/ims/oneroster
TIMEBACK_API_CLIENT_ID=your-client-id
TIMEBACK_API_CLIENT_SECRET=your-client-secret
TIMEBACK_API_AUTH_URL=https://auth.timeback.org

# Link sandbox demo user to a real TimeBack student
SANDBOX_TIMEBACK_STUDENT_ID=your-student-sourcedId
SANDBOX_TIMEBACK_COURSE_ID=your-course-id

With Vite Plugin

When using @playcademy/vite-plugin, TimeBack enrollments and roles can be configured directly in vite.config.ts for a streamlined experience. See the Vite plugin documentation for details.

The Vite plugin also provides a t hotkey to cycle through TimeBack roles during development.

Runtime Testing

Once configured, your game can use the SDK to submit activities:

await client.timeback.endActivity({
    correctQuestions: 8,
    totalQuestions: 10,
})

Note on CLI Commands: The sandbox provides TimeBack management endpoints, but CLI commands (playcademy timeback setup, verify) require Better Auth authentication and won't work against the sandbox. These commands should be run against the platform.

Available TimeBack Endpoints

The sandbox provides these TimeBack endpoints (matching production platform):

Management:

  • GET /api/timeback/integrations/:gameId - Get integration details
  • POST /api/timeback/setup - Create TimeBack integration
  • DELETE /api/timeback/integrations/:gameId - Delete integration
  • GET /api/timeback/verify/:gameId - Verify OneRoster resources
  • GET /api/timeback/config/:gameId - Get TimeBack configuration

Runtime:

  • POST /api/timeback/end-activity - Submit activity results

XP Queries:

  • GET /api/timeback/xp/today - Get today's XP
  • PUT /api/timeback/xp/today - Update today's XP
  • GET /api/timeback/xp/total - Get total XP
  • GET /api/timeback/xp/history - Get XP history

These use the same api-core handlers as the production platform, ensuring consistency.

When properly configured, your game can call client.timeback.endActivity() during local development and see real events appear in your TimeBack dashboard.

Debugging

Health Check

You can verify the sandbox API server is running by sending a request to its /health endpoint.

curl -sSL http://localhost:4321/health | jq

This should return a JSON object with the status:

{
    "status": "ok",
    "timestamp": "2023-10-27T00:00:00.000Z"
}

Common Issues

IssueSolution
Sandbox doesn't startWhen using Vite, check that autoStart: true in vite.config.ts and @playcademy/vite-plugin is installed. Check terminal for errors.
API calls return HTMLThis indicates the API server isn't running or is on a different port. Check your sandbox url configuration.
Port conflictsThe sandbox uses port 4321 by default. If another process is using this port, stop it or use --port to specify a different port.
Data persists unexpectedlyBy default, the sandbox persists data to disk. Use --recreate-db to reset the database, or --memory for session-only data that doesn't persist.

Production vs. Sandbox

The SDK is designed to work seamlessly in both environments. PlaycademyClient.init() automatically detects whether it's running in a production environment or against a local sandbox.

FeatureSandboxProduction
DataMock, temporaryReal user data
AuthenticationMock tokensSecure authentication
PersistenceLocal disk (default) or session-only (--memory)Permanent cloud database

Contributing

The sandbox is a crucial development tool for the Playcademy ecosystem. For contribution guidelines, see the monorepo CONTRIBUTING.md.

Variables

version

const version: string = packageJson.version;

Defined in: index.ts:25

Functions

startServer()

function startServer(
   port, 
   project?, 
   options?): Promise<{
  gameId: undefined | string;
  main: ServerType;
  reset: () => Promise<void>;
  setRole: (role) => void;
  stop: () => Promise<void>;
  timebackMode: null | "local" | "remote" | "mock";
}>;

Defined in: index.ts:35

Start the sandbox server

Parameters

port

number

Port to listen on

project?

ProjectInfo

Optional project information for seeding

options?

Omit<ServerOptions, "port" | "project"> = {}

Server configuration options

Returns

Promise<{ gameId: undefined | string; main: ServerType; reset: () => Promise<void>; setRole: (role) => void; stop: () => Promise<void>; timebackMode: null | "local" | "remote" | "mock"; }>

Server instance with stop method

On this page