# Puter User Guide

Puter is an open-source **internet operating system** — a complete desktop environment that runs in your web browser. It gives every user a personal cloud with files, apps, and settings that follow them to any device, and it gives developers a single JavaScript library (`puter.js`) that adds cloud storage, a key-value database, authentication, web hosting, serverless functions, and frontier AI models to any website with **no backend, no API keys, and no server bills**.

This guide is for the people who *use* Puter: end users of the desktop at [puter.com](https://puter.com), developers who build apps on top of `puter.js`, operators who self-host the platform, and anyone driving Puter from the `puter` command-line tool. It covers everything you can do with Puter, exactly as the product behaves at the documented commit — every SDK call, every CLI command, every install path, with copy-paste-runnable examples.

> [!NOTE]
> Puter has four faces: the **desktop** (the GUI at puter.com or your self-hosted instance), the **`puter.js` SDK** (the library third-party apps use), the **`puter` CLI** (deploy sites and workers from a terminal), and the **self-hostable backend** (run the whole thing yourself). This guide walks through all four. If you only want to add AI or cloud features to a website, jump straight to [Building with puter.js](#5-building-with-puterjs).

---

## 1. What Puter Is

Puter is several things at once, and which one matters depends on who you are.

| If you are… | Puter is… | Start at |
|-------------|-----------|----------|
| A person who wants a free personal cloud | A browser-based desktop with files, apps, and settings | [Using the Desktop](#4-using-the-puter-desktop) |
| A web developer | A zero-backend SDK for storage, auth, hosting, and AI | [Building with puter.js](#5-building-with-puterjs) |
| Someone who wants AI in their app without API keys | A gateway to GPT, Claude, Gemini, DALL·E, and more — paid by the *user*, not you | [The User-Pays Model](#6-the-user-pays-model) |
| A self-hoster or homelab operator | A full open-source stack you run with one Docker command | [Self-Hosting Puter](#9-self-hosting-puter) |
| A terminal user | A CLI to publish static sites and deploy serverless workers | [The puter CLI](#7-the-puter-cli) |

### The core idea

The web has always had a backend problem: to ship anything beyond a static page you need a server, a database, authentication, file storage, and — increasingly — paid AI APIs. Puter collapses all of that into one script tag. You write front-end code that calls `puter.fs.write(...)`, `puter.kv.set(...)`, `puter.ai.chat(...)`, or `puter.hosting.create(...)`, and Puter's cloud handles the storage, identity, and billing.

The model that makes this work is **user-pays**: when your app calls a paid resource (AI, storage, hosting), the cost is billed to the *end user's* Puter account, not to you. You ship apps with AI and cloud features and pay nothing for usage. See [The User-Pays Model](#6-the-user-pays-model).

### What's in the box

- **Desktop GUI** — a windowing desktop environment with a file manager, apps, settings, themes, and a taskbar, served as a single web app.
- **`puter.js`** — the official client SDK (npm package `@heyputer/puter.js`, or `<script src="https://js.puter.com/v2/">`). Modules: `ai`, `fs`, `kv`, `hosting`, `auth`, `apps`, `ui`, `workers`, `net`, `peer`, `perms`, plus utilities.
- **`puter` CLI** — the `@heyputer/cli` npm package; deploy static sites and serverless workers from a terminal.
- **Serverless Workers** — deploy a single JavaScript file that becomes an HTTP endpoint with a built-in router and access to your Puter resources.
- **Backend** — a Node.js + Express server you can self-host, with pluggable databases (SQLite, MySQL/MariaDB, PostgreSQL), object storage (S3-compatible), and AI providers.

> [!TIP]
> Everything in Puter is free and open-source under the **AGPL-3.0** license (the `puter` CLI is MIT). You can use the hosted service at puter.com or run your own instance — the SDK and desktop behave the same either way.

---

## 2. Quick Start

There are three independent ways to start with Puter. Pick the one that matches your goal.

=== "Use the hosted desktop"

    Open [puter.com](https://puter.com) in any modern browser, create a free account, and you have a personal cloud desktop. Nothing to install.

=== "Add AI/cloud to a website"

    Drop one script tag into any HTML page and start calling the SDK — no signup, no API key:

    ```html
    <!DOCTYPE html>
    <html>
    <head>
        <script src="https://js.puter.com/v2/"></script>
    </head>
    <body>
        <script>
            puter.ai.chat("Why is the sky blue?").then((response) => {
                puter.print(response);
            });
        </script>
    </body>
    </html>
    ```

    Open the file through a local web server (not `file://` — see [Troubleshooting](#10-troubleshooting)) and the answer prints to the page. The first paid call prompts the visitor to sign in to Puter so the cost is billed to them.

=== "Self-host the whole platform"

    With Docker installed, one command brings up the full stack:

    ```bash
    curl -fsSL https://raw.githubusercontent.com/HeyPuter/puter/main/install.sh | sh
    ```

    On Windows (PowerShell):

    ```powershell
    irm https://raw.githubusercontent.com/HeyPuter/puter/main/install.ps1 | iex
    ```

    Then open `http://puter.localhost` once the container reports healthy. See [Self-Hosting Puter](#9-self-hosting-puter) for details.

### Your first puter.js app in three lines

Every `puter.js` call returns a promise. The pattern is always the same — load the SDK, call a module method, handle the result:

```html
<script src="https://js.puter.com/v2/"></script>
<script>
    // Cloud storage: write a file to the user's Puter drive
    await puter.fs.write('hello.txt', 'Hello, Puter!');

    // Key-value store: save a setting
    await puter.kv.set('theme', 'dark');

    // AI: ask a question
    const reply = await puter.ai.chat('Give me a fun fact.');
    puter.print(reply);
</script>
```

```text
A fun fact prints directly into the page body.
```

> [!IMPORTANT]
> `puter.js` must be served over `http://` or `https://`. Opening an HTML file directly from disk (`file://`) is unsupported — Puter shows an "Unsupported Protocol" warning. Use any static server: `npx http-server`, VS Code Live Server, or `python -m http.server`.

---

## 3. Installation & Loading the SDK

### Loading puter.js in the browser

The simplest and recommended method is the CDN script tag, which always serves the current v2 build:

```html
<script src="https://js.puter.com/v2/"></script>
```

After it loads, a global `puter` object is available everywhere — `window.puter` — with every module attached (`puter.ai`, `puter.fs`, and so on).

### Installing from npm

For bundled apps (webpack, Vite, etc.) or Node.js usage, install the package:

```bash
npm install @heyputer/puter.js
```

```javascript
import puter from '@heyputer/puter.js';

const reply = await puter.ai.chat('Hello from a bundled app!');
console.log(reply);
```

> [!NOTE]
> The package exposes full TypeScript types (`index.d.ts`). Importing it in a TypeScript project gives you typed signatures for every module — `ChatOptions`, `WriteOptions`, `FilePickerOptions`, and dozens more.

### Where the SDK runs

`puter.js` detects its environment automatically and adapts. You don't configure this — it's listed here so you understand its behavior:

| Environment | When it applies | Notes |
|-------------|-----------------|-------|
| `gui` | Loaded inside the Puter desktop itself | Uses the desktop's auth token directly |
| `app` | Your app running in a window/iframe inside Puter | Receives auth + app identity from the host |
| `web` | A third-party website (the common case) | Shows a sign-in popup on the first paid call |
| `nodejs` | Node.js script | Polyfills browser globals; auth via stored token |
| `web-worker` / `service-worker` | Inside a Web/Service Worker | Used by Puter Workers |

> [!TIP]
> Read the current environment any time with `puter.env`. It returns one of the strings above.

### Installing the CLI

The `puter` command-line tool is a separate npm package:

```bash
npm install -g @heyputer/cli
```

```text
Adds the `puter` command to your PATH.
```

Requires Node.js 18 or newer. See [The puter CLI](#7-the-puter-cli).

---

## 4. Using the Puter Desktop

The desktop is the GUI you see at [puter.com](https://puter.com) (or at your self-hosted domain). It is a full windowing environment built as a single web application.

### Signing in

Visit the site, click **Sign Up**, and create an account with a username and password. On a self-hosted instance the first-boot **admin** account is created automatically and its password is printed once to the container log (see [Self-Hosting](#9-self-hosting-puter)).

> [!NOTE]
> A self-hosted instance prints the admin credentials exactly once at first boot:
> ```text
> ************************************************************
> * Your default login credentials are:
> * Username: admin
> * Password: <hex string>
> ************************************************************
> ```
> Save that password — it is not shown again.

### Your home folders

Every account gets a personal file tree. The standard folders are:

| Folder | Path | Purpose |
|--------|------|---------|
| Home | `/{username}` or `~` | Your root directory |
| Desktop | `~/Desktop` | Files and shortcuts shown on the desktop |
| Documents | `~/Documents` | General document storage |
| Public | `~/Public` | Files you intend to share |
| AppData | `~/AppData/{app-id}` | Per-app private storage (apps write here automatically) |
| Sites | `~/Sites` | Static websites you've published |
| Workers | `~/Workers` | Serverless worker source files |

The `~` shorthand expands to your home directory in every SDK path. AppData is sandboxed per app — an app you run only sees its own `~/AppData/{its-id}` folder unless you grant broader access.

### Working with windows and apps

The desktop supports multiple resizable, draggable windows; a taskbar; right-click context menus; and a settings panel for themes, language, and account management. Apps launched inside Puter run in sandboxed iframes and talk to the desktop through `puter.js`.

### Installing and publishing apps

Apps in Puter are web apps registered as desktop entries. You can browse and install community apps from the [Puter App Store](https://apps.puter.com/), and you can publish your own — any site built with `puter.js` can be registered as a Puter app. See [Building and Publishing Apps](#54-apps--puterapps) for the SDK side.

---

## 5. Building with puter.js

This is the heart of Puter for developers. `puter.js` exposes its capabilities as modules hanging off the global `puter` object. Every method returns a promise, so you can `await` it or use `.then()`.

The modules are:

| Module | Object | What it does |
|--------|--------|--------------|
| AI | `puter.ai` | Chat (GPT/Claude/Gemini/…), image gen, TTS, transcription, OCR, video gen |
| Cloud Storage | `puter.fs` | Read/write/move/copy/delete files and directories |
| Key-Value Store | `puter.kv` | Per-app NoSQL key-value database |
| Hosting | `puter.hosting` | Publish static sites to `*.puter.site` subdomains |
| Auth | `puter.auth` | Sign in, sign out, identify the user, read usage |
| Apps | `puter.apps` | Create and manage registered Puter apps |
| UI | `puter.ui` | Dialogs, windows, pickers, menus, notifications |
| Workers | `puter.workers` | Deploy and manage serverless functions |
| Networking | `puter.net` | Raw TCP/TLS sockets and a server-side `fetch` |
| Peer | `puter.peer` | Peer-to-peer (WebRTC) data channels |
| Perms | `puter.perms` | Request access to the user's files and resources |
| Utilities | `puter` | `print()`, `randName()`, `env`, `appID` |

### 5.1 AI — `puter.ai`

The AI module is the most popular reason to use Puter: it gives any website access to frontier models with no API keys and no per-token bill to you.

#### Chat — `puter.ai.chat()`

The flexible entry point for text and vision models. Call shapes:

```javascript
// Simplest: a single prompt string
const answer = await puter.ai.chat('What is the capital of France?');
puter.print(answer);

// Choose a model
await puter.ai.chat('Explain quantum tunneling', {
    model: 'claude-sonnet-4-6',
});

// Full message array (system + user turns)
await puter.ai.chat([
    { role: 'system', content: 'You are a terse assistant.' },
    { role: 'user', content: 'Summarize the French Revolution.' },
]);

// Vision: pass an image URL as the second argument
await puter.ai.chat("What's in this picture?", 'https://example.com/cat.jpg');
```

```text
Paris (and so on, depending on the prompt).
```

**Streaming** — set `stream: true` to receive the response incrementally as it's generated:

```javascript
const response = await puter.ai.chat('Write a short poem about the sea.', {
    stream: true,
});
for await (const part of response) {
    puter.print(part?.text);
}
```

**Function calling** — pass a `tools` array describing functions the model can call, then execute the requested tool and feed the result back. Puter supports both single and streaming function-calling flows.

> [!TIP]
> Puter routes to many providers behind `chat()`: OpenAI (GPT family, o3-mini), Anthropic (Claude Sonnet), Google (Gemini), DeepSeek, xAI (Grok), Mistral, Groq, Together, OpenRouter, and more. Use `puter.ai.listModels()` to see exactly which model IDs your instance exposes — never hard-code a list.

**Discovering models and providers:**

```javascript
const models = await puter.ai.listModels();          // all chat models, grouped by provider
const providers = await puter.ai.listModelProviders(); // provider names only
```

#### Image generation — `puter.ai.txt2img()`

Generate an image from a text prompt. Returns an image element/URL you can display:

```javascript
const image = await puter.ai.txt2img('A serene mountain lake at sunrise');
document.body.appendChild(image);
```

The optional second argument carries options (model, size, image-to-image input). For multi-turn image editing, keep the conversation going with the same model.

#### Text-to-speech — `puter.ai.txt2speech()`

Convert text to spoken audio. Supports multiple engines and voices:

```javascript
const audio = await puter.ai.txt2speech('Hello, world!');
audio.play();

// With options: pick a provider/voice
await puter.ai.txt2speech('Bonjour le monde', {
    engine: 'elevenlabs',   // or 'openai', 'gemini', 'xai', 'aws'
});
```

Discover what's available:

```javascript
const engines = await puter.ai.txt2speech.listEngines(); // available TTS engines/models
const voices  = await puter.ai.txt2speech.listVoices();  // available voices per provider
```

#### Transcription — `puter.ai.speech2txt()`

Turn an audio recording into text. Returns a `Speech2TxtResult`:

```javascript
const result = await puter.ai.speech2txt(audioFileOrBlob);
puter.print(result.text);
```

#### Voice changer — `puter.ai.speech2speech()`

Transform an audio clip into a different voice (provided by ElevenLabs-class engines), accepting either a URL or a stored file.

#### OCR — `puter.ai.img2txt()`

Extract text from an image:

```javascript
const text = await puter.ai.img2txt('https://example.com/receipt.png');
puter.print(text);
```

#### Video generation — `puter.ai.txt2vid()`

Generate video from a text prompt, with an options form for model and parameters:

```javascript
const video = await puter.ai.txt2vid('A timelapse of clouds over a city');
document.body.appendChild(video);
```

> [!CAUTION]
> AI calls are paid resources. In the common `web` environment, the first AI call prompts the visitor to sign in to Puter; the cost is then billed to *their* account. Your app pays nothing. See [The User-Pays Model](#6-the-user-pays-model).

#### AI quick reference

| Method | Purpose |
|--------|---------|
| `puter.ai.chat(prompt, options?)` | Text/vision chat completion (streaming optional) |
| `puter.ai.chat(messages, options?)` | Multi-turn chat with a message array |
| `puter.ai.listModels()` | List available chat models by provider |
| `puter.ai.listModelProviders()` | List available providers |
| `puter.ai.txt2img(prompt, options?)` | Generate an image from text |
| `puter.ai.txt2speech(text, options?)` | Generate speech audio from text |
| `puter.ai.txt2speech.listEngines()` | List TTS engines |
| `puter.ai.txt2speech.listVoices()` | List TTS voices |
| `puter.ai.txt2vid(prompt, options?)` | Generate video from text |
| `puter.ai.img2txt(image)` | Extract text from an image (OCR) |
| `puter.ai.speech2txt(audio)` | Transcribe audio to text |
| `puter.ai.speech2speech(options)` | Change the voice of an audio clip |

### 5.2 Cloud Storage — `puter.fs`

A full cloud filesystem scoped to the signed-in user. Paths use `~` for the home directory and forward slashes.

```javascript
// Write a file (creates or overwrites)
await puter.fs.write('~/Documents/notes.txt', 'My notes');

// Read a file (returns a Blob)
const blob = await puter.fs.read('~/Documents/notes.txt');
const text = await blob.text();
puter.print(text);

// Make a directory
await puter.fs.mkdir('~/Projects');

// List a directory
const items = await puter.fs.readdir('~/Documents');
for (const item of items) puter.print(item.name);

// Get metadata about a file or folder
const info = await puter.fs.stat('~/Documents/notes.txt');

// Rename, copy, move, delete
await puter.fs.rename('~/Documents/notes.txt', 'old-notes.txt');
await puter.fs.copy('~/Documents/old-notes.txt', '~/Projects/');
await puter.fs.move('~/Projects/old-notes.txt', '~/Documents/');
await puter.fs.delete('~/Documents/old-notes.txt');
```

**Useful options:**

- `createMissingParents: true` — create intermediate folders (`mkdir -p` semantics) for `write`, `mkdir`, `move`, `upload`.
- `dedupeName: true` — auto-number the new item (`file`, `file (1)`, …) instead of overwriting.
- `overwrite: true` — replace an existing file.

```javascript
// Write into a directory that doesn't exist yet
await puter.fs.write('~/a/b/c/deep.txt', 'content', { createMissingParents: true });

// Create a uniquely-named folder every time
const folder = await puter.fs.mkdir('~/Uploads/batch', { dedupeName: true });
```

**Uploading browser files** — `puter.fs.upload()` accepts `File`/`Blob` objects, e.g. from an `<input type="file">`:

```javascript
const input = document.querySelector('input[type=file]');
await puter.fs.upload(input.files, '~/Uploads/');
```

**Sharing / public URLs** — `puter.fs.getReadURL()` returns a signed URL you can hand out to read a file without authentication:

```javascript
const url = await puter.fs.getReadURL('~/Public/photo.png');
```

> [!WARNING]
> Known issue: at the documented commit, `puter.fs.mkdir(..., { dedupeName: true })` does not always auto-number when the target already exists ([#3201](https://github.com/HeyPuter/puter/issues/3201)). If you rely on unique folder names, verify the returned path and retry with a different name if needed.

#### FS quick reference

| Method | Purpose |
|--------|---------|
| `write(path, data, options?)` | Create or overwrite a file |
| `read(path)` | Read a file as a Blob |
| `mkdir(path, options?)` | Create a directory |
| `readdir(path)` | List directory contents (array of `FSItem`) |
| `rename(path, newName)` | Rename in place |
| `copy(source, dest, options?)` | Copy a file/dir |
| `move(source, dest, options?)` | Move a file/dir |
| `stat(path)` | Get file/dir metadata (`FSItem`) |
| `delete(path, options?)` | Delete a file/dir |
| `upload(files, dest, options?)` | Upload `File`/`Blob` objects |
| `getReadURL(path)` | Get a signed, shareable read URL |

### 5.3 Key-Value Store — `puter.kv`

A per-app NoSQL key-value database — ideal for app settings, user data, and small documents. Values can be strings, numbers, or JSON-serializable objects.

```javascript
await puter.kv.set('username', 'alice');
const name = await puter.kv.get('username');     // 'alice'

await puter.kv.incr('visits');                   // atomic +1
await puter.kv.decr('credits', 5);               // atomic -5

await puter.kv.del('username');                  // delete one key
const keys = await puter.kv.list();              // list keys
await puter.kv.flush();                           // delete everything (this app)
```

**Expiration (TTL):**

```javascript
await puter.kv.set('otp', '123456');
await puter.kv.expire('otp', 300);               // expires in 300 seconds
await puter.kv.expireAt('otp', 1893456000);      // expires at a UNIX timestamp
```

**Nested updates** — `incr`/`decr` can target a path inside an object value, and `update`/`remove` edit nested fields:

```javascript
await puter.kv.set('player', { score: 0, lives: 3 });
await puter.kv.incr('player.score', 10);
await puter.kv.update('player.lives', 2);
await puter.kv.remove('player.score');
```

**Listing with patterns and pagination** — design your keys for prefix queries (e.g. `user:alice:post:001`). `list()` returns keys in lexicographic order and supports a cursor + limit for pagination. Zero-pad numeric keys so they sort correctly.

> [!TIP]
> The store enforces limits exposed as constants: `puter.kv.MAX_KEY_SIZE` and `puter.kv.MAX_VALUE_SIZE`. Read them rather than guessing.

#### KV quick reference

| Method | Purpose |
|--------|---------|
| `set(key, value)` | Store a value |
| `get(key)` | Retrieve a value |
| `incr(key, amount?)` / `decr(key, amount?)` | Atomic increment/decrement (supports nested paths) |
| `add(key, value)` | Add a value to a key |
| `update(path, value)` | Update a nested path |
| `remove(path)` | Remove a nested path |
| `del(key)` | Delete a key |
| `list(options?)` | List keys (supports pagination) |
| `flush()` | Clear all keys for this app |
| `expire(key, seconds)` / `expireAt(key, ts)` | Set TTL / absolute expiry |
| `MAX_KEY_SIZE` / `MAX_VALUE_SIZE` | Size limits (constants) |

### 5.4 Apps — `puter.apps`

Register and manage Puter apps (desktop-OS entries that point at a hosted URL):

```javascript
// Register an app pointing at a URL
const app = await puter.apps.create('my-app', 'https://example.com');

// List your apps
const apps = await puter.apps.list();

// Get, update, delete
await puter.apps.get('my-app');
await puter.apps.update('my-app', { title: 'My Great App' });
await puter.apps.delete('my-app');
```

This is how apps end up on the desktop and in the App Store. Typically you publish a static site with `puter.hosting`, then register it as an app with `puter.apps.create()`.

### 5.5 Hosting — `puter.hosting`

Publish a directory of files to a public `*.puter.site` subdomain — instant static web hosting:

```javascript
// Create a directory of files, then host it
await puter.fs.mkdir('~/Sites/my-site', { createMissingParents: true });
await puter.fs.write('~/Sites/my-site/index.html', '<h1>Hello world!</h1>');

// Publish it
const site = await puter.hosting.create('my-unique-subdomain', '~/Sites/my-site');
// → live at https://my-unique-subdomain.puter.site

// Manage sites
const sites = await puter.hosting.list();
await puter.hosting.get('my-unique-subdomain');
await puter.hosting.update('my-unique-subdomain', '~/Sites/new-folder');
await puter.hosting.delete('my-unique-subdomain');
```

> [!NOTE]
> The CLI wraps this whole flow into a single `puter site deploy` command — see [The puter CLI](#7-the-puter-cli).

### 5.6 Auth — `puter.auth`

Identify and manage the signed-in user:

```javascript
// Trigger the sign-in popup (web environment)
await puter.auth.signIn();

// Check status and identity
const signedIn = puter.auth.isSignedIn();
const user = await puter.auth.getUser();   // { username, ... }

// Sign out
await puter.auth.signOut();

// Usage reporting
const usage = await puter.auth.getMonthlyUsage();
const detail = await puter.auth.getDetailedAppUsage();
```

In the `web` environment you rarely need to call `signIn()` explicitly — the first paid call (AI, FS, KV) automatically surfaces the consent popup. Use `getUser()` to greet the user or branch on identity.

### 5.7 UI — `puter.ui`

Dialogs, windows, pickers, and desktop integration. The most-used calls:

```javascript
// Dialogs
await puter.ui.alert('Saved!');
const name = await puter.ui.prompt('What is your name?');
puter.ui.notify('Upload complete');           // desktop notification

// Pickers
const file = await puter.ui.showOpenFilePicker();
const target = await puter.ui.showSaveFilePicker();
const dir = await puter.ui.showDirectoryPicker();
const color = await puter.ui.showColorPicker();
const font = await puter.ui.showFontPicker();

// Spinners
puter.ui.showSpinner();
puter.ui.hideSpinner();

// Context menus and menubars
puter.ui.contextMenu({ items: [/* ... */] });
puter.ui.setMenubar({ items: [/* ... */] });
```

When your app runs *inside* the Puter desktop, the UI module also controls your window — `setWindowTitle()`, `setWindowSize()`, `setWindowPosition()`, `showWindow()`/`hideWindow()`, plus lifecycle events:

```javascript
puter.ui.onWindowClose(() => { /* save before close */ });
puter.ui.onLaunchedWithItems((items) => { /* opened with files */ });
```

`puter.ui.launchApp('editor')` opens another Puter app, and `puter.ui.socialShare(...)` triggers a share sheet.

> [!NOTE]
> Known behavior at the documented commit: `showOpenFilePicker`/`showSaveFilePicker` may not behave as expected in the standalone `web` environment ([#2897](https://github.com/HeyPuter/puter/issues/2897)). They are most reliable for apps running inside the Puter desktop.

#### UI quick reference (selected)

| Method | Purpose |
|--------|---------|
| `alert(message, buttons?)` | Modal alert dialog |
| `prompt(message)` | Text-input dialog |
| `notify(message)` | Desktop notification |
| `contextMenu(options)` | Right-click menu |
| `setMenubar(options)` | App menubar |
| `showOpenFilePicker()` / `showSaveFilePicker()` | File pickers |
| `showDirectoryPicker()` | Folder picker |
| `showColorPicker()` / `showFontPicker()` | Color / font pickers |
| `showSpinner()` / `hideSpinner()` | Loading overlay |
| `createWindow(options)` | New window |
| `setWindowTitle/Size/Position(...)` | Window control |
| `launchApp(name, options?)` | Launch another Puter app |
| `socialShare(...)` | Share sheet |
| `authenticateWithPuter()` | Drive the sign-in popup directly |

### 5.8 Serverless Workers — `puter.workers`

Deploy a single JavaScript file as an HTTP endpoint. See the full Workers programming model in [Serverless Workers](#8-serverless-workers); the SDK side manages deployments:

```javascript
// Deploy a worker from a file already in your Puter drive
const deployment = await puter.workers.create('my-api', '~/Workers/my-api.js');
// → reachable at https://my-api.puter.work

const workers = await puter.workers.list();
await puter.workers.get('my-api');
await puter.workers.delete('my-api');

// Call an authenticated worker
const result = await puter.workers.exec('https://my-api.puter.work/endpoint');
```

### 5.9 Networking — `puter.net`

Low-level network primitives unavailable to ordinary browser JavaScript:

```javascript
// Raw TCP socket
const socket = new puter.net.Socket('example.com', 8080);

// TLS socket
const tls = new puter.net.tls.TLSSocket('example.com', 443);

// Server-side fetch (bypasses browser CORS)
const res = await puter.net.fetch('https://api.example.com/data');
```

These route through Puter's relay so a browser app can open real TCP/TLS connections and fetch cross-origin resources.

### 5.10 Peer — `puter.peer`

Peer-to-peer (WebRTC) data channels for direct browser-to-browser communication:

```javascript
const server = await puter.peer.serve();   // become a peer others can connect to
const conn = await puter.peer.connect(peerId);
await puter.peer.ensureTurnRelays();        // ensure TURN relays for NAT traversal
```

### 5.11 Permissions — `puter.perms`

When your app needs broader access than its own sandbox, request it explicitly. The user is prompted to grant or deny:

```javascript
await puter.perms.requestReadDocuments();
await puter.perms.requestWritePictures();
await puter.perms.requestEmail();
await puter.perms.request(/* a specific permission */);
```

The full set covers Desktop, Documents, Pictures, Videos, Apps, and Subdomains, each in read and write/manage variants, plus `requestEmail()`.

### 5.12 Utilities

Convenience helpers on the root `puter` object:

```javascript
puter.print('Hello');                 // append to the page (HTML or escaped/code)
const name = puter.randName();        // e.g. "clever-idea-1234" — unique friendly names
const env = puter.env;                // 'gui' | 'app' | 'web' | 'nodejs' | ...
const appID = puter.appID;            // this app's unique ID
puter.quiet = true;                   // suppress the dev console CTA
```

---

## 6. The User-Pays Model

This is the business model that makes Puter unusual, and understanding it is essential to building with the AI and cloud APIs.

**You don't pay for your users' usage.** When an app built with `puter.js` calls a paid resource — an AI model, file storage, hosting — the cost is billed to the **end user's** Puter account, not to the developer. The flow:

1. Your site loads `puter.js` and calls, say, `puter.ai.chat(...)`.
2. If the visitor isn't signed in to Puter, a consent popup appears asking them to sign in (or sign up) and authorize your app.
3. Once authorized, the call runs and any cost is metered against *their* account.
4. You ship AI- and cloud-powered features with **zero** API keys and **zero** usage bills.

Consequences for your code:

- **No API keys.** You never embed an OpenAI/Anthropic key. There's nothing to leak.
- **Expect a sign-in prompt.** The first paid call in the `web` environment surfaces the popup. Design your UI to handle the brief pause.
- **Let users see their usage.** `puter.auth.getMonthlyUsage()` and `getDetailedAppUsage()` let you show consumption.
- **Free tiers exist.** Puter accounts include free allowances (the platform models subscription tiers like `user_free`, `temp_free`, and `unlimited`), so casual use of your app often costs the user nothing.

> [!IMPORTANT]
> Because the user pays, your app can offer expensive capabilities (GPT-class chat, image and video generation) that would be cost-prohibitive if you paid per call. This is the core value proposition of building on Puter.

---

## 7. The puter CLI

The `puter` command-line tool (`@heyputer/cli`) lets you publish static sites and deploy serverless workers from a terminal — handy for CI/CD and scripting. It is in **beta (0.x)**; behavior may change.

### Install and authenticate

```bash
npm install -g @heyputer/cli

# Interactive login (opens a browser flow)
puter login

# Non-interactive: pipe a token on stdin (never as an argument)
echo "$PUTER_TOKEN" | puter login --with-token

# Confirm who you are
puter whoami

# Log out
puter logout
```

> [!CAUTION]
> Tokens are read only from stdin, never from `argv` — so they don't leak into shell history or process listings. `echo $TOKEN | puter login --with-token` is the correct form.

### Publishing a static site

```bash
# Deploy a folder to a subdomain (interactive prompts fill in blanks)
puter site deploy ./public my-cool-site
# → live at https://my-cool-site.puter.site

# Manage sites
puter site list
puter site get my-cool-site
puter site delete my-cool-site
```

`puter site deploy` uploads the whole directory tree (skipping OS junk like `.DS_Store`/`Thumbs.db`), creates a versioned deployment folder so previous versions are preserved, then points the subdomain at the new folder. In interactive mode it suggests a random subdomain and re-prompts on invalid input. In non-interactive mode both the directory and subdomain are required.

> [!TIP]
> Subdomains accept lowercase letters, numbers, and hyphens (not at the ends). You may paste a full host like `my-site.puter.site` — the CLI strips the suffix for you.

### Deploying a serverless worker

```bash
# Deploy a single JS file as a worker
puter worker deploy ./api.js my-api
# → reachable at https://my-api.puter.work

# Manage workers
puter worker list
puter worker get my-api
puter worker delete my-api
```

Deploying writes your file to `~/Workers/{name}.js` and registers the worker (or updates the file in place if it already exists).

> [!WARNING]
> Beta caveat: there is no readiness signal yet, so the CLI cannot confirm that a worker deploy is live — it reports the URL but you should verify by calling it.

### Managing apps

```bash
puter app list
puter app get my-app
```

App commands are read-only in the beta.

### CLI command reference

| Command | Purpose |
|---------|---------|
| `puter login` | Interactive login |
| `puter login --with-token` | Login with a token piped on stdin |
| `puter logout` | Clear the stored login |
| `puter whoami` | Show the current account |
| `puter site deploy <dir> <subdomain>` | Publish a static site |
| `puter site list` / `get <sub>` / `delete <sub>` | Manage sites |
| `puter worker deploy <file> <name>` | Deploy a serverless worker |
| `puter worker list` / `get <name>` / `delete <name>` | Manage workers |
| `puter app list` / `get <name>` | List/inspect apps (read-only) |

> [!TIP]
> Set `PUTER_DEBUG=1` to see verbose output (resolved paths, uploaded files, raw API responses) during `site`/`worker` operations.

---

## 8. Serverless Workers

A Puter Worker is a single JavaScript file you deploy that becomes a live HTTP endpoint at `https://{name}.puter.work`. It runs in a Worker runtime with a built-in router and direct access to your Puter resources.

### The router

Inside a worker, a global `router` object defines routes. It supports the standard HTTP verbs plus a `custom()` for anything else:

```javascript
// my-api.js — deploy with: puter worker deploy ./my-api.js my-api

router.get('/health', () => ({ ok: true }));

router.get('/version', () => 'v1');

// :param captures a path segment
router.get('/posts/:id', ({ params }) => {
    return { id: params.id };
});

// Multiple params
router.get('/posts/:id/comments/:cid', ({ params }) => {
    return `${params.id}/${params.cid}`;
});

// *wildcard captures the rest of the path
router.get('/files/*path', ({ params }) => {
    return new Response(params.path);
});

// POST with a request body
router.post('/echo', async ({ request }) => {
    const body = await request.text();
    return { body };
});

// Non-standard methods
router.custom('PATCH', '/posts/:id', ({ params }) => ({ patched: params.id }));
```

Each handler receives an **event** with:

- `params` — captured route parameters
- `request` — the standard `Request` object (`request.text()`, `request.json()`, headers, …)
- `user` — the *caller's* Puter context when the request carries a `puter-auth` header; `user.puter` is their full SDK surface
- `me` / `my` / `myself` — the *deployer's* own Puter resources (the worker owner)

```javascript
// Read the caller's identity (authenticated requests)
router.post('/whoami', async ({ user }) => {
    if (user) {
        const profile = await user.puter.auth.getUser();
        return { username: profile.username };
    }
    return { anonymous: true };
});

// Use the deployer's own KV store
router.get('/kv/:key', async ({ params }) => {
    return { value: await me.puter.kv.get(params.key) };
});
```

### Return values

A handler may return:

- A plain object → serialized as JSON
- A string, `Blob`, `ArrayBuffer`, `Uint8Array`, `ReadableStream`, or `URLSearchParams` → sent as-is
- A standard `Response` object → used directly

CORS is handled automatically (preflight `OPTIONS` and `Access-Control-Allow-Origin: *`) unless you set `router.handleCors = false`.

### Environment bindings

Workers expose globals like `puter_endpoint` and `puter_auth` so the embedded Puter client knows where and how to authenticate:

```javascript
router.get('/env', () => ({
    endpoint: puter_endpoint,
    authPrefix: puter_auth.slice(0, 4),
}));
```

### TypeScript support

The `@heyputer/worker-types` package provides full types for the worker runtime (`Handler`, `WorkerEvent`, the `router`, env bindings). Route params are inferred from the path: `router.get('/posts/:id', ...)` types `params` as `{ id: string }`.

```typescript
import type { Handler, WorkerEvent } from '@heyputer/worker-types';

const getPost: Handler<{ id: string }> = ({ params }) => ({ id: params.id });
router.get('/posts/:id', getPost);
```

### Calling a worker from puter.js

```javascript
const result = await puter.workers.exec('https://my-api.puter.work/health');
```

`puter.workers.exec()` sends the caller's Puter auth so the worker's `user` context is populated.

---

## 9. Self-Hosting Puter

Run the entire Puter platform on your own machine or server. The only requirement is Docker with the Compose plugin.

### One-line install

=== "Linux / macOS"

    ```bash
    curl -fsSL https://raw.githubusercontent.com/HeyPuter/puter/main/install.sh | sh
    ```

=== "Windows (PowerShell)"

    ```powershell
    irm https://raw.githubusercontent.com/HeyPuter/puter/main/install.ps1 | iex
    ```

The installer:

1. Checks that `docker` (with the compose plugin) is present (plus `curl`/`openssl` on Linux).
2. Creates `./puter-selfhosted/` (override with `PUTER_DIR`).
3. Downloads `docker-compose.yml` and `nginx/nginx.conf`.
4. Generates fresh secrets and writes `.env` + `puter/config/config.json`.
5. Runs `docker compose up -d` and prints how to find the first-boot admin password.

First boot takes ~30 seconds while the database initializes. Then open `http://puter.localhost` (or your chosen domain/port).

### Tunable install options

Set these as environment variables before running the installer:

| Variable | Default | Purpose |
|----------|---------|---------|
| `PUTER_DIR` | `puter-selfhosted` | Install directory |
| `PUTER_DOMAIN` | `puter.localhost` | Domain Puter serves on |
| `PUTER_PORT` | `80` | HTTP port for nginx |
| `PUTER_URL` | GitHub raw (main) | Where to fetch the compose/nginx files |
| `PUTER_FORCE` | `0` | Set to `1` to overwrite existing `.env`/`config.json` |

```bash
# Example: serve on a custom domain and port
PUTER_DOMAIN=cloud.example.com PUTER_PORT=8080 \
  curl -fsSL https://raw.githubusercontent.com/HeyPuter/puter/main/install.sh | sh
```

> [!NOTE]
> Re-running the installer in an already-initialized directory is safe: it keeps your existing secrets and just refreshes the compose file and brings the stack up. Pass `PUTER_FORCE=1` to regenerate everything.

### Finding the admin password

The first-boot admin credentials are printed once to the container log:

=== "Linux / macOS"

    ```bash
    cd puter-selfhosted
    docker compose logs puter | grep password
    ```

=== "Windows (PowerShell)"

    ```powershell
    cd puter-selfhosted
    docker compose logs puter | Select-String password
    ```

### The self-hosted stack

The Compose stack brings up several containers: the **puter** app server, a **MariaDB** database, **Valkey** (Redis-compatible cache), a **DynamoDB**-compatible service, and an **S3**-compatible object store. The default dev configuration instead uses bundled SQLite and a local fake-S3, so a from-source `npm start` needs no external services.

Operational details — configuration reference, database engines, scaling, TLS, security hardening, and the full architecture — are in the **Admin Guide**.

> [!TIP]
> For a development run from source (not Docker), clone the repo, ensure **Node.js 24+**, then `npm install` and `npm start`. The default config (`config.default.json`) serves on port **4100** at `puter.localhost` using SQLite — no external services required.

---

## 10. Troubleshooting

Solutions to the most common problems, drawn from the platform's own error messages and community-reported issues.

### Error: `Unsupported Protocol` / puter.js does nothing on a local file

**Cause:** You opened your HTML file with the `file://` protocol. `puter.js` requires `http://` or `https://`.
**Solution:** Serve the file from any web server — `npx http-server`, `python -m http.server`, or VS Code Live Server — and open it via `http://localhost:...`.
**Prevention:** Always develop against a local server, even for single-page experiments.

### Error: Sign-in popup is blocked or doesn't appear

**Cause:** The browser blocked the popup, or the call happened before a user gesture.
**Solution:** Trigger the first paid call (or `puter.auth.signIn()`) from a click handler so the browser allows the popup. Ensure pop-ups aren't blocked for the site.
**Prevention:** Gate the first AI/FS/KV call behind an explicit user action.

### Error: `puter.fs.mkdir` with `dedupeName` doesn't auto-number

**Cause:** Known issue at the documented commit ([#3201](https://github.com/HeyPuter/puter/issues/3201)) — `dedupeName` may not always produce a unique name when the folder exists.
**Solution:** Check the path returned by `mkdir()`; if it collided, retry with a different base name or add your own suffix.
**Prevention:** Don't assume `dedupeName` guarantees uniqueness; verify the returned `path`.

### Error: `showOpenFilePicker` / `showSaveFilePicker` not working on the web

**Cause:** Known issue ([#2897](https://github.com/HeyPuter/puter/issues/2897)) — the pickers are most reliable for apps running inside the Puter desktop, less so in a standalone third-party page.
**Solution:** For standalone pages, use a native `<input type="file">` plus `puter.fs.upload()` to bring files into Puter.
**Prevention:** Reserve the UI pickers for apps that run inside the Puter desktop.

### Self-host: container reports `Up (unhealthy)`

**Cause:** A dependency (database/cache/object store) hasn't finished initializing, or a bind-mounted directory has the wrong permissions ([#3191](https://github.com/HeyPuter/puter/issues/3191)).
**Solution:** Give it ~30s on first boot. Then check logs: `docker compose logs -f puter`. On native Linux, ensure the `puter/data/*` directories are writable (the installer `chmod 0777`s them to sidestep non-root container UID mismatches).
**Prevention:** Use the official installer, which pre-creates and permissions the data dirs.

### Self-host: can I use Podman instead of Docker?

The installer and compose files target Docker with the Compose plugin. Podman is community-requested ([#3170](https://github.com/HeyPuter/puter/issues/3170)) but not officially documented; you may need to adapt the compose invocation. Use Docker for the supported path.

### Error: `pip install puter` fails / wrong client

**Cause:** Puter's client is JavaScript (`@heyputer/puter.js` on npm, or the CDN script tag). There is no official `pip` package.
**Solution:** Use the npm package or the `<script src="https://js.puter.com/v2/">` tag.

### Sign-up problems on a fresh self-host

**Cause:** Email verification or host/domain configuration ([#3127](https://github.com/HeyPuter/puter/issues/3127)).
**Solution:** The default OSS config sets `strict_email_verification_required: false`, so accounts work without email. If you changed domain settings, confirm `domain`, `static_hosting_domain`, and the `*_app_hosting_domain` values match how you reach the instance.

### CLI: `Cannot log in non-interactively without a token`

**Cause:** You ran `puter login` in a non-interactive context (CI) without piping a token.
**Solution:** `echo "$PUTER_TOKEN" | puter login --with-token`.

---

## 11. Frequently Asked Questions

**Q: Do I need an API key to use AI with puter.js?**
No. That's the point — there are no API keys. Paid calls are billed to the end user's Puter account under the [user-pays model](#6-the-user-pays-model).

**Q: How much does it cost me to add GPT/Claude/Gemini to my site?**
Nothing for usage. Your users cover the cost through their Puter accounts, which include free allowances.

**Q: Which AI models are available?**
Many — OpenAI (GPT family, o3-mini), Anthropic (Claude), Google (Gemini), DeepSeek, xAI (Grok), Mistral, and others. Call `puter.ai.listModels()` for the authoritative, current list rather than hard-coding model IDs.

**Q: Is Puter free and open-source?**
Yes. The platform is AGPL-3.0 (the CLI is MIT). You can use puter.com or self-host.

**Q: Can I run Puter entirely on my own server?**
Yes — one Docker command. See [Self-Hosting](#9-self-hosting-puter). It supports SQLite (default dev), MySQL/MariaDB, and PostgreSQL, plus S3-compatible object storage.

**Q: Where do files written with `puter.fs` go?**
Into the signed-in user's personal Puter drive, scoped to your app's sandbox (`~/AppData/{app-id}`) unless you request broader access via `puter.perms`.

**Q: What's the difference between Hosting and Workers?**
`puter.hosting` serves **static** files at `*.puter.site`. **Workers** run your **JavaScript** as a live HTTP endpoint at `*.puter.work` with a router and access to Puter resources.

**Q: Can I use puter.js in Node.js, not just the browser?**
Yes. Install `@heyputer/puter.js` and import it; the SDK polyfills the browser globals it needs and authenticates via a stored token.

**Q: How do I see how much a user has consumed?**
`puter.auth.getMonthlyUsage()` and `puter.auth.getDetailedAppUsage()`.

**Q: My app needs to read the user's existing files. How?**
Request access explicitly with `puter.perms` (e.g. `requestReadDocuments()`); the user is prompted to grant it. By default an app only sees its own AppData sandbox.

**Q: Can I publish my puter.js app to the Puter desktop / App Store?**
Yes. Host it (with `puter.hosting` or anywhere), then register it with `puter.apps.create()`. Submit to the [App Store](https://apps.puter.com/).

**Q: Does the SDK support TypeScript?**
Fully. `@heyputer/puter.js` ships type declarations, and `@heyputer/worker-types` types the Workers runtime.

**Q: Where's the official, always-current API reference?**
[docs.puter.com](https://docs.puter.com), with a live [playground](https://docs.puter.com/playground/) of runnable examples for every API.

---

## 12. Reference

### SDK module map

| Object | Methods (summary) |
|--------|-------------------|
| `puter.ai` | `chat`, `listModels`, `listModelProviders`, `txt2img`, `txt2speech` (+ `listEngines`/`listVoices`), `txt2vid`, `img2txt`, `speech2txt`, `speech2speech` |
| `puter.fs` | `write`, `read`, `mkdir`, `readdir`, `rename`, `copy`, `move`, `stat`, `delete`, `upload`, `getReadURL` |
| `puter.kv` | `set`, `get`, `incr`, `decr`, `add`, `update`, `remove`, `del`, `list`, `flush`, `expire`, `expireAt`, `MAX_KEY_SIZE`, `MAX_VALUE_SIZE` |
| `puter.hosting` | `create`, `list`, `delete`, `update`, `get` |
| `puter.auth` | `signIn`, `signOut`, `isSignedIn`, `getUser`, `getMonthlyUsage`, `getDetailedAppUsage` |
| `puter.apps` | `create`, `list`, `delete`, `update`, `get` |
| `puter.ui` | `alert`, `prompt`, `notify`, `contextMenu`, `setMenubar`, window control, file/color/font pickers, spinners, `launchApp`, `socialShare`, lifecycle events |
| `puter.workers` | `create`, `delete`, `list`, `get`, `exec` |
| `puter.net` | `Socket`, `tls.TLSSocket`, `fetch` |
| `puter.peer` | `serve`, `connect`, `ensureTurnRelays` |
| `puter.perms` | `request`, `requestEmail`, `request{Read,Write}{Desktop,Documents,Pictures,Videos}`, `request{Read,Manage}{Apps,Subdomains}` |
| `puter` (root) | `print`, `randName`, `env`, `appID`, `exit` |

### Domains

| Domain | Used for |
|--------|----------|
| `puter.com` | The hosted desktop |
| `js.puter.com/v2/` | The SDK CDN |
| `api.puter.com` | The API origin the SDK calls |
| `*.puter.site` | Published static sites (`puter.hosting`) |
| `*.puter.work` | Deployed workers (`puter.workers`) |
| `docs.puter.com` | API docs + playground |
| `apps.puter.com` | The App Store |

### Environments returned by `puter.env`

`gui` · `app` · `web` · `nodejs` · `web-worker` · `service-worker`

### Helpful CLI environment variables

| Variable | Effect |
|----------|--------|
| `PUTER_DEBUG=1` | Verbose CLI output |
| `PUTER_AUTH_TOKEN` | Provide an auth token to the CLI |
| `PUTER_SITE_DOMAIN` | Override the site-hosting domain |
| `PUTER_WORKER_DOMAIN` | Override the worker domain |

### Where to get help

- **Docs & playground:** [docs.puter.com](https://docs.puter.com)
- **Discord:** [discord.com/invite/PQcx7Teh8u](https://discord.com/invite/PQcx7Teh8u)
- **Reddit:** [reddit.com/r/puter](https://www.reddit.com/r/puter)
- **Issues:** [github.com/HeyPuter/puter/issues](https://github.com/HeyPuter/puter/issues)
- **Security:** [email removed]

---

_Generated June 2026 from HeyPuter/puter at 98a21c4. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
