LIVE
Docs/SDK

Vibes SDK

Add persistent state, API access, and multiplayer to your AI-generated widgets. The SDK is automatically injected into every widget — no imports needed.

Building with AI?

Copy the SDK prompt into your AI chat so it knows how to use window.vibes automatically. When remixing, the AI will analyze your widget and suggest only the SDK features that are relevant for it — not a dump of everything.

Overview

The Vibes SDK is available as window.vibes in every widget. No imports needed — the platform injects it automatically. All methods are async and return Promises. Wrap calls in vibes.onReady() to ensure the SDK is initialized before use.

Getting Started

No installation. No imports. No setup.

The Vibes SDK is automatically injected into every widget. There is nothing to install, no package to import, and no script tag to add. Just use vibes.

When your widget loads inside It Just Vibes, the platform injects window.vibes before your code runs. You can use vibes directly — no setup, no configuration, no waiting for scripts to load.

Start here

widget.js
vibes.onReady(async () => {
  const saved = await vibes.load("myData");
  // your widget logic here
});

vibes.onReady() waits for the SDK to be available before running your code. Always wrap your startup logic in it — then everything in window.vibes is ready to use.

Persistent State

Save and load per-user data that persists across sessions and devices.

vibes.save

Persist a value under a key for the current user and widget. Prompts the user to sign in if not authenticated.

vibes.save(key: string, value: unknown): Promise<void>

Parameters

NameType Description
keystringreqStorage key — max 64 characters, alphanumeric, hyphens and underscores only.
valueunknownreqAny JSON-serializable value. Max 100KB per value, 500KB total per widget per user.

Returns

Promise<void>Resolves when the value is saved. Rejects on error.

Errors

Error CodeDescription
AUTH_REQUIREDUser dismissed the sign-in overlay without authenticating.
VALUE_TOO_LARGEValue exceeds the 100KB per-value limit.
QUOTA_EXCEEDEDUser has hit the 500KB per-widget or 5MB total limit.
KEY_LIMITWidget already has 100 distinct keys for this user.
INVALID_KEYKey contains illegal characters or exceeds 64 characters.
RATE_LIMITEDMore than 30 write operations per minute.
vibes.save('score', 1200)
  .then(() => console.log('Saved!'))

vibes.load

Retrieve a previously saved value for the current user and widget.

vibes.load(key: string): Promise<unknown | null>

Parameters

NameType Description
keystringreqThe key to load. Must match the key used when saving.

Returns

Promise<unknown | null>Resolves with the stored value, or null if not found or user is unauthenticated.

Errors

Error CodeDescription
RATE_LIMITEDMore than 60 read operations per minute.
const score = await vibes.load('score')
console.log('High score:', score ?? 0)

vibes.delete

Remove a saved key-value pair for the current user and widget.

vibes.delete(key: string): Promise<void>

Parameters

NameType Description
keystringreqThe key to delete.

Returns

Promise<void>Resolves when the key is deleted. Resolves even if the key did not exist.

Errors

Error CodeDescription
AUTH_REQUIREDUser is not authenticated.
RATE_LIMITEDCombined write operations exceed 30 per minute.
JavaScript
// Reset the user's progress
await vibes.delete('playerData')
console.log('Progress cleared')

vibes.listKeys

List all saved keys for the current user and widget.

vibes.listKeys(): Promise<string[]>

Returns

Promise<string[]>Array of key names. Empty array if unauthenticated or no keys saved.

Errors

Error CodeDescription
RATE_LIMITEDMore than 30 listKeys calls per minute.
JavaScript
const keys = await vibes.listKeys()
console.log('Saved keys:', keys)
// → ['score', 'settings', 'playerData']

Fetch Proxy

Make HTTP requests to external APIs through the Vibes proxy. Use vibes.fetch() for non-CORS APIs. Use direct fetch() for CORS-enabled APIs.

vibes.fetch

Make an HTTP request to an external API through the Vibes proxy. Use this for APIs without CORS headers. For APIs with permissive CORS (Access-Control-Allow-Origin: *), use direct fetch() instead. Returns a fetch-like response object.

vibes.fetch(url: string, opts?: FetchOptions): Promise<FetchResponse>

Parameters

NameType Description
urlstringreqFull URL including https://. Only HTTPS endpoints are allowed.
opts.methodstringoptHTTP method. Defaults to GET. Allowed: GET, POST, PUT, PATCH, DELETE.
opts.bodystringoptRequest body as a string. Use JSON.stringify() for JSON payloads.
opts.headersRecord<string, string>optHTTP request headers as key-value pairs.
opts.timeoutnumberoptRequest timeout in milliseconds. Defaults to 10000 (10 seconds).

Returns

