Skip to main content

Overview

Shipping Customization Functions let you modify shipping rates, hide/show shipping options, rename delivery methods, and add custom shipping logic based on cart contents, location, or customer data.

Use Cases

Hide Shipping Methods

Remove options based on conditions
  • Hide express for remote areas
  • Remove free shipping below threshold
  • Restrict by product type

Adjust Rates

Modify shipping costs dynamically
  • Add handling fees
  • Apply discounts
  • Bulk order pricing
  • Weight-based adjustments

Rename Methods

Customize shipping option names
  • Add delivery estimates
  • Show carrier names
  • Include pricing details

Reorder Options

Change shipping method order
  • Promote fastest option
  • Prioritize cheapest
  • Customer preference

How It Works

1

Checkout Loads

Customer enters shipping address in checkout
2

Rates Calculated

Shopify calculates available shipping rates
3

Function Executes

Your function receives cart data and shipping rates
4

Rates Customized

Function returns modified rates and options
5

Checkout Updates

Customer sees customized shipping options

Example: Free Shipping Threshold

Generate a function that offers free shipping over $100:
1

Describe Your Function

Create a shipping customization function that renames the 
Standard Shipping option to show "Free Shipping" when cart 
total is over $100, and updates the rate to $0. Below $100, 
show normal shipping rates.
2

Review Generated Code

Synapse creates:
  • Function logic (Rust or JavaScript)
  • Input query for cart and shipping data
  • Rate modification logic
  • Configuration
3

Deploy & Test

  • Automatic validation
  • Deployed to dev store
  • Test with different cart totals
4

Test in Checkout

  1. Add items to cart
  2. Go to checkout
  3. Enter shipping address
  4. See customized shipping options

Generated Function Structure

extensions/shipping-customization/
├── src/
│   ├── run.rs                 # Function logic (Rust)
│   │   OR
│   ├── run.js                 # Function logic (JS)
│   └── run.graphql           # Input query
├── shopify.extension.toml     # Config
└── Cargo.toml / package.json

Function Code Examples

  • Free Shipping Threshold (Rust)
  • Hide Express for Remote Areas (JavaScript)
  • Add Handling Fee
  • Input Query
use shopify_function::prelude::*;
use shopify_function::Result;
use rust_decimal::Decimal;

#[shopify_function_target(
    query_path = "src/run.graphql",
    schema_path = "schema.graphql"
)]
fn run(input: input::ResponseData) -> Result<output::FunctionRunResult> {
    // Calculate cart total
    let cart_total = Decimal::from_str(
        &input.cart.cost.subtotal_amount.amount
    ).unwrap_or(Decimal::ZERO);
    
    let threshold = Decimal::from(100);
    
    let operations = input
        .cart
        .delivery_groups
        .iter()
        .flat_map(|group| {
            group.delivery_options.iter().filter_map(|option| {
                // Check if this is standard shipping
                if option.title.to_lowercase().contains("standard") {
                    if cart_total >= threshold {
                        // Free shipping!
                        return Some(output::Operation::Update(
                            output::UpdateOperation {
                                delivery_option_handle: option.handle.clone(),
                                title: Some("Free Standard Shipping".to_string()),
                                price: Some(Decimal::ZERO),
                            }
                        ));
                    }
                }
                None
            })
        })
        .collect();
    
    Ok(output::FunctionRunResult { operations })
}

Common Patterns

Different shipping costs at different cart values:
let cart_total = parse_amount(&input.cart.cost.subtotal_amount.amount);

let shipping_discount = if cart_total >= 100.0 {
    1.0  // Free (100% discount)
} else if cart_total >= 75.0 {
    0.5  // 50% off
} else if cart_total >= 50.0 {
    0.25 // 25% off
} else {
    0.0  // No discount
};

let operations = input.cart.delivery_groups
    .iter()
    .flat_map(|group| {
        group.delivery_options.iter().map(|option| {
            let current_price = parse_amount(&option.cost.amount);
            let new_price = current_price * (1.0 - shipping_discount);
            
            Operation::Update(UpdateOperation {
                delivery_option_handle: option.handle.clone(),
                price: Some(Decimal::from_f64(new_price).unwrap()),
                title: if shipping_discount == 1.0 {
                    Some(format!("Free {}", option.title))
                } else {
                    None
                },
            })
        })
    })
    .collect();
