Skip to content

Configuration reference

Configure host-owned commerce services, drivers, jobs, payments, shipping, tax, and model overrides.

node ace configure @adocommercekit/core publishes config/commerce.ts. The file is host-owned, loaded by the Commerce service provider, and checked by TypeScript through defineConfig().

import { defineConfig } from "@adocommercekit/core";

Run node ace commerce:doctor after changing configuration. Application boot validates the normalized config and model registry; doctor then checks required tables, payment driver capabilities, and inventory reservation totals.

Complete shape

export default defineConfig({
  tablePrefix: "commerce_",
  channels: { default: "web" },
  currencies: {
    supported: ["USD"],
    default: "USD",
    exponents: {},
    rounding: "half_even",
  },
  customers: {
    userModel: null,
    userForeignKey: "user_id",
  },
  carts: {
    tokenStrategy: sessionCartToken(),
    abandonedAfterMinutes: 4320,
    guardMaxLineQuantity: 999,
  },
  pricing: {
    resolver: defaultPriceResolver(),
    taxInclusivePricing: false,
  },
  images: {
    storage: adonisDriveProductImages('fs'),
    maxBytes: 4 * 1024 * 1024,
    maxPerProduct: 50,
  },
  inventory: {
    reservationTtlMinutes: 30,
    backorderPolicy: "deny",
    allocation: singleLocationAllocation(),
  },
  payments: {
    default: "manual",
    methods: { manual: manualPayment() },
    captureMode: "automatic",
    deferred: { inventoryPolicy: "reserve" },
    // codFee: { amount: 500, currency: "USD", label: "COD fee" },
  },
  shipping: {
    calculators: { flat: flatRateShipping() },
    eligibility: allEnabledShippingEligibility(),
    quoteProviders: {},
    // selectionSecret: env.get("COMMERCE_SHIPPING_SELECTION_SECRET"),
  },
  fulfillment: {
    providers: {},
  },
  taxes: {
    calculator: zoneTaxCalculator(),
  },
  orders: {
    autoConfirm: true,
  },
  models: {},
  events: {
    outbox: { enabled: false, drainBatchSize: 100 },
  },
  jobs: {
    dispatcher: "inline",
  },
});

The sample shows normalized defaults plus the required payment and shipping registrations. You may omit optional keys from your actual file.

Core settings

Key Default Notes
tablePrefix commerce_ Prefix baked into the published migrations and used by models. Choose it before the first migration; changing it later does not rename existing tables.
channels.default Required Code of the channel used when an operation does not select one explicitly. The demo seeder creates web.
currencies.supported Required Allowed ISO 4217 currency codes. Values are normalized to uppercase and deduplicated.
currencies.default Required Default currency; must be present in supported.
currencies.exponents ISO 4217 exponent Per-currency override of 0, 2, or 3 decimal places. Stored money remains integer minor units.
currencies.rounding half_even Global money rounding mode: half_even or half_up.

Table prefix changes

The prefix is rendered into migrations when the configurator publishes them. Do not change tablePrefix after applying those migrations unless a host migration renames every Adocommerce Kit table and constraint consistently. commerce:doctor can detect missing tables, but it cannot perform that rename.

Customers

Key Default Notes
customers.userModel Required; generated as null Lazy import of the host user model, or null for guest-only customer records.
customers.userForeignKey user_id Host user identifier field represented on a commerce customer.

To connect customers to an Adonis user model:

customers: {
  userModel: () => import('#models/user'),
  userForeignKey: 'user_id',
},

The storefront order controller authorizes an authenticated order read only when the order customer has a matching userId. Adapt the published controller if your authentication contract differs.

Carts and pricing

Key Default Notes
carts.tokenStrategy sessionCartToken() Resolves anonymous carts from the HTTP context. Use headerCartToken() for non-session clients.
carts.abandonedAfterMinutes 4320 Active-cart inactivity threshold used by the abandoned-cart maintenance job.
carts.guardMaxLineQuantity 999 Service-level maximum line quantity. Keep published HTTP validators aligned if you change it.
pricing.resolver defaultPriceResolver() Chooses a price by channel, customer group, currency, time window, and quantity.
pricing.taxInclusivePricing false Whether listed prices already include tax.

