@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
modules/my-feature/:2
Declare storage
Every Module declares Wire
storage: { kind: "none" }, storage: { kind: "config", ... }, or storage: { kind: "relational", ... }. Relational field names are locked: tables, extends, anchors, and publishes.src/schema.ts
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
@86d-store/my-feature to your active Template’s config.json.5
Regenerate
/api/, and its components are in the MDX registry.6
Run the tests
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
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 withsubgroup 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. Legacyexports and requires field metadata remains during migration; it does not grant table access.
Reaching the database
Database access goes throughModuleDataService 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 compileddist/ (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 includesrc,.turbo, or vitest config)"publishConfig.exports"mapping every public entry to./dist/*.jsand./dist/*.d.ts- A
buildthat runstscwithrootDir: "src",outDir: "dist",declaration: true, and copies non-TS assets (for example.mdx) intodist
2
Build
"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
bun run release). If you publish your own, that release and security process is yours to run.