Skip to main content
In development. 86d is being built in the open. Every capability is Experimental until it earns evidence, so check maturity levels before you rely on anything here.
Every Feature in 86d is a Module, including every first-party entry in the generated registry. There is no privileged internal API: what you write gets the same treatment as @86d-store/products. Your Module can add Storefront components, Store Admin pages, and API endpoints. Every Module declares exactly one storage kind. Use native Zod with the col registry for Relational tables, declared Config keys for settings, or { kind: "none" } when the Module owns no durable rows.

Scaffold it

1

Create the package

You get a package and surface scaffold under modules/my-feature/:
2

Declare storage

Every Module declares storage: { kind: "none" }, storage: { kind: "config", ... }, or storage: { kind: "relational", ... }. Relational field names are locked: tables, extends, anchors, and publishes.
src/schema.ts
Wire storage: myFeatureStorage from the Module factory. Do not author legacy Module.schema field maps.
3

Write endpoints

Add controllers and endpoints. Store endpoints are public. Admin endpoints sit under /api/admin/... and require an authenticated Store Admin session, enforced for you.
4

Enable it

This adds @86d-store/my-feature to your active Template’s config.json.
5

Regenerate

Your Module is now imported statically, its endpoints are mounted under /api/, and its components are in the MDX registry.
6

Run the tests

The starter test only checks that the factory returns the expected id and version. Replace it with coverage of what your Module actually does, including what it does when a dependency is unavailable.

Entry point

src/index.ts exports a factory that returns a Module with an explicit storage declaration:
src/index.ts
It is a factory rather than a singleton so a Store can pass it options through moduleOptions in config.json. Declare every cross-Module edge up front: capabilities you provide or accept (exact or caret SemVer ranges), hooks you define or implement, readers over published views, template data projections, and durable event emits/handlers (exact integer schema versions). Provisioning compiles those edges into one execution graph; missing or incompatible edges fail the build, and request paths perform no Module discovery.

Where your admin pages appear

Declare a group and your pages land in the Store Admin sidebar. The nine top-level groups are Catalog, Sales, Customers, Fulfillment, Marketing, Content, Finance, Support, and System. Each has collapsible subgroups, such as Sales → Orders, Cart, Billing. Name one with subgroup on an AdminPage:

Talking to another Module

Cross-Module work uses typed capabilities for immediate decisions. Cross-Module reads use published column-projected views granted at provision time. Legacy exports and requires field metadata remains during migration; it does not grant table access.
The runtime validates contracts at startup. A missing capability fails at boot, not on a shopper request.

Reaching the database

Database access goes through ModuleDataService as ctx.data. It reaches compiled mod_<moduleId> tables and declared Config keys only. There is no raw SQL escape and no JSON fallback. When isolation is enforced, each request transaction enters the Module Postgres role with SET LOCAL ROLE. A Module never imports @86d-store/db or an ORM client directly:
@86d-store/core/test-utils gives you an in-memory data service, so your unit tests need no database at all.

Publishing to npm

Anyone running 86d can install your Module once it is published. Published packages must ship compiled dist/ (JavaScript + .d.ts), not raw src/. Workspace development can keep exports on ./src for DX; publishConfig.exports must point at ./dist.
1

Update package.json

Set "private": false, pick a version, and write a description and keywords someone can find. Require:
  • "files": ["dist", "README.md"] (exclude tests; never include src, .turbo, or vitest config)
  • "publishConfig.exports" mapping every public entry to ./dist/*.js and ./dist/*.d.ts
  • A build that runs tsc with rootDir: "src", outDir: "dist", declaration: true, and copies non-TS assets (for example .mdx) into dist
2

Build

First-party and scaffolded Modules use "build": "86d module build", which runs tsc then copies non-TS assets (for example .mdx) into dist/. Confirm dist/ contains .js and .d.ts for every export, and that npm pack --dry-run lists only dist + README + package metadata. Add "86d" as a devDependency so the CLI bin resolves.
3

Rewrite installable dependency specs

Replace any workspace:* or catalog: dependency with a real semver before publish. First-party Releases run bun run prepare-publish for this; third-party authors must do it themselves.
4

Publish

First-party Modules ship through Changesets with npm provenance (bun run release). If you publish your own, that release and security process is yours to run.
Then anyone installs it with:
Publishing puts your package on npm. It does not add it to the first-party registry, and it says nothing about compatibility or maturity. Anyone installing it should read it first, and so should you.