Header-token example:

import { headerCartToken } from '@adocommercekit/core/drivers'

carts: {
  tokenStrategy: headerCartToken({ header: 'x-cart-token' }),
},

Review the factory signature in the generated API before changing factory options.

Product images

Product image persistence is part of the core catalog, but uploads remain disabled until the host supplies storage. The built-in adapter uses the host application’s AdonisJS Drive configuration:

pnpm add @adonisjs/drive
node ace configure @adonisjs/drive
import { adonisDriveProductImages } from '@adocommercekit/core/drivers'

images: {
  storage: adonisDriveProductImages('fs'),
  maxBytes: 4 * 1024 * 1024,
  maxPerProduct: 50,
},
Key Default Notes
images.storage none Product image storage driver. Without it, upload endpoints and the panel Images tab are disabled.
images.maxBytes 4 MiB Maximum decoded upload size enforced again by the core service.
images.maxPerProduct 50 Maximum persisted images per product; must be an integer from 1 through 1,000.

The published admin surfaces accept one through four JPEG, PNG, or WebP files per request, each up to 4 MiB. The core service inspects the bytes instead of trusting the filename or multipart content type. Images have stable ordering, accessible alternative text, and optional many-to-many variant assignments. Storefront transformers return ordered image URLs and variant ids; admin transformers also return the Drive disk/key, byte size, metadata, and timestamps.

The fs disk and local file serving are suitable for development. In production, point the adapter at a public S3-compatible, R2, GCS, or other Drive disk backed by durable object storage and a CDN. Database writes and object writes are coordinated: failed transactions remove new objects, while committed image or product deletion removes managed objects after commit.

Inventory

Key Default Notes
inventory.reservationTtlMinutes 30 Reservation lifetime and stuck-checkout reconciliation threshold.
inventory.backorderPolicy deny deny, allow, or per_item.
inventory.allocation single-location allocation Selects stock locations for reservations.

The built-in singleLocationAllocation() keeps allocation deterministic and does not split a line across locations. Implement the allocation port when fulfillment requires different behavior.

Payments

Key Default Notes
payments.default Required Key in payments.methods.
payments.methods Required Map of payment method codes to driver factories.
payments.captureMode automatic automatic captures an authorized payment during checkout; manual leaves it authorized for a later capture.
payments.deferred.inventoryPolicy reserve reserve holds stock until settlement; commit decrements at checkout and compensates on expiry.
payments.codFee none Optional amount/currency/label persisted as a cod_fee order adjustment for COD methods.

Local manual and COD payments:

import { cashOnDelivery, manualPayment } from '@adocommercekit/core/drivers'

payments: {
  default: 'manual',
  methods: {
    manual: manualPayment(),
    cod: cashOnDelivery({ expiresAfterDays: 30 }),
  },
  deferred: { inventoryPolicy: 'reserve' },
  codFee: { amount: 500, currency: 'USD', label: 'COD fee' },
},

Deferred providers declare capabilities().flow === 'deferred', return an expiry and generic payment instructions, and implement retrieveIntent() for expiry revalidation. CommerceManager.capabilities.require(COMMERCE_CAPABILITIES.deferredPayments) is the supported runtime compatibility check. Provider-backed methods should read secrets from Adonis environment validation. See Stripe setup.

Regional deferred payments use exponent-0 IDR by default and one configured method per channel. The explicit exponent below is optional but keeps the market assumption visible. Gateway minimums and enabled channels are volatile provider-account constraints: validate them against current official documentation and credentials instead of hard-coding them in core.

import { midtrans } from '@adocommercekit/midtrans'
import { xendit } from '@adocommercekit/xendit'
import { kodeUnik } from '@adocommercekit/id'

