@dashcommerce/core
v0.1.4
Published
DashCommerce — full WooCommerce-equivalent commerce plugin for EmDash CMS
Downloads
599
Maintainers
Readme
@dashcommerce/core
The e-commerce plugin for EmDash CMS.
Current npm version: 0.1.2. See the monorepo README for the full feature list and quick start, or dashcommerce.dev/docs for guides. Live demo: demo.dashcommerce.dev.
Package exports
| Entry | Context | Purpose |
|---|---|---|
| . | Vite / build time | Descriptor factory (dashcommerce()) — imported in astro.config.mjs. Side-effect-free. |
| ./sandbox | Runtime server | definePlugin({ hooks, routes }) loaded by the emdash runtime. Sandbox-safe. |
| ./admin | Browser | React admin pages + dashboard widgets + field widgets. |
| ./astro | Host SSR | blockComponents registry for Portable Text block renderers. |
| ./astro/components/* | Host SSR | Storefront Astro components (<ProductCard>, <CartSummary>, etc.). |
| ./astro/islands/* | Host SSR + client | React islands (CartDrawerIsland, CheckoutIsland, etc.). |
Install
Four steps to add DashCommerce to an existing EmDash + Astro site:
bun add @dashcommerce/coreRegister the plugin in astro.config.mjs:
import { dashcommerce } from "@dashcommerce/core";
emdash({ plugins: [dashcommerce()] });Merge the products collection + taxonomies into your seed file, then re-apply:
bunx dashcommerce-merge-seed
bun emdash seed --on-conflict=updateOpen /_emdash/admin/plugins/dashcommerce/settings and paste your Stripe test keys.
Want sample data? Add --with-demo-catalog to the merge step to seed six demo products (one per product type) plus product_category and product_tag terms, so the admin isn't empty on first boot:
bunx dashcommerce-merge-seed --with-demo-catalog
bun emdash seed --on-conflict=updateRe-running is safe — existing products with matching ids are preserved. Swap or delete the demo entries any time from the Products admin.
For a fully-wired reference setup (seed data, storefront pages, Stripe keys), see @dashcommerce/starter. Full walkthrough: dashcommerce.dev/docs/getting-started.
Seed merge CLI
@dashcommerce/core ships a dashcommerce-merge-seed binary (on your PATH via node_modules/.bin). It writes the products collection and product_category / product_tag taxonomies into your EmDash seed.json, replacing any prior DashCommerce entries by slug / name. Unrelated keys in the seed file (settings, content, menus, other collections) are left intact.
# Typical one-liner after emdash init (fresh DB)
bunx dashcommerce-merge-seed && bun emdash seed --on-conflict=updateOr add to package.json:
"scripts": {
"bootstrap": "dashcommerce-merge-seed && bun emdash seed --on-conflict=update"
}Why --on-conflict=update? On a first run it's a no-op, but subsequent runs (after a DashCommerce release that tweaks the products schema) need it to update the collection in place rather than skipping it.
Options: --seed <path> (override file), --cwd <dir>, --with-demo-catalog (append 6 demo products + curated taxonomy terms). When --seed is omitted the CLI uses the same default resolution as emdash seed: .emdash/seed.json if present, otherwise package.json → emdash.seed.
Prefer to assemble the seed in TypeScript? Import mergeDashCommerceSeed(seedObject, { withDemoCatalog }) from the package root — same dedupe rules as the CLI. The demo catalog is also exported directly as DEMO_PRODUCTS, DEMO_PRODUCT_CATEGORY_TERMS, and DEMO_PRODUCT_TAG_TERMS for cherry-picking.
Runtime surface
Public routes (storefront)
All paths mount under /_emdash/api/plugins/dashcommerce/.
GET /cart Current cart (creates if missing; re-runs recalculate)
POST /cart/items Add line item
PATCH /cart/item?lineId={id} Update qty
DELETE /cart/item?lineId={id} Remove line
DELETE /cart/clear Empty cart (keeps session)
POST /cart/currency Switch currency (empty cart only)
POST /cart/coupon Apply coupon
DELETE /cart/coupon/remove?code={code} Remove coupon
POST /cart/shipping-location Set country + postal (hosted checkout pre-flight)
POST /cart/shipping-address Full address (embedded checkout)
POST /cart/contact Email + billing address
POST /cart/shipping-methods List rates for current ship-to
POST /cart/shipping-method Choose method id
GET /cart/restore?token={t} Abandoned-cart recovery link
POST /checkout/create-intent Embedded Elements: create PaymentIntent
POST /checkout/create-session Hosted Checkout: create Checkout Session
GET /checkout/mode Which mode is active (hosted | embedded)
POST /checkout/webhook Stripe webhook sink (signature-verified)
GET /orders/by-draft?id={draftId} Poll order status after checkout
GET /downloads/serve?token={t} Signed-URL download delivery
GET /reviews/list?productId={id} Paginated reviews
POST /reviews/item Submit a review (moderation queue)
GET /config-check Read-only health check (Stripe keys present, etc.)Admin-gated routes
Same prefix, gated by emdash's admin auth:
GET /admin/settings Read all settings:* keys
POST /admin/settings Write (validated via schema)
POST /admin/stripe/ping Live-key sanity check
GET /admin/orders, /admin/orders/:id Order list + detail
POST /admin/orders/:id/refund Full or partial refund
PATCH /admin/orders/:id/status Transition order status (triggers review-request etc.)
GET /admin/customers … Customer CRUD
GET /admin/reports/revenue Range-sliced revenue with comparison
GET /admin/reports/top-products Best sellers by qty or revenue
GET /admin/reports/top-customers Lifetime-value leaderboard
GET /admin/reports/mrr Active subscription MRR by currency
… plus coupons, vendors, shipping zones/methods, tax rates, subscriptions, reviews moderation, menu, customer-portal-sessionHooks
content:beforeSaveon theproductscollection — field validation (price map, variants, stock, shipping class)cron— stock-lock sweep (5 min), abandoned-cart reminders (hourly), subscription renewals dry-checkplugin:install— seed default settings (taxMode=flat, currencies, download TTLs, etc.)plugin:activate— assert Stripe keys present; emit config warnings if not
Plugin storage collections
19 collections declared in storage-collections.ts:
orders · order_items · order_refunds · customers · customer_addresses
product_variants · inventory_ledger · stock_locks
coupons · coupon_usages · shipping_zones · shipping_methods · shipping_classes
tax_rates · reviews · subscriptions · vendors · vendor_payouts · download_grantsPlus one content collection declared by the host via defineProductsCollection():
products (lives on host content, not plugin storage — emdash restriction)KV namespaces
Prefixes ctx.kv uses under the hood:
settings:* plugin configuration (tax mode, currencies, Stripe keys, etc.)
cart:{sessionId} active carts (30-day TTL via cookie)
lock:{productId}:{variant} stock soft-locks during checkout
abandoned:{sessionId} abandoned-cart marker (sent / not sent)
email-throttle:{category}:* per-customer email de-duplication keysAdmin surface
React pages served at /_emdash/admin/plugins/dashcommerce/*:
- Orders (list, detail, refund, status transitions)
- Customers (list, detail, order history)
- Coupons (CRUD + usage reports)
- Shipping (zones + methods + classes)
- Tax (mode + flat rate + rate table + Stripe Tax status)
- Subscriptions (list, detail, pause/resume, invoice history)
- Reviews (moderation queue, bulk approve/reject)
- Vendors (invite, onboarding status, payout history)
- Menus (storefront nav editor, nested up to 4 levels)
- Reports (revenue + top products + top customers + MRR)
- Settings (all
settings:*keys with per-key validation) - Dashboard widgets: Revenue, Low Stock, Recent Orders, Pending Reviews, Failed Renewals
Field widgets (for use in any collection via widget: "dashcommerce:<name>"):
vendor-select— picks a vendor fromvendorsstorageprice-map— multi-currency price input, respectssettings:enabledCurrencies
Portable Text blocks (for any rich-text field):
product-embed— single product card by slugproduct-grid— N products from a category slugreview-quote— inline quote of an approved review
Events & emails
The plugin sends these transactional emails via ctx.email.send (HTML + plain text):
| Event | Template | Triggered by |
|---|---|---|
| Order receipt | emails/receipt | Webhook charge.succeeded / checkout.session.completed |
| Refund | emails/refund | Webhook charge.refunded or admin refund POST |
| Subscription renewed | emails/subscription-renewed | Webhook invoice.payment_succeeded (cycle only) |
| Dunning | emails/dunning | Webhook invoice.payment_failed |
| Abandoned cart | emails/abandoned-cart | Hourly cron, per settings:abandonedCartDelayHours |
| Review request | emails/review-request | Order status → completed on physical items |
| Vendor invite | emails/vendor-onboarding | startOnboarding() in vendors/onboarding.ts |
| Vendor activated | emails/vendor-onboarding | Webhook account.updated when chargesEnabled flips true |
| Vendor payout | emails/vendor-payout | Webhook payout.paid |
All senders are idempotent via ctx.kv de-dup keys.
Development
bun install
bun run typecheck
bun run build # tsdown bundles index + sandbox-entry + admin/entry
bun run dev # tsdown --watch
bun test # 66 tests across money / cart / coupons / webhook / tokens / splitsrc/astro/ is shipped as source — it is NOT bundled. It imports .astro components that rolldown can't compile; the host's Astro build resolves the imports at consumer build time.
License
MIT