Promise<FetchResponse>Resolves with a fetch-like response object: { status, ok, json(), text() }.

Errors

Error CodeDescription
RATE_LIMITEDWidget exceeded fetch rate limits.
BLOCKEDThe URL or domain is blocked by the Vibes proxy.
FETCH_ERRORThe request failed or timed out.
HTTP_NOT_HTTPSOnly HTTPS URLs are allowed.
const res = await vibes.fetch(
  'https://api.example.com/data'
)
const data = await res.json()
console.log(data)

Multiplayer

Real-time shared state for multi-user widgets, lobbies, and collaborative experiences.

vibes.shared.join

Connect to a multiplayer room. All users in the same room can share and receive state updates in real time. CAUTION: a hardcoded string room name (e.g. "lobby") creates a GLOBAL room shared by every viewer of this widget. For per-user or per-session isolation, derive the room name from a variable instead.

vibes.shared.join(room?: string, opts?: JoinOptions): Promise<void>

Parameters

NameType Description
roomstringoptRoom name. Defaults to "__default__". A hardcoded string means ALL viewers share the same room — every read and write affects every user. Use a variable (user ID, session token, etc.) for isolated state.
opts.persistentbooleanoptIf true, shared state is persisted to the database and restored when users rejoin.

Returns

Promise<void>Resolves when successfully connected to the room.

Errors

Error CodeDescription
NO_CLIENTRealtime is not available in this context.
JOIN_FAILEDCould not connect to the room.
// Join the default room (all viewers share this room — it is a GLOBAL room)
await vibes.shared.join()

// Listen for changes
vibes.shared.onChange('cursor', (pos) => {
  renderCursor(pos)
})

// Broadcast your cursor position
document.addEventListener('mousemove', (e) => {
  vibes.shared.set('cursor', { x: e.clientX, y: e.clientY })
})

// For per-user isolation, derive the room name from a variable:
// await vibes.shared.join(currentUserId)

vibes.shared.set

Broadcast a key-value update to all users in the room. Other users receive the update via onChange/onAny callbacks.

vibes.shared.set(key: string, value: unknown): Promise<void>

Parameters

NameType Description
keystringreqState key to update. Must call shared.join() first.
valueunknownreqAny JSON-serializable value. Delivered to all room subscribers.

Returns

Promise<void>Resolves when the broadcast is sent. The local onChange callback fires immediately.

Errors

Error CodeDescription
NOT_JOINEDMust call vibes.shared.join() before calling set().
AUTH_REQUIREDPersistent rooms require authentication.
JavaScript
await vibes.shared.join()

// Broadcast a vote
await vibes.shared.set('votes', currentVotes + 1)

vibes.shared.get

Read the current value of a shared state key. Returns the local cached value — no network request.

vibes.shared.get(key: string): unknown

Parameters

NameType Description
keystringreqThe shared state key to read.

Returns

unknownThe current cached value, or undefined if not set.
JavaScript
await vibes.shared.join()

// Read current shared state synchronously
const currentScore = vibes.shared.get('score') ?? 0
console.log('Current shared score:', currentScore)

vibes.shared.leave

Disconnect from a multiplayer room and clean up the subscription.

vibes.shared.leave(room?: string): void

Parameters

NameType Description
roomstringoptRoom name to leave. Defaults to the most recently joined room.

Returns

voidSynchronous — unsubscribes immediately.
JavaScript
// Clean up when done
vibes.shared.leave()

Lifecycle & Migration

Export, import, and migrate widget state for widget versioning and data portability.

vibes.exportState

Export all saved state for the current user and widget as a plain object. Useful for backup or debugging.

vibes.exportState(): Promise<Record<string, unknown>>

Returns

Promise<Record<string, unknown>>An object mapping all saved keys to their values. Empty object if unauthenticated.

Errors

Error CodeDescription
RATE_LIMITEDRate limit exceeded.
JavaScript
const backup = await vibes.exportState()
// → { score: 1200, settings: { theme: 'dark' }, ... }

// Download as JSON
const blob = new Blob(
  [JSON.stringify(backup, null, 2)],
  { type: 'application/json' }
)
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'my-widget-backup.json'
a.click()

vibes.importState

Restore saved state from a previously exported object. Existing keys are overwritten.

vibes.importState(data: Record<string, unknown>): Promise<void>

Parameters

NameType Description
dataRecord<string, unknown>reqObject mapping keys to values. Must pass the same validation as individual vibes.save calls.

Returns

Promise<void>Resolves when all keys are saved. Rejects if any value fails validation.

Errors

