Deploy a static site or attach files to a Worker with ordinary Wrangler configuration. Asset bytes use the appliance’s existing storage blob engine; the assets service adds immutable manifests, upload validation, routing, retention, and serving. This is not an S3 bucket, a pull-through cache, or a CDN product, and it does not create a process per site.
[assets]
directory = "./public"
binding = "ASSETS"
run_worker_first = ["/api/*"]Then run wrangler deploy against the local API. A site with no Worker script
can omit main; a full-stack Worker can use env.ASSETS.fetch(request).
See Databases for the combined native example and runnable check.
Routing
Matching files are served first by default. Requests without an asset match
fall through to the Worker. Set run_worker_first = true to invoke the Worker
first, or use a list of path patterns and ! exclusions to protect selected
paths. These patterns support one wildcard per pattern.
Supported asset behavior includes GET and HEAD, content types, ETags and
If-None-Match, byte ranges, ETag-based If-Range, HTML handling modes, SPA index
fallback, and nearest-parent 404.html fallback. _headers path rules and
_redirects path/placeholder rules are validated during publication. Redirect
statuses 301/302/303/307/308 and local 200 rewrites are supported. External proxy
rewrites and hostname-based _headers rules are rejected. Date-based conditional
requests are not implemented; use ETags.
Named placeholders occupy a complete path segment (for example, /blog/:slug);
ambiguous adjacent captures are rejected before publication.
An asset hit currently passes through a platform-injected wrapper in the same dynamic isolate as the Worker. It does not invoke the application’s fetch handler on an assets-first hit, but it is not a separate isolate-free CDN fast path. Application module initialization can therefore affect first use.
Publication and rollback
Wrangler submits a manifest, receives an expiring organization/Worker-scoped upload capability, and uploads only missing hashes. The pinned CLI’s single asset upload protocol is supported. The service checks both byte length and Wrangler’s BLAKE3 content hash, and refuses to publish an incomplete manifest.
Each Worker version records one immutable asset manifest. Code, bindings, and manifest change together at the existing routing cutover; rollback selects the old version’s manifest too. Requests already executing retain their selected version. The console’s deployment details show file count and manifest bytes.
Retained Worker versions pin their assets. After pruning, manifests and blobs remain available for the HAProxy drain budget (one hour by default), so an in-flight old isolate can still fetch its files. This grace retention remains charged to the organization. Abandoned uploads expire after one hour. The assets service periodically collects expired, unreferenced sessions and blobs. A durable control-plane cleanup outbox makes publication/pruning cleanup retryable after restart or service failure; cleanup failure does not turn a successful deployment into a reported failure.
Retention is not stale-URL routing: after a new deployment, new requests resolve against the newly active manifest. A browser holding old HTML may request an old fingerprinted URL that is absent from the new manifest and receive a 404. Include old fingerprinted files in subsequent builds when that overlap is needed. The service does not search another version’s manifest behind your back.
Isolation and quotas
Assets are scoped by organization and Worker version. Stored blobs use a private namespace outside tenant-visible R2 buckets. Tenants cannot list them using S3 or R2 bindings. Identical hashes deduplicate within an organization, not across organizations. Service credentials remain in trusted runtime bindings.
Asset allowances follow the organization tier: Free permits 64 MiB and two pending uploads, Standard permits 512 MiB and five, and Enterprise permits 2 GiB and ten. Each manifest supports up to 20,000 files and each file up to 25 MiB. Assets do not also consume the organization’s R2 bucket allowance. Expired upload slots are reclaimed by the periodic collector; referenced versions remain charged until their blobs are no longer retained.
Observability and continuity
Ansible provisions the assets service, scrape targets, Loki journal collection, Tempo request spans, and the Databases & Static Assets Grafana dashboard. Delivery metrics distinguish failures, cancellations, missing files, conditional hits, redirects, and successful body completion. First-byte timing starts at the assets service; it is not browser end-to-end latency. No traffic is not a passing SLO.
export ERMAHGERD_OWNER_PASSWORD='<local-owner-password>'
make load-data-updateThis generates k6 summaries and deployment timestamps under .cache/, with a
constant total of 10 public requests/second across direct asset delivery and a
Worker that reads both D1 and its bound assets. It checks code/manifest coherence
through deployment and rollback. The default p99 < 250 ms threshold is a test
guardrail, not a promised asset SLO. Benchmark small/large responses, warm/cold
storage, uploads, and sustained delivery before selecting service objectives.