Prompt Example:
"Create tiered shipping discounts: Free shipping over $100, 
50% off shipping for $75-$99, 25% off for $50-$74, normal 
rates below $50. Update titles to show 'Free' when applicable."
Hide shipping options based on cart contents:
let has_oversized = input.cart.lines.iter()
    .any(|line| line.merchandise.product.has_tags(vec!["oversized"]));

if has_oversized {
    // Only allow freight shipping for oversized items
    let operations = input.cart.delivery_groups
        .iter()
        .flat_map(|group| {
            group.delivery_options.iter().filter_map(|option| {
                if !option.title.to_lowercase().contains("freight") {
                    Some(Operation::Hide(HideOperation {
                        delivery_option_handle: option.handle.clone(),
                    }))
                } else {
                    None
                }
            })
        })
        .collect();
}
Prompt Example:
"If cart contains products tagged 'oversized', hide all shipping 
methods except Freight Shipping. Show a message that oversized 
items require freight delivery."
Adjust shipping rates by destination:
let country = input.cart.delivery_groups[0]
    .delivery_address
    .country_code
    .as_str();

let surcharge = match country {
    "AU" => 15.0,  // Australia surcharge
    "NZ" => 12.0,  // New Zealand surcharge
    "JP" => 20.0,  // Japan surcharge
    _ => 0.0,
};

if surcharge > 0.0 {
    let operations = input.cart.delivery_groups
        .iter()
        .flat_map(|group| {
            group.delivery_options.iter().map(|option| {
                let current_price = parse_amount(&option.cost.amount);
                let new_price = current_price + surcharge;
                
                Operation::Update(UpdateOperation {
                    delivery_option_handle: option.handle.clone(),
                    price: Some(Decimal::from_f64(new_price).unwrap()),
                })
            })
        })
        .collect();
}
Prompt Example:
"Add $15 surcharge to all shipping methods for Australian 
customers, $12 for New Zealand, $20 for Japan. Keep other 
countries at normal rates."
Show estimated delivery dates in shipping option names:
use chrono::{Utc, Duration};

let operations = input.cart.delivery_groups
    .iter()
    .flat_map(|group| {
        group.delivery_options.iter().map(|option| {
            let days = if option.title.contains("Express") {
                1
            } else if option.title.contains("Standard") {
                5
            } else {
                7
            };
            
            let delivery_date = Utc::now() + Duration::days(days);
            let formatted = delivery_date.format("%b %d");
            
            Operation::Update(UpdateOperation {
                delivery_option_handle: option.handle.clone(),
                title: Some(format!(
                    "{} (Arrives by {})",
                    option.title,
                    formatted
                )),
                price: None,
            })
        })
    })
    .collect();
Prompt Example:
"Add estimated delivery dates to shipping method names. Express 
delivers in 1 day, Standard in 5 days, Economy in 7 days. 
Format as 'Method Name (Arrives by Nov 5)'"

Operation Types

Shipping functions support these operations:
  • Update
  • Hide
  • Move
Modify shipping rate or name:
Operation::Update(UpdateOperation {
    delivery_option_handle: option.handle.clone(),
    title: Some("Free Shipping".to_string()),
    price: Some(Decimal::ZERO),
})
  • Change rate amount
  • Update display name
  • Modify description

Advanced Examples

let total_weight: f64 = input.cart.lines.iter()
    .map(|line| {
        let weight = line.merchandise.weight
            .as_ref()
            .and_then(|w| w.value.parse::<f64>().ok())
            .unwrap_or(0.0);
        weight * line.quantity as f64
    })
    .sum();

// Add $2 per kg over 5kg
let weight_surcharge = if total_weight > 5.0 {
    (total_weight - 5.0) * 2.0
} else {
    0.0
};

