Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Puter User Guide

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.

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, 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.


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
A web developer A zero-backend SDK for storage, auth, hosting, and AI Building with puter.js
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
A self-hoster or homelab operator A full open-source stack you run with one Docker command Self-Hosting Puter
A terminal user A CLI to publish static sites and deploy serverless workers 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.

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 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:

<!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) 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:

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

On Windows (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 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:

<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>
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:

<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:

npm install @heyputer/puter.js
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:

npm install -g @heyputer/cli
Adds the `puter` command to your PATH.

Requires Node.js 18 or newer. See The puter CLI.


4. Using the Puter Desktop

The desktop is the GUI you see at 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).

Note

A self-hosted instance prints the admin credentials exactly once at first boot:

************************************************************
* 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, and you can publish your own — any site built with puter.js can be registered as a Puter app. See Building and Publishing Apps 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:

// 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');
Paris (and so on, depending on the prompt).

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

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:

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:

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:

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:

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:

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:

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:

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.

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.

// 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.
// 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">:

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:

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). 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.

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):

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:

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):

// 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:

// 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.

5.6 Auth — puter.auth

Identify and manage the signed-in user:

// 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:

// 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:

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). 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; the SDK side manages deployments:

// 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:

// 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:

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:

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:

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

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

# 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

# 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

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:

// 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)
// 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:

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 }.

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

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

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

Windows (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
# 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

cd puter-selfhosted
docker compose logs puter | grep password

Windows (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) — 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) — 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). 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 0777s 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) 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). 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.

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. 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.

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, with a live 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