Flat Rate Shipping Modification

This snippet has been replaced. In place of the following snippet, we now have a custom shipping code feature in FoxyCart 2.0 that allows you to modify returned live rates and provide custom rates to your customers without needing to add extra javascript to change the way the checkout works. Please use the new custom shipping code functionality instead of this snippet. Review this page for notes on migrating to the new functionality. This page will remain for reference.

If you're using handling fees, you will need to set them as part of your custom shipping logic - instead of through the administration. Setting a handling fee in the admin while making use of this snippet can have unintended results.

The following functionality allows you to add and update any number of custom flat rate shipping options based on any criteria (eg: what categories are products in, total cost of the cart, shipping destination country). This script is limited to flat rate shipping only.

The functionality described on this page require advanced javascript knowledge, and are not officially supported. They are included in our official wiki because certain shipping methods and functionality are not natively supported, and though we are working on radically improving our shipping functionality, in the meantime these methods may be great workarounds. Use with caution, test, and post in our forum if you run into problems.

Custom shipping snippets do not work for subscriptions. The reason for this is because the snippets are done as Javascript modifications and run in the browser, while subscriptions run completely on the server side and never touch the browser. Subscriptions will always calculate on the server side based on how the category is configured.

See the changelog for details on updates to this script.

Step 1: Update Categories

Update all categories to 'Shipped using a flat rate fee' with a value of 0 in the 'Product Delivery Option' section.

Step 2: Disable Shipping Rate Signing

To ensure the snippet works as normal, you'll also need to ensure that shipping rate signing is disabled for your store. To do that, head to the “shipping” section of your store's FoxyCart administration, and at the bottom of the page ensure that “enable shipping rate signing” is disabled.

Step 3: Add Javascript

Add the following in the “footer” section of the template configuration's “Add custom header and footer code to your templates” option. You will find this option within the “Cart” group on the “Configuration” section of your store's FoxyCart administration.

