spree-shipping-fulfillment
Build and customize Spree's shipping and fulfillment — ShippingMethod, ShippingCategory, Zone/ZoneMember, ShippingRate, the Stock::Estimator service, StockLocation/StockItem/StockMovement, multi-shipment orders, ShippingCalculator classes (FlatRate, FlatPercentItemTotal, PerItem, FlexiRate), shipment state machine, returns (ReturnAuthorization → CustomerReturn → Reimbursement → Refund), and integrating carrier APIs (UPS, FedEx, ShipStation). Use when configuring shipping rules, building fulfillment integrations, or debugging shipping-rate calculations.
Best use case
spree-shipping-fulfillment is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Build and customize Spree's shipping and fulfillment — ShippingMethod, ShippingCategory, Zone/ZoneMember, ShippingRate, the Stock::Estimator service, StockLocation/StockItem/StockMovement, multi-shipment orders, ShippingCalculator classes (FlatRate, FlatPercentItemTotal, PerItem, FlexiRate), shipment state machine, returns (ReturnAuthorization → CustomerReturn → Reimbursement → Refund), and integrating carrier APIs (UPS, FedEx, ShipStation). Use when configuring shipping rules, building fulfillment integrations, or debugging shipping-rate calculations.
Teams using spree-shipping-fulfillment should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/spree-shipping-fulfillment/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How spree-shipping-fulfillment Compares
| Feature / Agent | spree-shipping-fulfillment | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Build and customize Spree's shipping and fulfillment — ShippingMethod, ShippingCategory, Zone/ZoneMember, ShippingRate, the Stock::Estimator service, StockLocation/StockItem/StockMovement, multi-shipment orders, ShippingCalculator classes (FlatRate, FlatPercentItemTotal, PerItem, FlexiRate), shipment state machine, returns (ReturnAuthorization → CustomerReturn → Reimbursement → Refund), and integrating carrier APIs (UPS, FedEx, ShipStation). Use when configuring shipping rules, building fulfillment integrations, or debugging shipping-rate calculations.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Spree Shipping & Fulfillment
## Before writing code
**Fetch live docs**:
1. Fetch https://spreecommerce.org/docs/developer/core-concepts/architecture (shipping section) for the current model graph.
2. Inspect `Spree::Stock::Estimator` and `Spree::Stock::Splitter` in the live `spree` gem source.
3. Check the live `Spree::ShippingMethod` source for column names and associations.
4. For carrier integrations, search community gems (`spree-active-shipping`, `spree-ups`, `spree-fedex`) — verify which are maintained for v5.
5. Check release notes for any shipping-related changes (Markets in v5.4 affect shipping availability per region).
## Conceptual Architecture
### The Core Models
| Model | Purpose |
|-------|---------|
| **`ShippingMethod`** | Named shipping option (UPS Ground, FedEx 2-Day) |
| **`ShippingRate`** | Computed cost option offered to the user during checkout |
| **`ShippingCategory`** | Categorize products by shipping needs (Hazmat, Frozen, Standard) |
| **`Zone`** / **`ZoneMember`** | Geographic regions a method ships to |
| **`Shipment`** | A subset of order items shipped together from one StockLocation |
| **`InventoryUnit`** | One unit of a Variant in a Shipment |
| **`StockLocation`** | Physical/logical warehouse |
| **`StockItem`** | Count of a Variant in a StockLocation |
| **`StockMovement`** | Append-only ledger of stock changes |
| **`StockTransfer`** | Move stock between StockLocations |
### Why Multiple Shipments per Order?
If an order's items span multiple `StockLocation`s, Spree creates one `Shipment` per location. The `Spree::Stock::Splitter` allocates items by stock availability and configured policy (closest, fastest, cheapest, etc.).
### ShippingMethod Availability
A ShippingMethod is offered for a Shipment if:
- The ShippingMethod's Zones include the destination
- The Variants' ShippingCategories overlap with the ShippingMethod's allowed categories
- The ShippingMethod is enabled for the current Store
- (v5.4+) The Market for the destination allows this ShippingMethod
### Shipping Calculators
Polymorphic `Calculator` (same base class as promotion/tax calculators):
| Calculator | Math |
|------------|------|
| `Spree::Calculator::Shipping::FlatRate` | Fixed cost |
| `Spree::Calculator::Shipping::FlatPercentItemTotal` | % of cart |
| `Spree::Calculator::Shipping::PerItem` | Per-item flat |
| `Spree::Calculator::Shipping::FlexiRate` | Tiered by total |
| `Spree::Calculator::Shipping::PriceSack` | Free over threshold |
| `Spree::Calculator::Shipping::DigitalDelivery` | Free for digital |
Carrier-API calculators (UPS, FedEx, USPS) come from community gems — they compute rates by calling the carrier's API live.
### Stock::Estimator
`Spree::Stock::Estimator.new(order).shipping_rates(package)` returns the ShippingRate options for a given Package. Replaceable via `Spree::Dependencies`:
```ruby
Spree::Dependencies.shipping_rate_estimator = MyCustomEstimator
```
This is the canonical extension point for custom shipping logic — preferable to decorating the model.
### Shipment State Machine
```
pending → ready → shipped
↓
canceled
```
- `pending` — awaiting payment / stock
- `ready` — paid and stocked, ready to fulfill
- `shipped` — `shipped_at` set, tracking number captured
- `canceled` — canceled with order
### Returns Flow
```
ReturnAuthorization (authorized | canceled)
└── ReturnItem[]
↓
CustomerReturn (when physically received)
└── ReturnItem[]
↓
Reimbursement (pending | reimbursed | errored)
└── Refund (against original Payment)
OR StoreCredit
```
## Implementation Guidance
### Setting Up Shipping for a New Store
```ruby
# A US zone
us = Spree::Zone.create!(name: 'US')
us.zone_members.create!(zoneable: Spree::Country.find_by(iso: 'US'))
# A shipping category
standard = Spree::ShippingCategory.find_or_create_by!(name: 'Standard')
# A shipping method
method = Spree::ShippingMethod.create!(
name: 'Standard Shipping',
display_on: 'both',
shipping_categories: [standard],
zones: [us],
tax_category: Spree::TaxCategory.find_by(name: 'Shipping')
)
method.calculator = Spree::Calculator::Shipping::FlatRate.new(preferred_amount: 5.00)
method.save!
# A stock location
loc = Spree::StockLocation.create!(
name: 'Main Warehouse',
default: true,
active: true,
country: Spree::Country.find_by(iso: 'US')
)
```
### Customizing Shipping Rate Selection
If you need custom logic (e.g., "always offer free shipping for Gold customers"):
**Option A — Subscribe to events** (preferred when reacting):
```ruby
class FreeGoldShippingSubscriber < Spree::Subscriber
subscribes_to 'order.recalculate'
on 'order.recalculate', :apply_free_shipping
end
```
**Option B — Swap the Estimator** (preferred when modifying rate generation):
```ruby
class MyEstimator < Spree::Stock::Estimator
def shipping_rates(package, frontend_only = true)
rates = super
rates.each { |r| r.cost = 0 } if package.order.user&.gold_tier?
rates
end
end
# config/initializers/spree.rb
Spree::Dependencies.shipping_rate_estimator = MyEstimator
```
### Adding a Carrier API Integration
Community gems for UPS/FedEx/USPS exist; verify v5 compatibility before adoption. The pattern:
1. Add a `Spree::Calculator::Shipping::CarrierName` calculator
2. Calculator's `compute(package)` calls the carrier's rate API
3. Authenticate with carrier credentials stored in Rails credentials
4. Cache rates for the cart's lifetime to avoid repeat API calls
### Tracking Numbers and Webhooks
When the fulfillment provider ships:
```ruby
shipment.update!(tracking: 'TRACKING123', shipped_at: Time.current)
shipment.ship!
# Fires shipment.shipped event → webhook → customer email
```
### Stock Management
```ruby
# Increase stock
stock_item.adjust_count_on_hand(10)
# Decrease (validates sufficient stock)
stock_item.reduce_count_on_hand(2)
# Transfer between locations
Spree::StockTransfer.create!(
source_location: loc_a,
destination_location: loc_b,
stock_movements_attributes: [
{ quantity: 5, stock_item_id: source_item.id }
]
)
```
### Backorders
If `track_inventory` is on and stock hits zero, Spree marks the InventoryUnit as `backordered`. The shipment goes into `backorder` state until stock returns.
To disable backorders globally:
```ruby
Spree::Config[:allow_backorders] = false
```
Verify the exact preference name in current config.
### Returns Workflow
```ruby
# Customer requests return
ra = Spree::ReturnAuthorization.create!(
order: order,
return_items_attributes: items.map { |i| { inventory_unit_id: i.id } },
stock_location: order.shipments.first.stock_location
)
ra.authorize!
# Warehouse receives
cr = Spree::CustomerReturn.create!(
return_items: ra.return_items,
stock_location: ra.stock_location
)
# Reimburse
reimb = Spree::Reimbursement.create!(
customer_return: cr,
order: order,
return_items: cr.return_items
)
reimb.perform! # creates Refunds and/or StoreCredits
```
### Common Pitfalls
- **Missing ShippingCategory** on a Product → no ShippingMethod can match → checkout fails at `delivery`.
- **Zone doesn't include the country** → no ShippingMethod offered.
- **Multiple StockLocations with overlapping inventory** → unexpected multi-shipment splits. Configure `propagate_all_variants` or pin to a primary location.
- **Carrier API rate-limited during checkout** → cache rates, fall back to flat rate on error.
- **Forgetting to fire `shipped!`** when manually marking a shipment — no event, no email, no webhook.
- **Stock movements without going through `adjust_count_on_hand`** — bypasses the ledger and breaks reconciliation.
Always verify model relationships and calculator names against the live source — shipping is one of the most decoupled subsystems and has community gems that vary in v5 readiness.Related Skills
woo-shipping
Build WooCommerce shipping methods — WC_Shipping_Method, shipping zones, shipping classes, rate calculation, tracking, and integration with carriers. Use when creating custom shipping integrations or configuring shipping logic.
ucp-fulfillment
Implement the UCP Fulfillment extension — shipping and pickup methods, destinations, fulfillment groups, selectable options, and estimated delivery. Use when adding shipping/pickup logic to a UCP checkout.
spree-upgrades
Upgrade a Spree application — version-to-version migration paths (v4 → v5 is a major rewrite; v5.x → v5.y are simpler), Gemfile pinning strategy, decorator brittleness across versions, deprecated gems to remove (`spree_auth_devise`, `deface`, `spree_backend`), API v2 → v3 migration via `spree_legacy_api_v2`, admin Bootstrap → Tailwind transition, the upgrade guide workflow, and rollback strategy. Use when planning a Spree upgrade or auditing technical debt blocking one.
spree-typescript-sdk
Build storefronts and integrations using `@spree/sdk` — the official TypeScript SDK for Spree's API v3 (v5.4+). Covers installation, the resource-builder pattern (`spree.products.list`, `spree.checkout.create`, etc.), Zod schema validation, server-only auth (httpOnly JWTs, never exposing API keys), error handling, typing customizations, and pinning SDK versions to backend Spree releases. Use when integrating Spree into a Next.js / Node.js / TypeScript project.
spree-testing
Test Spree applications and extensions with RSpec — `spree_dev_tools` gem (v5.2+) for factories and helpers, FactoryBot patterns (prefer `build` over `create`), Capybara feature specs, controller/request specs, testing decorators and subscribers, dummy app for extension testing, system specs for the Hotwire admin, and CI patterns. Use when writing Spree tests, setting up CI, or refactoring slow specs.
spree-setup
Bootstrap a new Spree project — `create-spree-app` CLI (v5.2+), `spree-starter` Rails backend, the Next.js storefront repo, `bin/rails g spree:install`, sample data, Docker Compose, and the PostgreSQL + Redis + Sidekiq prerequisites. Use when starting a new Spree project from scratch or onboarding an existing repo.
spree-security
Secure a Spree deployment — Rails credentials and env-var hygiene, Devise auth (Spree v5 ships it in-core; `spree_auth_devise` is archived), CanCanCan authorization rules, Doorkeeper OAuth2 scopes, Storefront publishable key vs admin API key, webhook HMAC verification, OWASP Top 10 for Rails (mass assignment, CSRF, SQL injection via Ransack, XSS, IDOR through prefixed IDs), PCI scope (Spree never touches raw cards thanks to gateway tokenization), and multi-store data isolation. Use when auditing a Spree app, hardening a deploy, or addressing a security incident.
spree-promotions
Build and customize Spree's promotions engine — Promotion + PromotionRule + PromotionAction + CouponCode + Adjustment, the bundled rules (FirstOrder/ItemTotal/Product/Taxon/User/OneUsePerUser/Country/CustomerGroup/etc.), bundled actions (CreateAdjustment/CreateItemAdjustments/FreeShipping/CreateLineItems), Calculator classes, coupon batches with CSV export, the v5.1+ advanced rule-based engine, and authoring custom rules/actions/calculators. Use when modeling promotions, building discount UIs, or extending the promotions engine.
spree-performance
Profile and optimize a Spree application — N+1 queries with bullet/scout, database indexing strategy for Spree's polymorphic associations, Rails fragment + Russian doll caching, ActiveStorage variant pre-generation, Sidekiq queue tuning, MeiliSearch vs Postgres FTS tradeoffs, Puma worker/thread sizing, CDN strategy for catalog pages, asset precompile time, and load testing. Use when Spree is slow, the database is hot, or you're preparing for a traffic spike (Black Friday, launch).
spree-payments
Integrate payment gateways with Spree — PaymentMethod model, the v5.4+ PaymentSession provider-agnostic checkout flow, Stripe via `spree_stripe` (Apple/Google Pay, Link, Connect for marketplaces), Adyen via `spree_adyen`, PayPal via `spree_paypal_checkout`, StoreCredit / GiftCard as payment methods, refunds, payment state machine, and authoring a custom gateway. Use when wiring a payment integration, handling webhooks from a gateway, or debugging payment-state issues.
spree-multi-store
Configure Spree for multi-store and multi-region commerce — one Rails install running many `Store` records, the v5.4+ `Market` model (currency + locale + payment methods + shipping per region), what's shared vs per-store (products+inventory+customers shared; orders+payments+themes per-store), the Marketplace module (Enterprise — vendors/commission/payouts via Stripe Connect), and the Multi-tenant SaaS model. Use when planning a multi-brand or multi-region Spree deployment.
spree-legacy-api-v2
Work with Spree's legacy v2 APIs — JSON:API-style Storefront API at `/api/v2/storefront/*` and Platform API at `/api/v2/platform/*`, Doorkeeper OAuth2 (password grant for storefront, client_credentials + admin scope for platform), the `spree_legacy_api_v2` gem (required in v5+ for backward compatibility), and migration patterns to API v3. Use when maintaining a v2 client during a migration window, integrating an older partner system, or deciding when to cut over to v3.