
International Shipping
- 161 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Integrate international shipping carriers, rate quotes, customs data, landed-cost estimates, and label generation into ecommerce checkout and fulfillment workflows.
About
international-shipping skill from finsilabs/awesome-ecommerce-skills implements cross-border fulfillment: carrier rate quotes, duty and tax estimates, customs fields, restricted countries, label purchase, and tracking updates inside ecommerce checkout and ops flows.
- Carrier rate shopping
- Customs and HS codes
- Landed-cost estimates
- Label and tracking sync
- Zone and restriction rules
International Shipping by the numbers
- 161 all-time installs (skills.sh)
- Ranked #2,387 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill international-shippingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 161 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Integrate international shipping carriers, rate quotes, customs data, landed-cost estimates, and label generation into ecommerce checkout and fulfillment workflows.
Files
International Shipping
Overview
International shipping requires more than just choosing a carrier — you need to handle customs declarations, duties and taxes (showing customers the landed cost at checkout prevents nasty surprises), HS code classification for each product, and screening for items that are prohibited in certain countries. Getting this wrong leads to packages stuck in customs, unexpected bills delivered to customers, and damaged brand reputation.
This skill walks through setting up international shipping on each major platform, including the right apps for duties/taxes and customs form generation.
When to Use This Skill
- When expanding from domestic to international shipping and need to handle customs declarations
- When offering Delivered Duty Paid (DDP) so customers see the full landed cost at checkout
- When building a product catalog that needs HS codes for accurate tariff classification
- When screening orders for restricted items before processing international shipments
- When integrating with a customs broker or carrier (EasyPost, Shippo, Easyship, Flexport)
Core Instructions
Step 1: Determine your platform and choose the right international shipping tools
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Shopify Markets + Zonos Duty & Tax or Global-e | Shopify Markets handles multi-currency and localized checkout; Zonos/Global-e add accurate DDP landed cost |
| WooCommerce | WooCommerce Shipping + Easyship plugin or Zonos for WooCommerce | Easyship provides multi-carrier rates including international; Zonos handles duties/taxes |
| BigCommerce | BigCommerce Multi-Currency + Easyship or Avalara AvaTax Cross-Border | Built-in multi-currency handles pricing; Easyship/Avalara add duties estimation |
| Custom / Headless | EasyPost or Shippo for labels + Zonos or Avalara for duties estimation | Use carrier aggregators for labels; use a landed-cost API for duties/taxes |
Step 2: Assign HS codes to all products
HS (Harmonized System) codes are required on customs declarations for every international shipment. Missing codes cause customs delays.
Shopify
1. Go to Products → [Product] → Shipping 2. Under "Customs information", enter the HS tariff code (6–10 digits) and Country/Region of origin 3. For large catalogs: export your product CSV (Products → Export), fill in the Variant HS Code and Variant Origin Country columns in bulk, then re-import
Common HS codes for e-commerce:
6109.10— Cotton T-shirts6109.90— Synthetic T-shirts6404.11— Athletic footwear8471.30— Laptops9503.00— Toys3304.99— Cosmetics
For HS code classification help, use:
- Zonos Hello (free tool at zonos.com/hs-code) — paste a product description and get a suggested HS code
- Avalara TariffFinder — free online tool for HS code lookup
- Schedule B Search Engine (US Census Bureau) — authoritative for US exports
WooCommerce
1. Go to Products → [Product] → Shipping tab 2. Most shipping plugins (ShipStation, Easyship) add custom HS code and country of origin fields here 3. If your plugin doesn't add these fields, install WooCommerce Extra Product Options or use the Custom Fields metabox to store _hs_code and _country_of_origin per product
BigCommerce
1. Go to Products → [Product] → Shipping 2. Enter the HS code in the "Harmonized System Code" field (visible on all plans) 3. Enter Country of Origin in the "Country of Manufacture" field
Step 3: Configure duties and taxes (DDP vs. DDU)
DDP (Delivered Duty Paid): Customer pays duties at checkout — best for conversion, no surprises. DDU (Delivered Duty Unpaid): Customer pays duties on delivery — creates friction and returns.
Always aim for DDP on key international markets.
Shopify
Using Shopify Markets (DDP): 1. Go to Settings → Markets → International markets 2. Enable each country you ship to and set the currency 3. For duties: install Zonos Duty & Tax or Global-e from the App Store 4. Zonos integrates with Shopify Markets checkout and shows the exact duty amount as a line item 5. In Zonos settings, configure which markets to show DDP (collect duties at checkout) vs. DDU
Note: Shopify's built-in international shipping does NOT automatically calculate duties — you need Zonos or Global-e for that.
WooCommerce
1. Install the Easyship plugin from WordPress.org or easyship.com 2. In Easyship settings, enable "Show duties and taxes at checkout" 3. Easyship calculates duties based on destination country and your HS codes 4. Customers see the duties estimate during checkout as a separate line item
Alternative for larger operations: Use the Zonos Checkout for WooCommerce plugin which guarantees the landed cost accuracy.
BigCommerce
1. Install Avalara AvaTax Cross-Border from the BigCommerce App Marketplace 2. Configure Avalara with your shipping origin address and enable cross-border tax calculation 3. Avalara displays duties estimates on checkout; upgrade to their DDP product for guaranteed landed costs
Step 4: Set up international label creation with customs forms
Carriers require customs declarations (CN22 for items under $400; CN23/commercial invoice for higher values) on every international package.
Shopify
- Shopify Shipping generates customs forms automatically when you buy labels for international orders
- Go to Orders → [Order] → Fulfill items and Shopify shows the customs information form pre-filled from your product HS codes
- Review and adjust the declared value if needed (use sale price, not a lowered value — declaring a lower value to avoid duties is illegal)
- For high-volume operations: ShipStation (Shopify App Store) or Easyship generate customs forms in bulk and integrate with all major carriers (DHL, FedEx, UPS, USPS)
WooCommerce
- WooCommerce Shipping (powered by WooCommerce Shipping & Tax plugin) generates customs forms for USPS and DHL Express labels
- Go to WooCommerce → Shipments → Create Label for an order — customs fields are auto-populated from product data
- For full carrier coverage: use ShipStation or Easyship plugins which handle customs forms for all carriers
BigCommerce
- ShipStation (BigCommerce App Marketplace) handles customs forms for all carriers and has BigCommerce order sync built in
- Easyship (BigCommerce App Marketplace) also supports multi-carrier international labels with auto-generated customs forms
Step 5: Block restricted and prohibited items
Some products cannot ship to certain countries (firearms parts, alcohol, certain electronics).
Shopify
1. Use Shopify Markets to set country availability per product:
- Go to Products → [Product] → Sales channels
- Disable specific markets for restricted products
2. For a comprehensive restriction list: Install Fraud Filter or use Shopify Flow (Plus) to flag orders where a restricted product is shipping to a restricted country 3. Apps like Geoipfy can redirect or block checkout for restricted country/product combinations
WooCommerce
1. Use the WooCommerce Shipping Restrictions plugin to block specific products from shipping to specific countries 2. Go to Products → [Product] → Shipping tab and set "Shipping restrictions" by country 3. For order-level blocking: use WooCommerce's built-in "Restrict to countries" in WooCommerce → Settings → General → Selling location(s)
BigCommerce
1. Go to Products → [Product] → Shipping 2. Set "Free Shipping" to disabled and use custom shipping rules to block certain destinations 3. For comprehensive blocking: Use the ShipperHQ app which supports zone-based shipping restrictions
Custom / Headless
// Screen cart for restricted items before allowing checkout to proceed
async function screenForRestrictions(params: {
orderLines: { productId: string; hsCode?: string }[];
destinationCountry: string;
}): Promise<{ allowed: boolean; blockedItems: string[] }> {
const blockedItems: string[] = [];
// Common restrictions — seed these from a country-restrictions database
const RESTRICTIONS: Record<string, string[]> = {
AU: ['9305'], // Firearm parts
IN: ['2207'], // Alcohol (requires import licence)
CN: ['8517'], // Consumer electronics require CCC certification
};
const countryRestrictions = RESTRICTIONS[params.destinationCountry] ?? [];
for (const line of params.orderLines) {
if (!line.hsCode) continue;
const hsPrefixBlocked = countryRestrictions.some(prefix => line.hsCode!.startsWith(prefix));
if (hsPrefixBlocked) {
blockedItems.push(line.productId);
}
}
return { allowed: blockedItems.length === 0, blockedItems };
}Best Practices
- Assign HS codes to every product before enabling international shipping — missing HS codes are the single most common cause of customs delays; use the Zonos or Avalara classification tools for bulk assignment
- Always include a phone number in the ship-to address — most international carriers require a recipient phone number; make it required for all non-domestic shipping addresses
- Offer DDP for your top 5 international markets — customers who see the full landed cost at checkout convert at significantly higher rates than those who receive a duty bill on delivery
- Respect de minimis thresholds — orders below the de minimis value (US: $800, UK: £135, EU: €150, AU: AUD $1,000) are often duty-free; most DDP apps handle this automatically
- Keep customs descriptions generic but accurate — "Cotton T-shirt" is better than a brand name for faster clearance; avoid anything that sounds like it requires special permits
- Test with real international orders before scaling — send a test order to each new country and track it through customs before launching a marketing campaign there
Common Pitfalls
| Problem | Solution |
|---|---|
| Customs form not generated for international order | Ensure your shipping app has HS codes and country of origin on all product records; missing fields are the most common cause |
| Duties estimated at checkout differ from actual duties assessed | Use a DDP provider (Zonos, Global-e) for guaranteed accuracy; label estimates as "estimated" when using DDU |
| Prohibited item detected after label is created and package is in transit | Screen for restrictions at checkout, not at fulfillment; block checkout for restricted country/product combinations |
| Package returned for "customs information required" | All international shipments need customs forms — even low-value ones; configure your shipping app to always include customs data for non-domestic destinations |
Related Skills
- @order-fulfillment-workflow
- @shipment-tracking
- @shipping-rate-calculator
- @same-day-delivery
- @order-management-system
{
"context": "Tests whether the agent correctly generates customs declaration payloads following the international shipping skill. Focuses on field-level correctness: description length limits, weight unit conversion, declared value override logic, non-delivery handling, export compliance fields, and contents metadata.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Description length cap",
"max_score": 12,
"description": "Customs item description is truncated to at most 45 characters (e.g. uses .slice(0, 45) or equivalent)"
},
{
"name": "Weight unit conversion",
"max_score": 12,
"description": "Product weight_oz is divided by 16 to convert to pounds for the net_weight field"
},
{
"name": "Declared value override",
"max_score": 12,
"description": "Uses declared_value_override when non-null, falling back to unit_price when it is null"
},
{
"name": "Non-delivery option",
"max_score": 10,
"description": "Sets non_delivery_option to 'return' (not 'abandon' or any other value)"
},
{
"name": "EEL/PFC exemption code",
"max_score": 10,
"description": "Sets eel_pfc to exactly 'NOEEI 30.37(a)'"
},
{
"name": "Contents type",
"max_score": 8,
"description": "Sets contents_type to 'merchandise'"
},
{
"name": "Customs certify flag",
"max_score": 8,
"description": "Sets customs_certify to true (boolean)"
},
{
"name": "Signer from environment",
"max_score": 8,
"description": "Sets customs_signer from an environment variable (e.g. process.env.CUSTOMS_SIGNER_NAME or similar) rather than a hardcoded string"
},
{
"name": "Currency USD",
"max_score": 6,
"description": "Sets currency to 'USD' on each customs item"
},
{
"name": "Generic descriptions",
"max_score": 7,
"description": "README or code comments note that customs descriptions should be generic/category-level (avoiding brand names or trade names)"
},
{
"name": "Value in dollars",
"max_score": 7,
"description": "Declared value is converted from cents to dollars (divided by 100) and formatted as a decimal string (e.g. toFixed(2)) in the customs item"
}
]
}
Customs Declaration Generator
Problem/Feature Description
GlobalMart, a mid-size e-commerce retailer, is preparing to launch international shipping to customers in the UK, Germany, and Australia. Their fulfilment operations team has asked the engineering team to build a TypeScript module that generates customs declaration payloads for outbound international orders.
The product catalogue already stores item weights in ounces, and some products have a special customs-declared value that differs from the retail price (for example, promotional items sold below cost, or items where customs value is contractually fixed). The fulfilment team wants the module to respect these overrides and handle all the formatting and field requirements that international carriers and customs agencies expect. The operations manager has flagged that an incorrect handling of undeliverable packages in the past led to significant losses, and the legal team has highlighted that export compliance fields must be included for every outgoing shipment.
The team uses a PostgreSQL database accessible via a db object with the following shape (already defined, do not redefine): db.orders.findById(id), db.orderLines.findByOrderId(id), db.products.findById(id). Products have fields: name (string), weight_oz (number|null), declared_value_override (number|null, in cents), hs_code (string), country_of_origin (string). Order lines have product_id, quantity, unit_price (cents). The warehouse identity fields are available via environment variables.
Output Specification
Produce a single TypeScript file customs-declaration.ts containing:
1. A generateCustomsItem() helper that builds a single customs line item from a product and order line 2. A generateCustomsInfo() async function that takes an orderId: string and returns the complete customs info payload ready to be passed to a carrier API 3. A brief README.md (max 20 lines) explaining the key decisions made in the implementation — particularly around field values and formats
{
"context": "Tests whether the agent implements duties estimation using EasyPost, defines the correct DutyEstimation interface, uses accurate de minimis thresholds per country, suppresses estimates for below-threshold orders, and implements a graceful fallback to DDU rather than throwing on API failure.",
"type": "weighted_checklist",
"checklist": [
{
"name": "EasyPost package",
"max_score": 8,
"description": "Imports and uses '@easypost/api' package (not an alternative duties/tax API like taxjar, avalara, stripe, or a generic HTTP client)"
},
{
"name": "DutyEstimation method type",
"max_score": 8,
"description": "DutyEstimation interface declares 'method' as the union type 'DDP' | 'DDU' (not just string)"
},
{
"name": "Fallback returns DDU",
"max_score": 10,
"description": "When the EasyPost API call fails (catch block), the function returns a result with method set to 'DDU' rather than throwing or returning DDP"
},
{
"name": "Fallback GB VAT rate",
"max_score": 8,
"description": "Fallback VAT rate for GB is exactly 0.20 (20%)"
},
{
"name": "Fallback DE VAT rate",
"max_score": 8,
"description": "Fallback VAT rate for DE is exactly 0.19 (19%)"
},
{
"name": "US de minimis threshold",
"max_score": 8,
"description": "DE_MINIMIS_CENTS includes US mapped to 80000 (representing $800 USD)"
},
{
"name": "GB de minimis threshold",
"max_score": 8,
"description": "DE_MINIMIS_CENTS includes GB mapped to 13500 (representing £135)"
},
{
"name": "EU de minimis threshold",
"max_score": 8,
"description": "DE_MINIMIS_CENTS includes EU mapped to 15000 (representing €150)"
},
{
"name": "De minimis suppression",
"max_score": 10,
"description": "checkout-integration.md or code shows that duty estimate is suppressed (not shown / set to zero) when the order total is at or below the destination country de minimis threshold"
},
{
"name": "Monetary values in cents",
"max_score": 8,
"description": "DutyEstimation interface uses integer cents for duties, taxes, and total fields (not floats/dollars)"
},
{
"name": "EasyPost API key from env",
"max_score": 8,
"description": "EasyPost client is instantiated using process.env.EASYPOST_API_KEY (not a hardcoded key)"
},
{
"name": "Fallback no-throw",
"max_score": 8,
"description": "The catch block does NOT re-throw the error — execution continues to return a fallback DDU estimate"
}
]
}
International Duties Estimation at Checkout
Problem/Feature Description
TradeWave, an online marketplace, is expanding to serve customers in the UK, Germany, France, Australia, Canada, Japan, and Mexico. The checkout team has been asked to show customers the estimated duties and taxes before they place an order — a key requirement for the "landed cost" feature the product team wants to launch next quarter.
The team is aware that small, low-value orders in many countries are often fully exempt from import duties, and they want the checkout experience to reflect this: if an order qualifies for the duty-free exemption in the destination country, no duty estimate should be shown or added to the order total. The carrier integration currently being used for rate shopping and label creation is EasyPost, and the engineering team would like the duties estimation to be consistent with that platform. The team also needs a reliable fallback path in case the live estimation service is unavailable — but the fallback must not cause the checkout to error out for customers.
Output Specification
Produce a single TypeScript file duties-estimation.ts containing:
1. A DutyEstimation TypeScript interface capturing all fields returned by the estimation 2. A DE_MINIMIS_CENTS constant mapping destination country codes to their duty-free thresholds 3. An exceedsDeMinimisCents(totalValueCents, destinationCountry) helper function 4. An estimateDutiesAndTaxes(orderLines, destinationCountry, destinationZip) async function that calls EasyPost for live estimates and falls back gracefully if the API is unavailable
Also produce a checkout-integration.md (max 20 lines) explaining how the de minimis check should be integrated into the checkout flow and what to show the customer in each case.
{
"context": "Tests whether the agent correctly extends the product schema with the right column types and creates the HS code reference table, implements a two-layer restriction screening function, seeds known restrictions, and positions screening at checkout rather than post-label.",
"type": "weighted_checklist",
"checklist": [
{
"name": "hs_code column type",
"max_score": 8,
"description": "hs_code column is defined as VARCHAR(10) (not VARCHAR(6), VARCHAR(20), TEXT, or other type)"
},
{
"name": "country_of_origin column type",
"max_score": 8,
"description": "country_of_origin column is defined as VARCHAR(2) (exactly 2-char code, ISO 3166-1 alpha-2)"
},
{
"name": "restricted_countries array type",
"max_score": 8,
"description": "restricted_countries column is defined as an array of 2-char codes (e.g. VARCHAR(2)[] or TEXT[] with a note about 2-char codes)"
},
{
"name": "declared_value_override in cents",
"max_score": 8,
"description": "declared_value_override column is INTEGER (not DECIMAL/FLOAT) with a comment or note indicating it stores cents"
},
{
"name": "hs_codes reference table",
"max_score": 8,
"description": "A separate hs_codes reference table is created with at least a primary key code column and a description column"
},
{
"name": "Product-level restriction check",
"max_score": 10,
"description": "screenForRestrictions checks each product's own restricted_countries field against the destination country"
},
{
"name": "Global restriction DB check",
"max_score": 10,
"description": "screenForRestrictions also queries a global/shared restrictions table by (country_code, hs_code) — two-layer check, not just product-level"
},
{
"name": "Return type shape",
"max_score": 8,
"description": "screenForRestrictions returns an object with both an 'allowed' boolean AND a 'blockedProducts' array (not just one or the other)"
},
{
"name": "Known restrictions seeded",
"max_score": 10,
"description": "Seed data includes entries for at least 2 of: Australia firearm parts (hs prefix 9305), India alcohol (hs prefix 2207), China consumer electronics (hs prefix 8517)"
},
{
"name": "Screening at checkout",
"max_score": 10,
"description": "notes.md or code comments explicitly state that screening should occur before fulfillment/at checkout, not after label creation"
},
{
"name": "Blocked product details",
"max_score": 12,
"description": "Each entry in blockedProducts includes at least productId (or id), name, and a reason string"
}
]
}
International Shipping — Product Catalogue and Order Screening
Problem/Feature Description
Nomad Commerce, a growing direct-to-consumer brand, is launching international shipping to 30+ countries. Before they can go live, two things need to happen: first, their PostgreSQL product database needs new fields to support cross-border shipments; second, their order processing pipeline needs a screening step that catches prohibited or restricted items before an order reaches the fulfilment queue.
The engineering team has found that catching restricted items late (after a label is created) is both costly and operationally disruptive. Their compliance officer also provided a list of known product-country restrictions they need to seed as a starting point. The team wants a SQL migration that extends the products table, a new reference table for tariff classification support, a seed script for known restriction data, and a TypeScript function that screens a cart against destination-country restrictions before the order is submitted.
Output Specification
Produce the following files:
1. migration.sql — SQL migration that adds the required international-shipping fields to the products table and creates any supporting reference tables 2. seed-restrictions.sql — SQL statements to seed a country_restrictions table with at least the known problematic product categories for key destination countries 3. screen-order.ts — TypeScript module exporting an async screenForRestrictions(orderLines, destinationCountry) function; include the TypeScript interface definitions for inputs and the return value 4. notes.md — A short (max 20 lines) design note explaining where in the checkout flow the screening should happen and why
{
"name": "finsi/international-shipping",
"version": "0.1.0",
"summary": "Cross-border commerce: customs forms, duties estimation, restricted items",
"skills": {
"international-shipping": {
"path": "SKILL.md"
}
}
}