[PUT-1090] Improve worker docs (#3217)
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
Notify HeyPuter / notify (push) Has been cancelled
release-please / release-please (push) Has been cancelled

* Improve worker docs

* refine
This commit is contained in:
Reynaldi Chernando
2026-06-05 21:54:56 +07:00
committed by GitHub
parent bb61103080
commit a4b6ee0dd2
3 changed files with 116 additions and 23 deletions
+2
View File
@@ -144,6 +144,8 @@ You can see various Puter.js workers management features in action from the foll
Once your worker is ready, you can put it online on a free `*.puter.work` subdomain.
<div class="info">A worker is created once and keeps its name and URL. To ship changes, overwrite its source file rather than creating a new worker — see <a href="/Workers/create/#updating-a-worker">Updating a worker</a>.</div>
### Publish from puter.com
The quickest way to publish a worker is to create it on [puter.com](https://puter.com) and publish it.
+20 -1
View File
@@ -10,6 +10,8 @@ Creates and deploys a new worker from a JavaScript file containing [router](../r
<div class="info">After a worker is created or updated, full propagation may take between 5 to 30 seconds to fully take effect across all edge servers. </div>
<div class="info">A worker is tied to its <strong>name</strong> — you create it <strong>once</strong> and keep that name. To deploy changes, don't call <code>create()</code> again with a new name; instead overwrite the worker's source file (see <a href="#updating-a-worker">Updating a worker</a> below). Recreating under a different name leaves the old worker live at its old URL while your callers end up pointing at an orphaned one.</div>
## Syntax
@@ -85,4 +87,21 @@ puter.workers.create('my-api', 'api-server.js')
</script>
</body>
</html>
```
```
## Updating a worker
A worker keeps the same name and URL for its whole lifetime. You create it once with `create()`; after that, you **update it by overwriting its source file**, not by creating a new worker.
[`puter.workers.get()`](/Workers/get/) returns the worker's [`file_path`](/Objects/workerinfo), so you can write your new code back to it:
```js
// Look up the deployed worker's source file
const info = await puter.workers.get('my-api');
// Overwrite it with your new code — this redeploys the worker
// at the same name and URL
await puter.fs.write(info.file_path, updatedWorkerCode);
```
The worker redeploys from that file, so `https://my-api.puter.work` keeps serving — now running your updated code. Anything already calling the worker keeps working without changes.
+94 -22
View File
@@ -28,35 +28,86 @@ The router object supports standard HTTP methods and provides a clean way to org
### Handler Parameters
Route handlers receive structured parameters:
Route handlers receive a single object as their parameter, which can be destructured into the following properties:
- `request` - The incoming [HTTP request](https://developer.mozilla.org/en-US/docs/Web/API/Request).
- `user` - The user object, contains `user.puter` (available when called via [`puter.workers.exec()`](/Workers/exec/))
- `user.puter` - The user's Puter resources (KV, FS, AI, etc.)
- `params` - URL parameters (for dynamic routes)
- `me` - The deployer's Puter object (your own Puter resources for KV, FS, AI, etc.)
- `user` - An object representing the user who made the request to this worker. It has a `puter` property (`user.puter`) that gives you access to that user's own Puter resources — KV, FS, AI, etc. Only available when the worker is called via [`puter.workers.exec()`](/Workers/exec/).
- `params` - Route parameters captured from the path (see [Route Parameters](#route-parameters))
## Global Objects
When writing worker code, you have access to several global objects:
When writing worker code, you have access to these global objects:
- `router` - The router object for defining API endpoints
- `me.puter` - The deployer's Puter object (your own Puter resources for KV, FS, AI, etc.)
**Note**: `me.puter` refers to the deployer's (your) Puter resources, while `user.puter` refers to the user's resources when they execute your worker with their own token.
- `me` - An object representing you, the worker's owner. It has a `puter` property (`me.puter`) that gives you access to your own Puter resources KV, FS, AI, etc.
## Integration with Puter.js
Just like in apps or websites, you can use Puter.js in workers to access AI, cloud storage, key-value stores, and databases.
The difference is where the resources are utilized. Normally with Puter.js, all resources belong to your users; each user has their own storage and databases. Workers give you the flexibility in using resources:
The difference is *whose* resources you use. A worker gives you two `.puter` objects to work with, and operations are billed to whichever one you call:
- **Worker context** (`me.puter`) - Store data in your own storage and databases. Use this for shared application data, server-side logic, and centralized resources that you control.
- **User context** (`user.puter`) - Keep data in each user's own storage and databases. This maintains the default [User-Pays model](/user-pays-model/) while still executing logic server-side.
- **`me.puter`** is the **worker context** — your own resources, as the owner. Use this for shared application data, server-side logic, and centralized resources you control. Operations run against your account and are billed to you.
- **`user.puter`** is the **user context** — the resources of the user who called the worker (available when it's executed via [`puter.workers.exec()`](/Workers/exec/), which runs it with their token). This keeps the default [User-Pays model](/user-pays-model/): each user's data stays in their own storage, billed to them, while your logic still runs server-side.
> The `user` object is available when the worker is executed via `puter.workers.exec()` in the frontend and contains the user's own Puter resources.
So you can mix and match within the same codebase — some endpoints reading and writing your own data (`me.puter`), others acting on the calling user's data (`user.puter`).
This means you can choose which parts of your app use centralized resources (your storage/database) versus user-specific resources, all from the same codebase.
## Route Parameters
Sometimes part of a path isn't fixed — like a post ID or a username. You can capture these segments by prefixing them with a colon (`:`) in the route path. Each captured segment becomes a property on the `params` object, keyed by the name you gave it.
```js
router.get("/api/posts/:category/:id", async ({ params }) => {
const { category, id } = params;
return { category, id };
});
```
A request to `/api/posts/tech/42` matches this route and gives you:
- `params.category``"tech"`
- `params.id``"42"`
You can use as many route parameters as you need. Captured values are always strings, so convert them yourself if you expect a number.
## Wildcard Routes
While a route parameter (`:name`) matches a single segment, a **wildcard** (`*name`) matches the rest of the path — any number of segments. Like a route parameter, the matched value is available on `params`, keyed by the name after the `*`.
```js
router.get("/files/*path", async ({ params }) => {
// A request to /files/images/avatars/me.png gives:
// params.path === "images/avatars/me.png"
return { path: params.path };
});
```
A common use is a catch-all route for unmatched paths — define it last so it only runs when nothing else matched (see the [404 Handler](#examples) example below).
## CORS
Every response from your worker automatically includes `Access-Control-Allow-Origin: *`, so **simple cross-origin requests work out of the box** — a basic `GET` or `POST` from another origin just works, no extra code.
Some requests need a **CORS preflight** first: the browser sends an `OPTIONS` request and waits for the allowed methods and headers before sending the real one. This happens when the request uses a method like `PUT` or `DELETE`, or carries custom headers (e.g. `Authorization`).
To handle this, you can add an `OPTIONS` handler that returns the methods and headers you want to allow:
```js
router.options("/*path", async () => {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
});
```
This answers the preflight for any path with the CORS headers the browser expects, so your other routes work cross-origin.
If you need different CORS rules per endpoint — for example, restricting the allowed methods or headers on a specific route — define an `OPTIONS` handler on that individual path instead of using the wildcard.
## Examples
@@ -91,14 +142,14 @@ router.post("/api/user", async ({ request }) => {
});
```
<strong class="example-title">URL Parameters</strong>
<strong class="example-title">Query Parameters</strong>
```js
router.post("/api/user*tag", async ({ request }) => {
// Get URL parameters
router.get("/api/search", async ({ request }) => {
// Read query string parameters from the URL
const url = new URL(request.url);
const queryParam = url.searchParams.get("param");
return { processed: true };
const query = url.searchParams.get("q");
return { query };
});
```
@@ -112,13 +163,12 @@ router.post("/api/user", async ({ request }) => {
});
```
<strong class="example-title">URL Parameters</strong>
<strong class="example-title">Route Parameters</strong>
Use `:paramName` in your route path to capture dynamic segments:
Use `:name` in your route path to capture route parameters:
```js
router.get("/api/posts/:category/:id", async ({ request, params }) => {
// Dynamic route with parameters
const { category, id } = params;
return { category, id };
});
@@ -210,6 +260,28 @@ router.post("/api/risky-operation", async ({ request }) => {
});
```
<strong class="example-title">Worker Context vs User Context</strong>
The same operation can run against either Puter account. Here, one endpoint reads from the calling user's KV store (`user.puter`), the other from your own (`me.puter`).
```js
// Read from the calling user's KV store (user context)
router.get("/api/kv/user/get", async ({ request, user }) => {
const url = new URL(request.url);
const key = url.searchParams.get("key");
const value = await user.puter.kv.get(key);
return { value };
});
// Read from the worker owner's KV store (worker context)
router.get("/api/kv/worker/get", async ({ request }) => {
const url = new URL(request.url);
const key = url.searchParams.get("key");
const value = await me.puter.kv.get(key);
return { value };
});
```
<strong class="example-title">File System Integration</strong>
```js