Error CodeDescription
AUTH_REQUIREDUser is not authenticated.
VALUE_TOO_LARGEOne or more values exceed 100KB.
QUOTA_EXCEEDEDImport would exceed the storage quota.
RATE_LIMITEDImport is limited to 5 per minute.
JavaScript
// Restore from backup
const backup = {
  score: 1200,
  settings: { theme: 'dark', volume: 0.8 },
  inventory: ['sword', 'shield'],
}

await vibes.importState(backup)
console.log('State restored!')

vibes.migrate

Run a migration function when the widget schema version changes. Use this when updating your widget to reshape saved state from older versions.

vibes.migrate(version: number, fn: (state: Record<string, unknown>) => Record<string, unknown>): Promise<void>

Parameters

NameType Description
versionnumberreqSchema version number. The migration runs only if the stored schema is older than this version.
fn(state: Record<string, unknown>) => Record<string, unknown>reqMigration function. Receives the current state, returns the new state.

Returns

Promise<void>Resolves when the migration is complete. Resolves immediately if the stored version is already up to date.

Errors

Error CodeDescription
AUTH_REQUIREDUser is not authenticated.
MIGRATION_FAILEDThe migration function threw an error.
// v1 → v2: rename 'score' to 'highScore'
await vibes.migrate(2, (state) => ({
  ...state,
  highScore: state.score,
  score: undefined,
}))

Limits & Quotas

The Vibes SDK enforces per-user limits to keep the platform fast and fair. These limits apply to all widgets equally.

Storage

LimitValue
Keys per widget per user100
Value size100KB
Total per widget per user500KB
Total per user5MB

Rate Limits

OperationLimit
Writes (save + delete)30/min
Reads (load)60/min
List keys30/min

Content Rules

RuleValue
Max key length64 chars
Allowed key charactersa-z A-Z 0-9 - _
Reserved prefixes__vibes__
Value typeJSON only

Error Handling

All SDK methods throw errors with a code property. Wrap async calls in try/catch blocks to handle errors gracefully.

Error CodeDescription
RATE_LIMITEDToo many operations per minute (HTTP 429). Back off and retry.
STORAGE_EXCEEDEDWidget storage quota exceeded. Delete old keys before saving.
KEY_LIMIT_REACHEDMaximum number of keys per widget per user reached.
INVALID_KEYKey contains illegal characters or exceeds 64 characters.
VALUE_TOO_LARGEIndividual value exceeds 100KB size limit.
WIDGET_UNAVAILABLEWidget context is not available or SDK failed to initialize.
AUTH_REQUIREDOperation requires user authentication. SDK prompts sign-in automatically.
NOT_JOINEDMust call vibes.shared.join() before using shared state methods.
BLOCKEDThe fetch proxy blocked the requested URL or domain.
FETCH_ERRORThe proxied HTTP request failed or timed out.

AI Prompt

Copy this prompt into your AI chat session to teach it about the Vibes SDK. Paste it before describing what you want to build and your AI will use window.vibes correctly from the start. When used via the Remix with AI panel, the prompt also instructs the AI to analyze your widget first, suggest a numbered list of relevant SDK features for your specific widget, and wait for your confirmation before writing any code.

Master SDK Prompt— covers the full Vibes SDK surface
## Vibes SDK Reference

You are building an HTML widget for It Just Vibes (itjustvibes.com). The Vibes SDK is auto-injected — do NOT add a script tag. Just use `window.vibes` (or just `vibes`).

### Setup

Wrap your startup code in `vibes.onReady`:

```js
vibes.onReady(async () => {
  const saved = await vibes.load("myKey");
  // your widget logic here
});
```

### State (Per-User Persistence)

Every user gets their own isolated state per widget. All methods return Promises.

| Method | Description |
|--------|-------------|
| `await vibes.save(key, value)` | Save JSON-serializable data |
| `await vibes.load(key)` | Load saved data (returns `null` if not found) |
| `await vibes.delete(key)` | Delete a saved key |
| `await vibes.listKeys()` | Get array of all saved key names |

**Key rules:**
- Keys are strings, max 64 characters, alphanumeric + dashes/underscores
- Values must be JSON-serializable (objects, arrays, strings, numbers, booleans)
- Max 100KB per value, 500KB per widget per user, 5MB per user total
- Max 100 keys per widget per user

### Fetch Proxy

Use direct `fetch()` for APIs with permissive CORS headers (`Access-Control-Allow-Origin: *`). Use `vibes.fetch` for APIs without CORS headers (the proxy handles cross-origin requests):

```js
const resp = await vibes.fetch("https://api.example.com/data", {
  method: "GET",         // GET, POST, PUT, DELETE
  headers: {},           // optional headers
  body: null,            // optional body (string)
  timeout: null          // optional timeout in ms
});
const data = await resp.json(); // or resp.text()
console.log(resp.status, resp.ok);
```

### Multiplayer (Shared State)

