/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
Double opt-in email capture with signed confirmation links and CSV export.
Per-product waitlists with confirmation, queue position, referral codes and admin export.
A small content store with an editor: typed collections, versioned, in the venture's own database. Public reads, admin-token writes.
Users, identities, credentials, sessions and single-use tokens. Authorization code with PKCE, ES256 JWTs with JWKS, account linking on verified email only.
WebAuthn registration and login verified in pure Rust, and Google over OpenID Connect. Apple, Meta, password and magic links are Designed.
Renders every module's declared surface: forms and admin pages at /ui, a 4 KB embed for static sites, styling by plain CSS.
Two-tier secrets with envelope encryption, a KMS trait and local provider, a tamper-evident audit chain and key rotation.
The shared test suite every module must pass; run it in your own crate.
CLI: migrations collect and apply, doctor, modules, data export and import.
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.
- 01Add the harness to Cargo.toml.
cargo add cratefield, or depend oncratefield-coredirectly if you want only the trait. - 02Implement Module. name(), version(), requires(), migrations(), validate_config(), router(). Ask for ports by name; never import an adapter.
- 03Write migrations in the portable SQL subset. Forward-only, include_str!, numbered. The kit checks idempotency.
- 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.
- 05Run the conformance kit. Concurrent requests, problem+json, rate-limit headers, request-id propagation, migration idempotency, against SQLite and Postgres both.
- 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.
| Crate | What it is |
|---|---|
| The one you start with | |
cratefield | The facade. Re-exports the core, and pulls in a runtime, adapters and modules by feature, at versions known to agree. |
| The core | |
cratefield-core | The Module trait, the Harness builder, the ports, problem+json errors, request scope, event bus, template registry. |
cratefield-ui | Renders the module surface as HTML inside the Worker: pages, fragments, the cf-* markup contract. |
cratefield-testing | The conformance kit every module runs against. Fake ports, in-memory database, request helpers. |
cratefield-cli | The fz binary: migrations collect, migrations apply, doctor, modules. |
| Runtimes | |
cratefield-runtime-cloudflare | Workers. D1, KV, rate limiting, R2 and wait_until mapped to ports. |
cratefield-runtime-native | The same harness as one tokio binary: axum on a TcpListener, Redis ports, in-process cron. |
| Adapters | |
cratefield-adapter-sqlite | Database over rusqlite. Every test, and single-node self-hosting. |
cratefield-adapter-postgres | Database over sqlx, for the native runtime. |
cratefield-adapter-resend | Mailer over the Resend API, refusing to send until a domain is verified. |
cratefield-adapter-turnstile | Captcha over Cloudflare Turnstile, fail-closed. |
cratefield-adapter-apns | Push over Apple Push Notification service. |
cratefield-adapter-stripe | Payments over the Stripe REST API, through the runtime’s HttpClient port. |
| Secrets | |
cratefield-secrets | Envelope-encrypted secrets over the Database port, two tiers, ciphertexts bound to their row. |
cratefield-kms | The KMS port: wrap and unwrap data keys, with a local-file provider that refuses production. |
| Modules | |
cratefield-module-email-signup | Email signup with double opt-in, unsubscribe, admin export. |
cratefield-module-waitlist | Per-product waitlist with confirm, position and referral codes. |
cratefield-module-cms | A 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.