A module adds behaviour the platform does not ship to a project: a carrier, an invoicing, payment or order-fulfilment provider, a booking system, a sync with a CRM or a newsletter tool, an admin page or widget of your own. Modules are installed per project from the console's Modules area, and every module — yours or the platform's — goes through the same contract.
This reference is that contract. It is written for module authors — a
developer building a module for their own projects or for a client — and
for the project managers who install, configure and update modules
(§8 Lifecycle, §9 The console). Each section stands on its own; a §n
reference points at the page of the same number in this guide.
What a module can do
- Check or annotate an order at checkout, before it is written
(
order.validate, §4). - Plug into a whole core contract as a carrier, an invoicer, a
payment gateway or an orders fulfilment provider — the project
then picks the module wherever it would pick a built-in provider
(
provides, §2.4). - Receive the project's writes as signed events, retried when a delivery fails
(§6), and third-party webhooks through the platform without exposing its
own address (
routes.inbound, §4). - Serve endpoints on the project's own website under
/m/{id}/**(routes.web, §4). - Add configuration fields, forms, buttons, cards, full admin pages and Overview widgets to the console (§4.2, §4.3).
- Keep its own content collections, private records and state (§2.1, §5.5), ship element kits, e-mail templates and translations (§7), and run on a schedule (§4).
Where it runs — the three hosting tiers, compared in §1: connected
(your own HTTPS endpoint — or Cloud Functions the platform deploys into your
space's own Firebase project for you, §8.7), hosted (a Wasm package on the
platform's module runner — you ship code, not a server, §8.8) and platform
(the same format, published by the platform for every project).
Modules or services. A module is a product projects install. For a
small backend that belongs to one site — a few routes calling another
system with a key — write a store service instead: one JS
file at /fn/{id}/**, no manifest, no install.
Before you start
- A project's plan decides whether it may install modules, which tiers, and how many (§5.1); a refused install names the plan and the limit.
hostedandplatformmodules need the environment's module runner; where none is configured, installing one answershosting_unavailable.- A developer with the
developerplatform role submits a package for their organization and it waits in review until a platform admin approves it; a platform admin's upload is published at once (§7.2 "Publishing"). - Build and check a package locally first — the starter's
module:packruns the same checks as the upload (§7.2, §7.3), and §11 shows how to test a module end to end.
Contents: 1 What a module is · 2 The manifest · 3 The envelope · 4 Points · 5 Identity and access · 6 Events and retries · 7 Packages · 8 Lifecycle · 9 The console · 10 Where things live · 11 Testing a module locally.
1 What a module is#
A module is a manifest (ModuleManifest, core-lib
entities/module.ts) plus code somewhere. The manifest declares what the
module uses; the install decides what it gets. Where the code runs is the
hosting tier:
| Tier | Who writes the code | Where it runs | How the platform reaches it | How it reaches the platform |
|---|---|---|---|---|
connected |
the client (or an agency) | their own Firebase project as Cloud Functions gen2, or any HTTPS host of theirs | signed POST {endpoint}{path} (the envelope, §3) |
the SDK with the install's scoped store token |
hosted |
the client | a Wasm package on the platform's module runner (a separate project, drop-core-modules) |
the gateway invokes the runner with the envelope + the install token | host functions only — the token is injected per call; the module never sees it |
platform |
the platform | the same runner, same package format, published by master / administrator, run with the platform limits profile |
the gateway invokes the runner | host functions, with a platform-profile token per install |
none — an elements module |
the client, an agency or the platform | nowhere on the platform: its kits' elements and global css / js run in the shopper's browser on the pages that place them | never — no endpoint, no runner; every routing entry point answers 404 module_static |
never — it has no token (§2.1 "hosting: 'none'") |
One gateway in the core API makes the tiers interchangeable: a store
installs a module without knowing which tier it is; a client module can move
between hosted and connected (the starter ships an Express router and
an Extism entry over the same handler object); a platform module is a client
module with a different publisher and profile. Every platform → module call
crosses the gateway; every module → platform call is an ordinary API call on
the install token, evaluated like any other store token.
A connected module is therefore: an HTTPS endpoint that verifies the
envelope on every request, a handler per point it declares (§4), and the SDK
(drop-core-sdk) started with the install token it received at install
(§5). The package it ships is usually manifest.json alone (§7).
An elements module (hosting: 'none') is the opposite end: a package of
element kits plus their shared global css / js and nothing else — no code
the platform runs, no token, no events, no config, no console pages. A store
mounts it from the catalog and gets the elements in its library; updates and
uninstalls work as for every module-owned element (§8.2a). It needs no
modules plan feature and counts against its own cap (§8.2).
What a module can never do (the security model's checklist, in author terms):
- Run in a process or project that holds a master token — never the console
server, never the API function, never the tenant fleet. A
connectedmodule is an untrusted, internet-reachable endpoint from the platform's point of view, even when it is deployed into the organization's own cloud. - Hold a token that is not
role: service,revealable: false,access ∩ MODULE_MAX_ACCESS, with a validatedscopeand nocustomer(a module never spends a wallet: every paid route answers 400). - Read another party's secrets: with
secure.secretgranted, a module token reachesmodule_{id}_*keys only andkey: '*'is refused. - Reach
setup/hooks,setup/smtp,setup/payments,setup/analytics,members,promo/incoreorsystem/**— the hard deny list (§5). - Write outside its namespace:
modules/{id}/**,{entity}.modules.{id}, the collections it registered (taggedmodule), files underfiles/modules/{id}/**. - Receive the install token before its endpoint is proven (the two-step handshake, §4), or have the platform fetch anything from its endpoint to decide what it may do — the manifest comes only from the uploaded package.
- Change its own grants: a version whose
access/scopewiden waits for a store manager's explicit accept (§8). - Escape the gateway's deadlines, size caps, SSRF guard and breaker (20
consecutive failures on a point →
health.failing, the point short-circuits to its fallback). - Have its
/secure/accesstoken edited or deleted directly: managed tokens (noteMODULE - {id}) change only through install / reroll / uninstall (409 managed_token).
Every install / configure / enable / uninstall / reroll / update and every
declarative action lands in the store's activity feed (module id + path +
entity id, never values); every gateway call is a drop.stat row.