Real-time shared state across all users viewing the same widget.

```js
// Join a room (call once at startup)
await vibes.shared.join("lobby", { persistent: true });

// Set shared state (broadcasts to all users)
await vibes.shared.set("score", { player1: 10, player2: 7 });

// Read shared state (synchronous, returns last known value)
const score = vibes.shared.get("score");

// Listen for changes to a specific key
vibes.shared.onChange("score", (newValue) => {
  console.log("Score updated:", newValue);
});

// Listen for any shared state change
vibes.shared.onAny((key, value) => {
  console.log(key, "changed to", value);
});

// Get number of connected users
const count = await vibes.shared.getUserCount();

// Leave room
vibes.shared.leave();

// Clear all shared state for this room
await vibes.shared.clear();
```

**Shared state options:**
- `{ persistent: true }` — state survives page reloads (stored server-side)
- Default room name is "__default__" if omitted

### Agent-Accessible State (vibes.ai.*)

State written with the standard `vibes.save` / `vibes.load` methods is private to each user and is **NOT readable by AI agents**. To share state with an AI agent (via the MCP connector), use the `vibes.ai` sub-namespace:

| Method | Description |
|--------|-------------|
| `await vibes.ai.setState(key, value)` | Write agent-readable state. Key is stored internally as `ai/<key>`. |
| `await vibes.ai.getState(key)` | Read agent-readable state. Returns `null` if key absent. |
| `await vibes.ai.listKeys()` | List all agent-readable keys (without the `ai/` prefix). |

**Important rules:**
- Regular `vibes.save()` / `vibes.setState()` is **NOT agent-accessible** — use `vibes.ai.*` for state you want agents to read.
- The widget **owner** must enable agent access in **Manage → Agent tab** before any agent can read or write `vibes.ai.*` state.
- Keys are auto-prefixed to `ai/` internally; you supply just the short key (e.g. `'context'`).

```js
vibes.onReady(async () => {
  // Write state an AI agent can later read
  await vibes.ai.setState('context', { currentLevel: 3, score: 1500 });

  // Read it back (same auto-prefix applies)
  const ctx = await vibes.ai.getState('context');

  // List all agent-accessible keys for this widget
  const keys = await vibes.ai.listKeys(); // e.g. ['context']
});
```

### Rules

1. **Do NOT use localStorage, sessionStorage, or window.storage** — they are blocked or undefined in the sandbox. Use `vibes.save`/`vibes.load` instead.
2. **Do NOT add a script tag to import the SDK** — it is auto-injected.
3. **Wrap startup code in `vibes.onReady()`** — the SDK may not be ready immediately.
4. **Await all SDK calls** — every method (except `vibes.shared.get`) returns a Promise.
5. **Do NOT import external JS libraries via script tags** — they will be blocked. Include library code inline or use a CDN link in a `<link>` tag for CSS only.
6. **Keep total code under 80KB** — that is the default widget size limit (your account limit may be higher).

### Rate Limits

| Operation | Limit |
|-----------|-------|
| Writes (save + delete combined) | 30/min |
| Reads (load) | 60/min |
| List keys | 30/min |
| Fetch proxy | Rate limited per widget |

### Error Handling

All SDK methods can reject. Wrap in try/catch:

```js
try {
  await vibes.save("key", value);
} catch (err) {
  console.error("Save failed:", err.message);
}
```

Fetch proxy errors include `err.code`: `"RATE_LIMITED"`, `"BLOCKED"`, `"FETCH_ERROR"`.

### Data Export

Export rows of data as CSV or Excel directly from your widget.

```js
// Register dataset for the platform's "Export data" button
// AND optionally trigger an immediate download
vibes.exportData(rows, { filename: 'results.csv' })

// rows: Array of arrays (each inner array is one row; first row = headers)
// options.filename: sets the download filename and format (csv or xlsx)
// options.directDownload: false to only register without downloading (default: true)
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `filename` | string | `'data.csv'` | Download filename; extension determines format (`.csv` or `.xlsx`) |
| `directDownload` | boolean | `true` | Trigger immediate browser download in addition to registering the dataset |

**Notes:**
- First call registers the dataset so the widget chrome shows an "Export data" button
- Use `.csv` extension for CSV, `.xlsx` for Excel
- CSV injection safety is automatic (formula-starting cells prefixed with `'`)
- Data stays in the browser — no server round-trip

Use-case variants

Manage Your Data

Widgets can save data on your behalf. You own that data and can view, export, or delete it at any time.

My Stuff

See what widgets have saved about you. Browse by widget, download a backup, or clear your data.

/my-stuff

State is isolated per widget. Deleting data for one widget does not affect others. You can also export your data as JSON for portability.

FEEDDISCOVER
Start typing to search