Docs / Serverless Objects / Quick start

Quick start

Write a Durable Object counter, run it locally with sobjects dev, then deploy the Wrangler project to a bucket and serve it with sobjects up.

This walkthrough runs a Durable Object counter locally, then deploys it to a single node. You need the sobjects CLI from installation.

Write a Wrangler project

The code is written exactly as it would be for Cloudflare. sobjects reads wrangler.json or wrangler.jsonc, not wrangler.toml.

// wrangler.jsonc
{
  "name": "hello",
  "main": "src/index.js",
  "compatibility_date": "2026-02-24",
  "durable_objects": { "bindings": [{ "name": "COUNTER", "class_name": "Counter" }] },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Counter"] }]
}
// src/index.js
import { DurableObject } from "cloudflare:workers";

export class Counter extends DurableObject {
  async increment() {
    const count = ((await this.ctx.storage.get("count")) ?? 0) + 1;
    await this.ctx.storage.put("count", count);
    return count;
  }
}

export default {
  async fetch(request, env) {
    const name = new URL(request.url).pathname.slice(1) || "default";
    const count = await env.COUNTER.getByName(name).increment();
    return new Response(`${name}: ${count}\n`);
  },
};

Run it locally with sobjects dev

No bucket is needed. The first run downloads and verifies the pinned celld release.

sobjects dev . --port 8787
curl http://127.0.0.1:8787/alice   # alice: 1

sobjects dev delegates to celld's local development server and does not start the ingress. Run sobjects dev --help to see celld's own options.

To run the same project in Node on the native engine instead:

sobjects dev . --engine native --port 8787

The native engine is for trusted code. See native engine and benchmarks.

Deploy to a node

Create sobjects.toml next to the project and point it at a bucket you control. The bucket must support conditional writes.

[celld]
bucket = "s3://my-bucket/serverless-objects"
# endpoint = "https://<account>.r2.cloudflarestorage.com"   # for S3-compatible stores
# region = "auto"

Put the credentials in the environment, then deploy and start the node:

export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=...
sobjects deploy .            # bundle the app and upload it to the bucket
sobjects up --detach         # start the node on 127.0.0.1:8080
curl http://127.0.0.1:8080/alice
sobjects status
sobjects logs
sobjects down

State survives down and up because it lives in the bucket. Running nodes pick up a new deployment on their next poll.

What deploy does to your config

Before calling celld, dev and deploy parse the Wrangler config:

  • Unknown top-level keys are rejected.
  • observability and upload_source_maps are removed, with a warning.
  • A declared build.command runs first.
  • If assets.directory is missing in a project that uses @cloudflare/vite-plugin, vite build runs and the generated index.html is located. Ambiguous output fails before celld starts.

deploy also fills a missing bucket, endpoint and region from the project's sobjects.toml. Explicit flags and environment variables take precedence. By default, deploy wraps your Durable Object classes in a small storage inspection hook for the dashboard. Pass --no-inspector to deploy the app unwrapped.

Next steps