let operations = input.cart.delivery_groups
    .iter()
    .flat_map(|group| {
        group.delivery_options.iter().map(|option| {
            let current_price = parse_amount(&option.cost.amount);
            let new_price = current_price + weight_surcharge;
            
            Operation::Update(UpdateOperation {
                delivery_option_handle: option.handle.clone(),
                price: Some(Decimal::from_f64(new_price).unwrap()),
                title: if weight_surcharge > 0.0 {
                    Some(format!(
                        "{} (+ ${:.2} heavy item fee)",
                        option.title,
                        weight_surcharge
                    ))
                } else {
                    None
                },
            })
        })
    })
    .collect();
let customer_tier = input.cart.buyer_identity
    .as_ref()
    .and_then(|id| id.customer.as_ref())
    .and_then(|c| {
        if c.has_tags(vec!["vip"]) { Some("VIP") }
        else if c.has_tags(vec!["premium"]) { Some("Premium") }
        else { None }
    });

let discount = match customer_tier {
    Some("VIP") => 1.0,      // Free shipping
    Some("Premium") => 0.5,  // 50% off
    _ => 0.0,
};

let operations = input.cart.delivery_groups
    .iter()
    .flat_map(|group| {
        group.delivery_options.iter().map(|option| {
            let current_price = parse_amount(&option.cost.amount);
            let new_price = current_price * (1.0 - discount);
            
            Operation::Update(UpdateOperation {
                delivery_option_handle: option.handle.clone(),
                price: Some(Decimal::from_f64(new_price).unwrap()),
                title: if discount == 1.0 {
                    Some(format!("Free {} (VIP)", option.title))
                } else if discount > 0.0 {
                    Some(format!("{} ({}% off)", option.title, (discount * 100.0) as i32))
                } else {
                    None
                },
            })
        })
    })
    .collect();
let total_quantity: i64 = input.cart.lines.iter()
    .map(|line| line.quantity)
    .sum();

// Free shipping for bulk orders (20+ items)
if total_quantity >= 20 {
    let operations = input.cart.delivery_groups
        .iter()
        .flat_map(|group| {
            group.delivery_options.iter().map(|option| {
                Operation::Update(UpdateOperation {
                    delivery_option_handle: option.handle.clone(),
                    title: Some(format!("Free {} (Bulk Order)", option.title)),
                    price: Some(Decimal::ZERO),
                })
            })
        })
        .collect();
}

Testing Shipping Functions

1

Test in Checkout

  1. Add items to cart
  2. Go to checkout
  3. Enter various shipping addresses
  4. Verify rates update correctly
2

Test Different Scenarios

  • Various cart values
  • Different product combinations
  • Multiple shipping addresses
  • International vs domestic
3

Check Function Logs

shopify app function logs --watch
Monitor function executions
4

Performance Testing

  • Should execute in under 5ms
  • Test with many shipping options
  • Large cart edge cases

Best Practices

Always Show Options

Never hide all shipping methodsEnsure at least one method remains visible

Clear Messaging

Explain rate changesShow why fees added or discounts applied

Handle Errors

Graceful fallbacksReturn empty operations on errors, don’t crash

Performance

Keep functions fastMinimize loops, use Rust, cache calculations

Example Prompts

Create a shipping function with tiers: free shipping over $100, 
50% off shipping for $50-$99, normal rates below $50. Update 
the shipping method names to show the discount percentage.
If cart contains any products tagged 'fragile', add a $5 
handling fee to all shipping methods and update the name to 
show '(+ fragile handling fee)'.
Hide Express Shipping for regular customers. Only show Express 
to customers tagged 'vip'. Keep Standard and Economy visible 
for everyone.
For zip codes in remote areas (list: 99999, 88888, 77777), 
add $10 to all shipping methods and update name to show 
'(+ remote area surcharge)'.

Troubleshooting

Cause: Function hiding all methodsSolution: Add safeguard to keep at least one visible
Cause: Price calculation errorSolution: Check Decimal parsing, handle errors with unwrap_or
Cause: Deployment issueSolution: Check deployment logs, verify function active

Next Steps