currencies: {
  supported: ['IDR'],
  default: 'IDR',
  exponents: { IDR: 0 },
},
payments: {
  default: 'midtrans_qris',
  methods: {
    midtrans_qris: midtrans({
      serverKey: env.get('MIDTRANS_SERVER_KEY'),
      clientKey: env.get('MIDTRANS_CLIENT_KEY'),
      environment: env.get('MIDTRANS_ENVIRONMENT'),
      channel: 'qris',
    }),
    midtrans_bca: midtrans({
      serverKey: env.get('MIDTRANS_SERVER_KEY'),
      clientKey: env.get('MIDTRANS_CLIENT_KEY'),
      environment: env.get('MIDTRANS_ENVIRONMENT'),
      channel: 'bca_va',
    }),
    xendit_qris: xendit({
      secretKey: env.get('XENDIT_SECRET_KEY'),
      webhookToken: env.get('XENDIT_WEBHOOK_TOKEN'),
      channel: 'QRIS',
    }),
    kode_unik: kodeUnik({
      receivingAccount: {
        id: env.get('COMMERCE_ID_BANK_ACCOUNT_ID'),
        bankName: env.get('COMMERCE_ID_BANK_NAME'),
        accountNumber: env.get('COMMERCE_ID_BANK_ACCOUNT_NUMBER'),
        accountName: env.get('COMMERCE_ID_BANK_ACCOUNT_HOLDER'),
      },
    }),
  },
  deferred: { inventoryPolicy: 'reserve' },
},

All Midtrans method instances share provider code midtrans; all Xendit instances share xendit. The webhook endpoint uses that provider code and the payment service resolves the event across configured channel method keys by provider intent ID. Keep credentials identical across method instances sharing a provider code. commerce:doctor calls each provider’s credential/schema health check and still rejects a production deferred configuration without a durable queue.

Kode-unik confirmation and rejection are host-owned admin actions. Construct KodeUnikAdminService with a Bouncer-backed authorizer; unauthorized calls fail before lease or payment mutation. Unsupported automatic refunds throw typed errors with the required operator action instead of pretending an API refund exists.

Shipping, fulfillment, and tax

Key Default Notes
shipping.calculators Required Named static calculator factories referenced by shipping methods.
shipping.eligibility all enabled methods Filters static methods before rates are calculated.
shipping.quoteProviders {} Dynamic multi-service quote provider factories. Empty preserves the existing static flow.
shipping.selectionSecret none At least 32 characters when quote providers are configured; signs complete quote selections.
fulfillment.providers {} Providers used by explicit shipment preparation, waybill, handoff, tracking, and return flow.
taxes.calculator zoneTaxCalculator() Calculates tax from configured zones and rates.
taxes.addressResolver none Optional strategy that decides which address drives tax.

Built-in shipping factories are flatRateShipping(), perItemShipping(), and freeShippingOver(). Dynamic providers return multiple service drafts; the service signs provider, service, amount, currency, expiry, cart lines, address, complete parcel measurements, COD, and insurance into the selection token. Checkout rejects an expired, replaced, tampered, or input-mismatched token.

Fulfillment waybills are created by FulfillmentService.prepare(), never as a side effect of handoff. handoff() is separate; delivery_failed keeps stock unavailable, and only returned permits restocking. The default tax calculator matches country, region, postal code, and tax category, choosing the most specific zone.

Indonesia S3 drivers

IDR is exponent zero by default; an explicit override is still accepted for hosts that keep all market assumptions visible. Configure the PPN and logistics drivers through the existing generic ports:

import { biteship } from '@adocommercekit/biteship'
import { ppnCalculator } from '@adocommercekit/id'
import { rajaOngkir } from '@adocommercekit/rajaongkir'

const biteshipDriver = biteship({
  apiKey: env.get('COMMERCE_BITESHIP_API_KEY'),
  origin: {
    areaId: env.get('COMMERCE_BITESHIP_ORIGIN_AREA_ID'),
    address: 'Host-owned warehouse address',
    contactName: 'Merchant',
    contactPhone: '628...',
  },
  couriers: ['jne', 'sicepat', 'gojek'],
  // Match the verifier to the official webhook contract pinned by the host.
  webhookVerifier: verifyPinnedBiteshipWebhook,
})

shipping: {
  quoteProviders: {
    biteship: biteshipDriver,
    rajaongkir: rajaOngkir({
      apiKey: env.get('COMMERCE_RAJAONGKIR_API_KEY'),
      originId: env.get('COMMERCE_RAJAONGKIR_ORIGIN_ID'),
      couriers: ['jne', 'sicepat'],
    }),
  },
  selectionSecret: env.get('COMMERCE_SHIPPING_SELECTION_SECRET'),
},
fulfillment: {
  providers: { biteship: biteshipDriver },
},
taxes: { calculator: ppnCalculator() },

