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
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
| Name | Type | Description | |
|---|---|---|---|
key | string | req | Storage key — max 64 characters, alphanumeric, hyphens and underscores only. |
value | unknown | req | Any 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 Code | Description |
|---|---|
AUTH_REQUIRED | User dismissed the sign-in overlay without authenticating. |
VALUE_TOO_LARGE | Value exceeds the 100KB per-value limit. |
QUOTA_EXCEEDED | User has hit the 500KB per-widget or 5MB total limit. |
KEY_LIMIT | Widget already has 100 distinct keys for this user. |
INVALID_KEY | Key contains illegal characters or exceeds 64 characters. |
RATE_LIMITED | More 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
| Name | Type | Description | |
|---|---|---|---|
key | string | req | The 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 Code | Description |
|---|---|
RATE_LIMITED | More 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
| Name | Type | Description | |
|---|---|---|---|
key | string | req | The key to delete. |
Returns
Promise<void>— Resolves when the key is deleted. Resolves even if the key did not exist.Errors
| Error Code | Description |
|---|---|
AUTH_REQUIRED | User is not authenticated. |
RATE_LIMITED | Combined write operations exceed 30 per minute. |
// 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 Code | Description |
|---|---|
RATE_LIMITED | More than 30 listKeys calls per minute. |
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
| Name | Type | Description | |
|---|---|---|---|
url | string | req | Full URL including https://. Only HTTPS endpoints are allowed. |
opts.method | string | opt | HTTP method. Defaults to GET. Allowed: GET, POST, PUT, PATCH, DELETE. |
opts.body | string | opt | Request body as a string. Use JSON.stringify() for JSON payloads. |
opts.headers | Record<string, string> | opt | HTTP request headers as key-value pairs. |
opts.timeout | number | opt | Request timeout in milliseconds. Defaults to 10000 (10 seconds). |
Returns
Promise<FetchResponse>— Resolves with a fetch-like response object: { status, ok, json(), text() }.Errors
| Error Code | Description |
|---|---|
RATE_LIMITED | Widget exceeded fetch rate limits. |
BLOCKED | The URL or domain is blocked by the Vibes proxy. |
FETCH_ERROR | The request failed or timed out. |
HTTP_NOT_HTTPS | Only HTTPS URLs are allowed. |
const res = await vibes.fetch(
'https://api.example.com/data'
)
const data = await res.json()
console.log(data)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 Code | Description |
|---|---|
RATE_LIMITED | Rate limit exceeded. |
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
| Name | Type | Description | |
|---|---|---|---|
data | Record<string, unknown> | req | Object 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 Code | Description |
|---|---|
AUTH_REQUIRED | User is not authenticated. |
VALUE_TOO_LARGE | One or more values exceed 100KB. |
QUOTA_EXCEEDED | Import would exceed the storage quota. |
RATE_LIMITED | Import is limited to 5 per minute. |
// 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
| Name | Type | Description | |
|---|---|---|---|
version | number | req | Schema version number. The migration runs only if the stored schema is older than this version. |
fn | (state: Record<string, unknown>) => Record<string, unknown> | req | Migration 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 Code | Description |
|---|---|
AUTH_REQUIRED | User is not authenticated. |
MIGRATION_FAILED | The 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
| Limit | Value |
|---|---|
| Keys per widget per user | 100 |
| Value size | 100KB |
| Total per widget per user | 500KB |
| Total per user | 5MB |
Rate Limits
| Operation | Limit |
|---|---|
| Writes (save + delete) | 30/min |
| Reads (load) | 60/min |
| List keys | 30/min |
Content Rules
| Rule | Value |
|---|---|
| Max key length | 64 chars |
| Allowed key characters | a-z A-Z 0-9 - _ |
| Reserved prefixes | __vibes__ |
| Value type | JSON only |
Error Handling
All SDK methods throw errors with a code property. Wrap async calls in try/catch blocks to handle errors gracefully.
| Error Code | Description |
|---|---|
RATE_LIMITED | Too many operations per minute (HTTP 429). Back off and retry. |
STORAGE_EXCEEDED | Widget storage quota exceeded. Delete old keys before saving. |
KEY_LIMIT_REACHED | Maximum number of keys per widget per user reached. |
INVALID_KEY | Key contains illegal characters or exceeds 64 characters. |
VALUE_TOO_LARGE | Individual value exceeds 100KB size limit. |
WIDGET_UNAVAILABLE | Widget context is not available or SDK failed to initialize. |
AUTH_REQUIRED | Operation requires user authentication. SDK prompts sign-in automatically. |
NOT_JOINED | Must call vibes.shared.join() before using shared state methods. |
BLOCKED | The fetch proxy blocked the requested URL or domain. |
FETCH_ERROR | The 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.
## 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.