Last Updated: January 2026
Status: Authoritative reference for order routing
Swyft supports two independent order routing approaches. Brands can use either one based on their fulfillment setup:
| Approach | WMS Required? | Best For |
|---|---|---|
| Shopify-Native Routing | No | Brands using Shopify's built-in fulfillment locations |
| Trackstar-Based Routing | Yes | Brands with 3PL/WMS integration via Trackstar |
Routes orders by moving Shopify Fulfillment Orders to different Shopify Locations. No external WMS connection required.
Order Arrives
↓
[KV Lookup] Postal Code → Coordinates (44k+ US/CA, sub-5ms)
↓
[Haversine] Calculate distance to each location (< 1ms)
↓
[DB Query] Check inventory at nearby locations
↓
[Score] Rank by: Inventory (40%) + Distance (35%) + Cost (15%) + Priority (10%)
↓
[Select] Best location that CAN FULFILL the order
↓
Route Order
Total latency target: 30-50ms (well within Cloudflare Workers limits)
fulfillmentOrderHoldshopify_inventory for all line item SKUsfulfillmentOrderMove GraphQL mutationfulfillmentOrderReleaseHold mutation| Component | Purpose |
|---|---|
fulfillment_locations table |
Stores Shopify Locations for routing |
shopify_inventory table |
Inventory levels by location/variant |
shopify-routing-pipeline.ts |
Main routing service |
geocoding.ts |
KV-backed postal code geocoding |
POSTAL_CODES_KV |
Cloudflare KV namespace for geocoding |
routeShopifyNativeOrders() |
Entry point function |
shopify_native_routing flag |
Feature flag in feature_flags table |
The routing system uses Cloudflare KV for sub-5ms postal code lookups. No external API calls required.
Supported Geographies:
KV Key Structure:
US ZIP: "90210" → {"lat": 34.0901, "lng": -118.4065, "city": "Beverly Hills", "state": "CA"}
US ZIP3: "ZIP3:902" → {"lat": 34.05, "lng": -118.24, "state": "CA"}
Canada FSA: "CA:M5V" → {"lat": 43.6426, "lng": -79.3871, "city": "Toronto", "province": "ON"}
Lookup Order:
Each fulfillable location is scored using these factors:
| Factor | Weight | Description |
|---|---|---|
| Inventory | 40% | 1.0 if can fulfill all items, 0.0 otherwise |
| Distance | 35% | Closer = higher score (exponential decay) |
| Cost | 15% | Zone-based shipping cost estimate |
| Priority | 10% | Merchant-configured location priority (1-10) |
Distance Scoring:
// Exponential decay: closer locations score much higher
const distanceScore = Math.exp(-distance / 500);
// 0 miles = 1.0, 500 miles = 0.37, 1000 miles = 0.14
Cost Scoring (Zone-Based):
// Zone 2 (≤50mi) → Score: 1.0
// Zone 3 (≤150mi) → Score: 0.857
// Zone 4 (≤400mi) → Score: 0.714
// Zone 5 (≤600mi) → Score: 0.571
// Zone 6 (≤1000mi)→ Score: 0.429
// Zone 7 (≤1400mi)→ Score: 0.286
// Zone 8 (>1400mi)→ Score: 0.143
When no single location can fulfill an entire order:
This minimizes shipping complexity while ensuring orders get fulfilled.
-- Fulfillment locations (Shopify Locations for routing)
CREATE TABLE fulfillment_locations (
id UUID PRIMARY KEY,
shop_id UUID REFERENCES stores(id),
shopify_location_id TEXT NOT NULL,
shopify_location_gid TEXT NOT NULL, -- e.g., "gid://shopify/Location/123"
name TEXT,
address TEXT,
city TEXT,
state TEXT,
postal_code TEXT,
country TEXT DEFAULT 'US',
latitude DOUBLE PRECISION, -- For distance calculations
longitude DOUBLE PRECISION, -- For distance calculations
priority INTEGER DEFAULT 5, -- 1-10, higher = preferred
zone_coverage JSONB, -- {"2day": ["CA", "NV"], "ground": ["*"]}
is_active BOOLEAN DEFAULT true,
fulfills_online_orders BOOLEAN DEFAULT true,
-- Optional: Can link to Trackstar warehouse if using both approaches
mapped_warehouse_id UUID REFERENCES trackstar_warehouses(id)
);
-- Inventory levels by location/variant
CREATE TABLE shopify_inventory (
id UUID PRIMARY KEY,
shop_id UUID REFERENCES stores(id),
location_id UUID REFERENCES fulfillment_locations(id),
shopify_inventory_item_id TEXT NOT NULL,
sku TEXT,
available INTEGER DEFAULT 0,
updated_at TIMESTAMP WITH TIME ZONE
);
async function routeOrder(order: Order, locations: FulfillmentLocation[], env: Env) {
// 1. Get destination coordinates from postal code
const destCoords = await getCoordinates(order.ship_to_postal_code, env);
if (!destCoords) {
// Hold for manual review - can't geocode destination
return { action: 'hold', reason: 'unknown_postal_code' };
}
// 2. Get inventory for all order SKUs at all locations
const inventory = await getInventoryForOrder(order.shop_id, order.line_items);
// 3. Score each location
const scoredLocations = await scoreLocations(locations, destCoords, order.line_items, inventory);
// 4. Select best location (minimize-splits strategy)
const best = selectBestLocation(scoredLocations);
if (!best) {
return { action: 'hold', reason: 'no_inventory' };
}
// 5. Route to selected location
return { action: 'route', location: best.location, score: best.score };
}
-- Enable Shopify-native routing for a store
INSERT INTO feature_flags (shop_id, flag_name, enabled)
VALUES ('store-uuid', 'shopify_native_routing', true);
Before routing will work, the POSTAL_CODES_KV namespace must be seeded:
# In apps/worker directory
pnpm tsx scripts/seed-postal-codes-kv.ts --env production
This loads 44k+ US and Canadian postal codes into Cloudflare KV.
Routes orders to WMS warehouses via Trackstar's unified API. Requires a Trackstar connection with warehouse sync.
trackstar_warehousestrackstar_inventory| Component | Purpose |
|---|---|
trackstar_warehouses table |
Warehouses from WMS via Trackstar |
trackstar_inventory table |
Inventory levels by warehouse/SKU |
order-router.ts |
Main routing service |
routeOrders() |
Entry point function |
warehouse-sync.ts |
Syncs warehouses from Trackstar |
inventory-sync.ts |
Syncs inventory from Trackstar |
trackstar_connections)trackstar_warehouses populated)trackstar_inventory populated)| Feature | Shopify-Native | Trackstar-Based |
|---|---|---|
| Setup complexity | Low | Medium |
| WMS integration | Not needed | Required |
| Inventory source | Shopify inventory | WMS inventory via Trackstar |
| Geocoding | KV-backed (44k postal codes) | KV-backed (shared) |
| Distance calculation | Haversine (< 1ms) | Haversine (shared) |
| Scoring system | Weighted multi-factor | Transit time matrix |
| Split fulfillment | Minimize-splits strategy | Configurable |
| Best for | Simple to medium fulfillment | Complex multi-3PL networks |
Both routing approaches run on a schedule in the worker:
// apps/worker/src/handlers/scheduled.ts
// Trackstar-based routing (always runs)
await routeOrders(env, posthog);
// Shopify-native routing (feature-flagged)
const shopifyRoutingResult = await routeShopifyNativeOrders(env, posthog);
Brands can start with Shopify-native routing and later add Trackstar for advanced features:
fulfillment_locations.mapped_warehouse_id to Trackstar warehousesFalse. Shopify-native routing works without any WMS. Just needs Shopify Locations configured.
False. Trackstar is only needed for WMS-based routing and inventory sync from 3PLs.
Partially true. Currently, stores use one approach, controlled by feature flag. Future: hybrid routing possible.
| Operation | Latency | Notes |
|---|---|---|
| KV postal code lookup | 1-5ms | Hot reads from Cloudflare edge |
| Haversine calculation | < 1ms | Pure math, no I/O |
| Inventory query | 10-20ms | Supabase query |
| Scoring all locations | < 1ms | Pure math after data fetched |
| Total routing | 30-50ms | Well within 50ms CPU limit |
Scale Capacity: Can handle 500k+ orders/month easily
| Service | Monthly Cost | Notes |
|---|---|---|
| Cloudflare KV | ~$0.50 | 44k keys, read-heavy |
| External geocoding | $0 | All lookups from KV |
| Total | ~$0.50/month | No per-request API costs |
| File | Purpose |
|---|---|
apps/worker/src/services/shopify-routing-pipeline.ts |
Shopify-native routing implementation |
apps/worker/src/services/geocoding.ts |
KV-backed postal code geocoding |
apps/worker/src/services/order-router.ts |
Trackstar-based routing implementation |
apps/worker/src/handlers/scheduled.ts |
Scheduled task runner |
apps/worker/wrangler.toml |
KV namespace configuration |
apps/worker/scripts/seed-postal-codes-kv.ts |
Script to seed postal codes to KV |
supabase/migrations/20260119000000_fulfillment_locations.sql |
Fulfillment locations schema |
supabase/migrations/20260119100000_shopify_native_routing_flag.sql |
Feature flag |
Check geocoding: Verify postal code is in KV
wrangler kv:key get --namespace-id=<id> "90210"
Check inventory: Ensure shopify_inventory has current stock levels
SELECT fl.name, si.sku, si.available
FROM shopify_inventory si
JOIN fulfillment_locations fl ON si.location_id = fl.id
WHERE si.shop_id = 'your-shop-id'
ORDER BY fl.name, si.sku;
Check location coordinates: Verify fulfillment_locations has lat/lng
SELECT name, postal_code, latitude, longitude
FROM fulfillment_locations
WHERE shop_id = 'your-shop-id';
For postal codes not in the database, add them to the KV:
# Single entry
wrangler kv:key put --namespace-id=<id> "12345" '{"lat":40.7128,"lng":-74.0060,"city":"New York","state":"NY"}'
# Bulk update
wrangler kv:bulk put --namespace-id=<id> new-codes.json