/technology/modules/ · The field

A module is a crate that runs on anyone's Harness.

It implements the Module trait, ships its own migrations, asks for ports by name, declares the surface its pages are built from, and passes the conformance kit. Nothing in it knows which cloud it is on. Every module below carries its status.

Shippingin the repo Designedpublic issues, unbuilt Plannednamed, unspecified

email-signupShipping

Double opt-in email capture with signed confirmation links and CSV export.

Database · Mailer · Signer · Captcha and RateLimiter optional
waitlistShipping

Per-product waitlists with confirmation, queue position, referral codes and admin export.

Database · Mailer · Signer · Captcha and RateLimiter optional
cmsShipping

A small content store with an editor: typed collections, versioned, in the venture's own database. Public reads, admin-token writes.

Database
auth-coreShipping

Users, identities, credentials, sessions and single-use tokens. Authorization code with PKCE, ES256 JWTs with JWKS, account linking on verified email only.

Database · Clock · IdGen
auth-passkeys, auth-oidcShipping

WebAuthn registration and login verified in pure Rust, and Google over OpenID Connect. Apple, Meta, password and magic links are Designed.

Database · Clock · IdGen · Signer · HttpClient
factory0-uiShipping

Renders every module's declared surface: forms and admin pages at /ui, a 4 KB embed for static sites, styling by plain CSS.

renderer
secrets, kmsShipping

Two-tier secrets with envelope encryption, a KMS trait and local provider, a tamper-evident audit chain and key rotation.

Database · Clock
conformance-kitShipping

The shared test suite every module must pass; run it in your own crate.

test harness
fzShipping

CLI: migrations collect and apply, doctor, modules, data export and import.

tool

Writing one

Implement the trait. Pass the kit.

The conformance kit runs every module against the same contract: problem+json errors, rate-limit headers, request-id propagation, concurrent-request safety, migration idempotency. A module that passes on SQLite runs unchanged on D1.

  1. 01Add the harness to Cargo.toml. cargo add cratefield, or depend on cratefield-core directly if you want only the trait.
  2. 02Implement Module. name(), version(), requires(), migrations(), validate_config(), router(). Ask for ports by name; never import an adapter.
  3. 03Write migrations in the portable SQL subset. Forward-only, include_str!, numbered. The kit checks idempotency.
  4. 04Declare a surface, or don't. surface() names the actions a renderer may offer, and their input schemas come from the handler's own body types, so the forms cannot drift from the routes. A module that declares nothing renders nothing.
  5. 05Run the conformance kit. Concurrent requests, problem+json, rate-limit headers, request-id propagation, migration idempotency, against SQLite and Postgres both.
  6. 06Add it to a composition and build for wasm32. If it pulls tokio, mio or std::fs, the build fails. That is the point.

The crates Shipping

Eighteen crates on crates.io. MIT.

Most backends want one line: cargo add cratefield gives you the core and lets you pick a runtime, adapters and modules by feature. Every crate is also published on its own, and they are the same types either way, so you can depend on the parts instead whenever you would rather.

Every published Cratefield crate, with a link to its page on crates.io.
Crate What it is
The one you start with
cratefieldThe facade. Re-exports the core, and pulls in a runtime, adapters and modules by feature, at versions known to agree.
The core
cratefield-coreThe Module trait, the Harness builder, the ports, problem+json errors, request scope, event bus, template registry.
cratefield-uiRenders the module surface as HTML inside the Worker: pages, fragments, the cf-* markup contract.
cratefield-testingThe conformance kit every module runs against. Fake ports, in-memory database, request helpers.
cratefield-cliThe fz binary: migrations collect, migrations apply, doctor, modules.
Runtimes
cratefield-runtime-cloudflareWorkers. D1, KV, rate limiting, R2 and wait_until mapped to ports.
cratefield-runtime-nativeThe same harness as one tokio binary: axum on a TcpListener, Redis ports, in-process cron.
Adapters
cratefield-adapter-sqliteDatabase over rusqlite. Every test, and single-node self-hosting.
cratefield-adapter-postgresDatabase over sqlx, for the native runtime.
cratefield-adapter-resendMailer over the Resend API, refusing to send until a domain is verified.
cratefield-adapter-turnstileCaptcha over Cloudflare Turnstile, fail-closed.
cratefield-adapter-apnsPush over Apple Push Notification service.
cratefield-adapter-stripePayments over the Stripe REST API, through the runtime’s HttpClient port.
Secrets
cratefield-secretsEnvelope-encrypted secrets over the Database port, two tiers, ciphertexts bound to their row.
cratefield-kmsThe KMS port: wrap and unwrap data keys, with a local-file provider that refuses production.
Modules
cratefield-module-email-signupEmail signup with double opt-in, unsubscribe, admin export.
cratefield-module-waitlistPer-product waitlist with confirm, position and referral codes.
cratefield-module-cmsA small content store with an editor: typed collections, versioned.

Two mounts Shipping

Your module. Your Worker. Your source.

A module can be mounted two ways, and the caller cannot tell which. Compiled in is the default: the crate is linked into the worker alongside every other module. Sidecar is the other: the module is its own worker, compiled on its own, reached over a service binding and mounted at the same /v1/<name> path. Both workers live in the same account, which in the default mode is yours. The platform runs them on the same thread of the same server, so the hop costs no measurable latency and nothing extra to run.

The reason it matters is not performance. A sidecar is built and deployed by whoever owns it, which means a module you will not share can run inside your backend without the source ever reaching us. It binds the same database as everything else, so it is a real module with real ports, not a webhook. It is not meant to get the same secrets either: a module we did not build should never hold the key that signs the rest of your backend’s links, so it would be provisioned its own. The secrets layer that makes that possible is Shipping; wiring a sidecar to its own store is part of the sidecar programme, still Designed.

The mount itself is merged: ADR 0009, the Dispatcher port, and a sidecar's surface folded into the same /__surface the compiled-in modules answer with. What is still Designed is the programme around it: the build-artifact cache, the cold-start handshake, a module template you build yourself, conformance parity across both mounts, and the deployment model for customer-supplied modules. Those are epic #56, issues #59 and #61 to #67.

Want a module that does not exist yet? Tell us what you are running today.