Pass the signed quote’s selected service when preparing a Biteship shipment:

await fulfillment.prepare(shipment, 'biteship', {
  service: 'jne:reg',
  insurance: { amount: Money.of(100_000n, 'IDR') },
})

node ace configure @adocommercekit/id publishes config/commerce_id.ts, host-owned region validator/controller/routes, and additive migration stubs. Address mode is inert until addressMode: true. Region delivery is importer-only:

node ace commerce:id:regions-import --file=storage/regions-licensed.json
node ace commerce:id:ppn-audit --month=2026-07 --output=storage/ppn-2026-07.csv
node ace commerce:id:cod-import --file=storage/courier-remittance.csv

The region document must include provenance, HTTPS source/license URLs, version, retrieval date, SHA-256 checksum, corrections, and the province → city → district → village records the host is entitled to process. Postal codes are derived only for a unique mapping. Unmatched telemetry receives identifiers and a reason, never raw address lines.

config/commerce_id.ts also owns the Bouncer-compatible PDP authorizer and optional retention schedule. Consent receipts are append-only and purpose/notice-versioned; withdrawal immediately suppresses @adocommercekit/whatsapp. Retention supports dry-run planning, idempotent execution, and active legal holds. See the PDP operations runbook.

WhatsApp remains disabled until the host pins a Graph API version, consent notice version, approved templates, a minimized message resolver, and—only when desired—a fallback notifier. Production retries require jobs.dispatcher: 'queue'.

Implementation pins and revalidation sources, retrieved 2026-07-24:

Provider channel availability, credentials, webhook contracts, statutory interpretation, dataset rights, and template approval are release-time checks; source implementation does not close those operational gates.

Orders and model overrides

Key Default Notes
orders.numberGenerator built-in generator Produces order numbers. Replace it only with a concurrency-safe implementation.
orders.autoConfirm true Confirms an order after successful checkout finalization.
models {} Lazy imports overriding individual Lucid models in the model registry.

Do not add model overrides manually unless the subclass and migration already exist. Publish the supported baseline with:

node ace commerce:extend product

Then add host columns in a separate host migration. See Extend a model.

Events and jobs

Key Default Notes
events.outbox.enabled false Persists supported transactional events for asynchronous delivery.
events.outbox.drainBatchSize 100 Maximum records processed by one drain.
jobs.dispatcher inline inline uses process timers; queue adapts @adonisjs/queue.

At provider startup Adocommerce Kit schedules six maintenance jobs:

Schedule Job
Every minute Expire and reconcile pending payments
Every 5 minutes Expire stale inventory reservations
Hourly Mark inactive carts abandoned
Every 5 minutes Reconcile stuck checkouts
Every minute Drain the transactional event outbox
Daily Purge expired idempotency keys

inline is convenient for development and a single process. Use queue for a multi-instance production deployment so scheduling and work ownership are centralized:

jobs: { dispatcher: 'queue' },
events: { outbox: { enabled: true, drainBatchSize: 100 } },

@adonisjs/queue is an optional peer dependency and currently pre-1.0. Install and configure a compatible version before selecting queue; application boot, including commerce:doctor, fails when the queue integration cannot load.

commerce:doctor rejects production deferred-payment configuration unless jobs.dispatcher resolves to queue. The inline dispatcher remains development/test-only for expiry, reconciliation, webhook side effects, and retries.

Driver factories

Driver entries are factories, not driver instances. A factory receives the Adonis application service and may return synchronously or asynchronously:

import type { ApplicationService } from "@adonisjs/core/types";
import type { DriverFactory } from "@adocommercekit/core";

const customDriver: DriverFactory = async (app: ApplicationService) => {
  const dependency = await app.container.make(SomeDependency);
  return new CustomDriver(dependency);
};

Adocommerce Kit resolves and caches configured drivers during boot. Avoid request-scoped state inside a driver instance.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close