{% if (context == 'cart' or context == 'checkout') and item_count > 0 %}
<script type="text/javascript" charset="utf-8">
(function (FC, $) {
    FC.customFlatRates = {};
    FC.customFlatRates.shipping_results = [];
    FC.customFlatRates.config = {
        'autoSelect': false  /* Set to true if you'd like the top shipping option to be automatically selected for the user */
    FC.customFlatRates.logic = function(address) {
    /* Flat Rate Shipping Modification Logic v2.0.12 */
    FC.customFlatRates.add=function(a,b,c,d){FC.customFlatRates.shipping_results.push({method:c,price:b,service_id:a,service_name:d})};FC.customFlatRates.hide=function(a){a=FC.customFlatRates.filterShippingOptions(a);for(var b=0;b<a.length;b++)FC.customFlatRates.shipping_results[a[b]].hide=!0};FC.customFlatRates.show=function(a){a=FC.customFlatRates.filterShippingOptions(a);for(var b=0;b<a.length;b++)FC.customFlatRates.shipping_results[a[b]].hide=!1};
    FC.customFlatRates.remove=function(a){a=FC.customFlatRates.filterShippingOptions(a);a=a.sort(function(a,b){return b-a});for(var b=a.length-1;0<=b;b--)FC.customFlatRates.shipping_results.splice(a[b],1)};
    FC.customFlatRates.update=function(a,b,c,d){a=FC.customFlatRates.filterShippingOptions(a);for(var e=0;e<a.length;e++){if("number"===typeof b||"string"===typeof b&&""!==b){var f=FC.customFlatRates.modifyPrice(FC.customFlatRates.shipping_results[a[e]].price,b);FC.customFlatRates.shipping_results[a[e]].price=f}"string"===typeof c&&(FC.customFlatRates.shipping_results[a[e]].method=c);"string"===typeof d&&(FC.customFlatRates.shipping_results[a[e]].service_name=d)}};
    FC.api.getShippingOptions=function(a){var b={address_name:a.address_name,state:a.region,country:a.country,city:a.city,postal_code:a.postal_code};FC.customFlatRates.reset();FC.customFlatRates.logic(b);b=!1;for(var c=FC.customFlatRates.shipping_results.length-1;0<=c;c--)FC.customFlatRates.shipping_results[c].hide?FC.customFlatRates.shipping_results.splice(c,1):FC.customFlatRates.shipping_results[c].service_id==a.shipping_service_id&&(b=!0);b||(a.shipping_service_description="",a.shipping_service_id=
    0,a.total_shipping=0,a.total_future_shipping=0);if(0<FC.customFlatRates.shipping_results.length&&(FC.customFlatRates.shipping_results.sort(function(a,b){return a.price-b.price}),FC.customFlatRates.config.autoSelect&&0==a.shipping_service_id||1==FC.customFlatRates.shipping_results.length)){if(FC.json.has_multiship)for(c=0;c<FC.json.multiship_data.length;c++)FC.json.multiship_data[c].address_name==a.address_name&&(FC.json.multiship_data[c].shipping_service_id=FC.customFlatRates.shipping_results[0].service_id,
    FC.json.multiship_data[c].shipping_service_description=FC.customFlatRates.shipping_results[0].method+" "+FC.customFlatRates.shipping_results[0].service_name);else FC.json.shipping_address.shipping_service_id=FC.customFlatRates.shipping_results[0].service_id,FC.json.shipping_address.shipping_service_description=FC.customFlatRates.shipping_results[0].method+" "+FC.customFlatRates.shipping_results[0].service_name;FC.json.show_shipping_tbd=!1}return $.Deferred(function(a){setTimeout(function(){FC.customFlatRates.error_message?
    FC.customFlatRates.filterShippingOptions=function(a){if("number"==typeof a)for(var b=0;b<FC.customFlatRates.shipping_results.length;b++){if(FC.customFlatRates.shipping_results[b].service_id==a)return[b]}else{if("string"==typeof a){var c=[],d={};for(b=0;b<FC.customFlatRates.shipping_results.length;b++){var e=FC.customFlatRates.shipping_results[b];d[b]=e.method+" "+e.service_name}if("all"!=a.toLowerCase()){a=/(fedex|usps|ups)?\s?([\w\s]+)?/i.exec(a);if(void 0==a||""==a)return;for(b in d)void 0!=a[1]&&
    ""!=a[1]&&-1==d[b].toLowerCase().indexOf(a[1].toLowerCase())?delete d[b]:void 0!=a[2]&&""!=a[2]&&-1==d[b].toLowerCase().indexOf(a[2].toLowerCase())&&delete d[b]}for(b in d)c.push(parseInt(b));return c}if("object"==typeof a){c=[];for(b=0;b<a.length;b++)for(d=0;d<FC.customFlatRates.shipping_results.length;d++)FC.customFlatRates.shipping_results[d].service_id==a[b]&&c.push(a[b]);return c}}};
    FC.customFlatRates.modifyPrice=function(a,b){b=b.toString();var c=/([\+\-=\*\/])?(\d+(?:\.\d+)?)(%)?/.exec(b);a=parseFloat(a);var d=parseFloat(c[2]);void 0!=c[3]&&""!=c[3]&&(d=d/100*a);switch(void 0==c[1]&&""!=c[1]?"=":c[1]){case "+":a+=d;break;case "-":a-=d;break;case "/":a/=d;break;case "*":a*=d;break;default:a=d}return 0>a?0:a};
    function requireShipping(){var a=!1;FC.customFlatRates.config.onLocationChange=!1;if(FC.json.has_multiship)for(i=0;i<FC.json.multiship_data.length;i++)FC.json.multiship_data[i].has_shippable_products&&!FC.json.multiship_data[i].has_live_rate_shippable_products&&(FC.json.multiship_data[i].has_live_rate_shippable_products=!0,FC.json.shipping_address.custom_flat_rates_applied=!0,0==FC.json.multiship_data[i].shipping_service_id&&(FC.json.show_address_entry=!0,"cart"==FC.json.context&&(FC.json.show_shipping_tbd=
    !0)));FC.json.shipping_address.has_shippable_products&&!FC.json.shipping_address.has_live_rate_shippable_products&&(FC.json.shipping_address.has_live_rate_shippable_products=!0,FC.json.has_current_live_rate_shipping=!0,FC.json.has_current_flat_rate_shipping=!1,a=!0,0==FC.json.shipping_address.shipping_service_id&&(FC.json.show_address_entry=!0,FC.json.show_shipping_tbd=!0));a&&(a=!1,1==$('[data-fc-id="block-coupon-entry"] .fc-alert p').length&&(a=$('[data-fc-id="block-coupon-entry"] .fc-alert p').html(),
    function saveShippingId(a,b,c){FC.customFlatRates.shipping_service_ids=[];$.each(FC.json.has_multiship?FC.json.multiship_data:[FC.json.shipping_address],function(a,b){FC.customFlatRates.shipping_service_ids[a]=b.shipping_service_id})}
    function restoreShippingId(a,b,c){a=/ThisAction=(\w+)/;if(void 0!==c&&c.hasOwnProperty("url")){if(-1!=c.url.search(a))var d=c.url.match(a)[1];else-1<c.url.indexOf("/cart")&&(d="cart");-1==$.inArray(d,["SaveCartContactInfo","GetAddressByPostalCode","SaveShippingServiceInfo","GetTaxes"])&&(FC.customFlatRates.hasOwnProperty("shipping_service_ids")&&$.each(FC.json.has_multiship?FC.json.multiship_data:[FC.json.shipping_address],function(a,b){0==b.shipping_service_id&&(b.shipping_service_id=void 0===FC.customFlatRates.shipping_service_ids[a]?
    {% if cart_is_fullpage or context == 'checkout' %}FC.client.on("ready.done",requireShipping);{% else %}requireShipping();{% endif %}
})(FC, jQuery);
{% endif %}

If you have created your own cart or checkout templates, ensure you've followed the steps to include the custom code placeholders as detailed here

Step 4: Update Configuration

This script currently provides a single option to alter the way the script works. The following code is found at the top of the snippet you just copied in step 3, and if you'd like to alter the setting detailed below, you can alter that where you've just pasted the snippet code.

FC.customFlatRates.config = {
  'autoSelect': false
  • autoSelect: If set to true, the top option for rates will be automatically selected after your custom logic has run.

Step 5: Customise Shipping Options

Now the fun part, based on whatever criteria you want, add in the different shipping options you require for your site. Add your custom code between the /* BEGIN CUSTOM SHIPPING LOGIC */ and /* END CUSTOM SHIPPING LOGIC */ lines in the first script block.

Address Information

The logic() function where your custom shipping logic is placed is parsed an object of address information for the shipping address that is being requested. Specifically, the following information is passed to the function in a variable named address:

  • address.address_name Used for multiship - will contain the multiship name
  • address.state 2 character code if auto-completed, or the name as entered
  • address.country 2 character country code like US or GB
  • address.city:
  • address.postal_code

Helper functions

There are also a number of functions you can utilise to execute your custom shipping logic:


Adds an additional shipping option to the checkout.


  • code (Number) - Can not be the same as another rate
  • cost (Number) - A number to two decimal places
  • carrier (String)
  • service (String)


  • FC.customFlatRates.add(100, 4.99, 'PostBox', 'Local Delivery');
  • FC.customFlatRates.add(125, 30, ' ', 'Standard Mail');

Notes: Make sure that the code you give the added rate doesn't duplicate a code for an existing rate. Also, you don't have to provide both the carrier and the service parameters - but at least one of them is required.


Hides one or many existing shipping options.


  • selector (Number, String or Array) - Can be the code number of a rate, a string containing the carrier and the service (or a combination of) or an array of codes.


  • FC.customFlatRates.hide(1); - Will hide rate 1
  • FC.customFlatRates.hide('all'); - Will hide all rates
  • FC.customFlatRates.hide('FedEx'); - Will hide all rates for FedEx
  • FC.customFlatRates.hide('Overnight'); - Will hide all rates with a service name that contains 'Overnight'
  • FC.customFlatRates.hide('USPS Express'); - Will hide any rates from USPS that contain the word 'Express'
  • FC.customFlatRates.hide([1,2,5,7]); - Will hide rates with codes 1,2,5 and 7

Notes: Any rates that are still hidden at the end of the custom logic block will be removed to prevent them being shown when you don't want them to. They will be returned if the shipping options are refreshed by an address change by the customer.


Shows one or many existing shipping options.


  • selector (Number, String or Array) - Can be the code number of a rate, a string containing the carrier and the service (or a combination of) or an array of codes.


  • FC.customFlatRates.show(1); - Will show rate 1
  • FC.customFlatRates.show('all'); - Will show all rates
  • FC.customFlatRates.show('FedEx'); - Will show all rates for FedEx
  • FC.customFlatRates.show('Overnight'); - Will show all rates with a service name that contains 'Overnight'
  • FC.customFlatRates.show('USPS Express'); - Will show any rates from USPS that contain the word 'Express'
  • FC.customFlatRates.show([1,2,5,7]); - Will show rates with codes 1,2,5 and 7


Updates the cost of one or many existing shipping options.


  • selector (Number, String or Array) - Can be the code number of a rate, a string containing the carrier and the service (or a combination of) or an array of codes.
  • modifier (String or Number) - Can either be a number (which sets the price to match) or a string containing the operator and a number eg '+20' , '-10' , '*2' , '/2.5' , '=15' . You can also append the string with a % sign to make the operation based on a percentage, eg '+20%' - add 20%, '-20%' - less 20%, '/20%' - divide by 20%, '*20%' - multiply by 20%.
  • carrier (String)
  • service (String)


  • FC.customFlatRates.update(1, 5); - Will set rate 1 to be $5
  • FC.customFlatRates.update('all', '*2'); - Will set all returned rates to double their returned cost
  • FC.customFlatRates.update('FedEx', '+5'); - Will set all rates for FedEx to be $5 more than what they returned as
  • FC.customFlatRates.update('Overnight', '-5'); - Will set all rates with a service name that contains 'Overnight' to be $5 less than returned
  • FC.customFlatRates.update('USPS Express', '=6'); - Will set any rates from USPS that contain the word 'Express' to be $6
  • FC.customFlatRates.update([1,2,5,7], '/2'); - Will set rates with codes 1,2,5 and 7 to be half their returned cost
  • FC.customFlatRates.update('USPS', '+20%'); - Will add 20% of the returned rate to each of the USPS rates
  • FC.customFlatRates.update('USPS Ground', null, null, 'Super Saver'); - Will change “USPS Ground” to be called “USPS Super Saver”


Removes one or many existing shipping options.


  • selector (Number, String or Array) - Can be the code number of a rate, a string containing the carrier and the service (or a combination of) or an array of codes.


  • FC.customFlatRates.remove(1); - Will remove rate 1
  • FC.customFlatRates.remove('all'); - Will remove all rates
  • FC.customFlatRates.remove('FedEx'); - Will remove all rates for FedEx
  • FC.customFlatRates.remove('Overnight'); - Will remove all rates with a service name that contains 'Overnight'
  • FC.customFlatRates.remove('USPS Express'); - Will remove any rates from USPS that contain the word 'Express'
  • FC.customFlatRates.remove([1,2,5,7]); - Will remove rates with codes 1,2,5 and 7


Resets the shipping results to be empty.


  • FC.customFlatRates.reset();


Trigger an error response for the rates.


  • message (String)


  • FC.customFlatRates.error('Sorry, we can\'t ship to Canada');

Migrating from pre 2.0

If you're migrating from a 1.1 store or older, there were two common snippets available:

Multiple Flat Rates
  • addShippingOption()FC.customFlatRates.add()
  • updateShippingOptionCost()FC.customFlatRates.update()
  • removeShippingOption()FC.customFlatRates.remove()
  • removeCustomShippingContainer()FC.customFlatRates.reset()
Tiered Flat Rates

Tiered flat rates provided a single rate to customers. To migrate this snippet, after updating any JSON references within your logic to match what the new 2.0 structure, add the following to the end of your custom logic:

FC.customFlatRates.add(1, shippingCost, '', 'Standard Shipping');

The above line assumes that shippingCost is the name of the variable you set the shipping cost in, and that the shipping you're providing is called “Standard Shipping”. You can edit that line to match your required details.

If you want to run the flat rate logic again based on an event on your checkout (like changing a custom field select box), you can use this code in the .change() function:

FC.checkout.getShippingOptions({address: FC.json.shipping_address});

If you just have one option and would like to hide the selection area, you can use the following code.

FC.client.on('render.done', function(params, next) {


Example 1
  1. Free shipping if the customer orders $40 or more, otherwise it's $5 flat rate
FC.customFlatRates.add(1, 5, 'Postmaster', 'Standard Delivery');
if (FC.json.total_item_price >= 40) {
  FC.customFlatRates.update(1, 0);
Example 2
  1. 3 default shipping options: standard and priority with one postal provider, and express with another.
  2. If there are more than 5 products, remove the express option.
  3. If the total weight of the cart is greater that 10, adjust the shipping costs.
FC.customFlatRates.add(1, 5, 'Postmaster', 'Standard Delivery');
FC.customFlatRates.add(2, 9.45, 'Postmaster', 'Priority Delivery');
FC.customFlatRates.add(3, 10, 'PostPlus', 'Express (Next Day)');
if (FC.json.total_weight > 10) {
  FC.customFlatRates.update(1, 6);
  FC.customFlatRates.update(2, 10);
  FC.customFlatRates.update(3, 11.99);
if (FC.json.item_count > 5) {
Example 3
  1. Postage is calculated as a base price per product, with each subsequent product adding an additional cost.
  2. Two different groups of shipping options are presented, one for local delivery within the US, and one for international addresses based off of the shipping country.
  3. Shipping methods are first displayed after the customer has entered address details like country, state and postcode, and reset whenever they change the shipping address to be a different country.
if (address.country == "US") {
  postage = 10 + ((FC.json.item_count - 1) * 0.50);
  FC.customFlatRates.add(1, postage, 'USPS', 'Standard');
  postage = 12 + ((FC.json.item_count - 1) * 1.50);
  FC.customFlatRates.add(2, postage, 'USPS', 'Express');
} else {
  postage = 15 + ((FC.json.item_count - 1) * 2);
  FC.customFlatRates.add(3, postage, 'USPS', 'International');
Example 4
  1. Postage is assigned per category.
  2. If there is a product from CategoryA in the cart, then present express option
  3. If there is only a product from CategoryB in the cart, provide free shipping as an option
var hasCategoryA = false;
var hasCategoryB = false;
for (p in FC.json.items) {
  switch (FC.json.items[p].category) {
    case "CategoryA":
      hasCategoryA = true;
    case "CategoryB":
      hasCategoryB = true;
if (hasCategoryB && !hasCategoryA) {
  FC.customFlatRates.add(1, 0, '', 'Free Ground Shipping');
} else if (hasCategoryA) {
  FC.customFlatRates.add(2, 5.99, 'USPS', 'Express');
Example 5
  1. Postage is flat rate, but free if “free shipping” coupon is present
  2. Allow free shipping only if a certain coupon code is present. In this case, a coupon that contains the words “free shipping” (case insensitive).
FC.customFlatRates.add(1, 5, "Postmaster", "Standard");
if(!jQuery.isEmptyObject(FC.json.coupons)) {
	jQuery.each(FC.json.coupons, function(i, coupon){
		if(coupon.name.search(/free shipping/i) > -1) {
			FC.customFlatRates.update(1, 0);
Example 6

- Pricing tiers, one for the UK, one for Europe and then the rest of the world

var tier1 = ['GB'];
var tier2 = ['AL', 'AD', 'AM', 'AT', 'BY', 'BE', 'BA', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GE', 'GI', 'GR', 'HU', 'HR', 'IE', 'IS', 'IT', 'LT', 'LU', 'LV', 'MC', 'MK', 'MT', 'NO', 'NL', 'PL', 'PT', 'RO', 'RU', 'SE', 'SI', 'SK', 'SM', 'TR', 'UA', 'VA'];
if (tier1.indexOf(address.country) > -1) {
  // United Kingdom
  FC.customFlatRates.add(1, 10, 'Royal Mail', 'Standard');
} else if (tier2.indexOf(address.country) > -1) {
  // Europe
  FC.customFlatRates.add(2, 20, 'Royal Mail', 'International');
} else {
  // Rest of world
  FC.customFlatRates.add(3, 30, 'Royal Mail', 'International');


  • 2014/09/13 - v2.0 - Initial version
  • 2014/09/16 - v2.0.1 - TBD was persisting on cart when autoselect was enabled or a single rate was returned
  • 2014/09/18 - v2.0.2 - Ensure the shipping description is correctly set when auto-selecting
  • 2014/09/20 - v2.0.3 - Fix missing semi-colons causing issues in Sidecart
  • 2015/01/16 - v2.0.4 - Fix issue with early versions of IE acting strangely with regex
  • 2015/02/20 - v2.0.5 - Add refresh of rates on checkout submit to prevent specific scenarios
  • 2015/03/30 - v2.0.6 - Minor update
  • 2015/09/21 - v2.0.7 - Ensure selected shipping service is maintained when altering the cart
  • 2015/10/15 - v2.0.8 - Update to the events that the snippet uses to hook into the page
  • 2017/08/28 - v2.0.9 - Correcting logic to only try sorting and selecting a default rate if there are actual results
  • 2017/09/13 - v2.0.10 - Small bugfix from previous version relating to auto-selecting rates
  • 2017/11/20 - v2.0.11 - Small change to the delay until the custom rates are returned to the page
  • 2018/05/03 - v2.0.12 - Persist coupon error message temporarily to allow error response to be seen

Site Tools