Shipping Calculator Block

The Shipping Calculator is a theme app extension block that lets shoppers preview shipping rates from any storefront page (product detail page or cart page) before they reach checkout. The block reuses your active Shipping Rate script — no new script type or backend code is required.

circle-info

This block calls the same /task/{id} endpoint that Shopify's Carrier Service uses at checkout, but goes through the app proxy. Your existing shipping_rate script handles both flows.

How It Works

  1. Merchant adds one of two blocks to a theme section in the theme editor:

    • Shipping Calculator (Product) — for product detail pages

    • Shipping Calculator (Cart) — for the cart page

  2. Shopper enters address fields (postal code is required, other fields are toggleable per block).

  3. The block sends a POST request to /apps/{proxy-subpath}/task/{script_id} containing the destination address, line items, currency, locale, and logged-in customer info.

  4. Your shipping_rate script receives the payload as request.body.rate (identical shape to the checkout flow) and returns rates.

  5. The block parses the response and renders each rate inline.

Data flow

Shopper fills form → Block builds payload → POST /apps/{proxy}/task/{id}

DataJet runs your shipping_rate script with request.body.rate

Script returns { "rates": [...] }

Block renders rates list to shopper

Setup

Step 1: Activate a Shipping Rate Script

You must have an active Shipping Rate script in DataJet before the calculator can return rates. See Shipping Rate for setup. Note its script ID (or handle) — you'll paste it into the block settings.

circle-exclamation

Step 2: Add the Block to Your Theme

  1. In Shopify admin, open Online Store > Themes and click Customize on the active theme.

  2. Navigate to a product page (for the Product block) or the cart page (for the Cart block).

  3. In a section that accepts blocks, click Add block and pick:

    • Shipping Calculator (Product) on a product section, or

    • Shipping Calculator (Cart) on the cart section.

  4. Paste the script ID from Step 1 into the Shipping rate script ID field.

  5. Configure which address fields to show (see below) and save.

Block Settings

Both blocks expose the same settings:

Setting
Type
Default
Description

script_id

text

required

Script ID (Mongo ObjectId) or handle of your active shipping_rate script.

title

text

Calculate shipping

Heading rendered above the form. Leave blank to hide.

button_label

text

Calculate

Submit button text.

show_country

checkbox

true

Render a country input.

show_province

checkbox

false

Render a province / state input.

show_city

checkbox

true

Render a city input.

show_street

checkbox

false

Render a street (address1) input.

show_address2

checkbox

false

Render an address line 2 input.

prefill_customer_address

checkbox

true

When the shopper is logged in, pre-fill the inputs from customer.default_address.

The postal/ZIP code input is always rendered.

Request Payload

The block POSTs JSON in the same shape your shipping_rate script already understands:

Notes:

  • destination — only includes keys whose form inputs are present and non-empty. Postal code is always present; the others are gated by block settings.

  • items — for the Product block, contains a single item built from product.selected_or_first_available_variant at render time. For the Cart block, all line items are fetched from /cart.js at submit time.

  • originnot included. The storefront does not know the merchant's warehouse address. If your script needs request.body.rate.origin, hardcode it or fetch it from a DataJet variable.

  • customernull when the shopper is logged out. Otherwise contains id, email, first_name, last_name, tags.

  • _datajet_source — always "storefront_calculator". Use this to distinguish calls from the block from real Shopify Carrier Service calls.

  • _datajet_mode"product" or "cart".

Distinguishing block calls in your script

If you want different logic when the block calls vs. when Shopify's Carrier Service calls at checkout:

Response Format

The block expects the same response your shipping_rate script already returns:

total_price is in cents (matching Shopify's convention). The block formats it via Intl.NumberFormat using the rate's currency. If description is present, it's rendered under the rate name.

States the block renders:

Condition
UI

rates array has entries

List of rates, one per row, with name + formatted price + optional description

rates is empty

Empty-state copy ("No shipping options available for this address.")

Non-2xx response or fetch error

Error copy ("Could not calculate shipping. Please try again.")

Example: Different Rates for Block vs. Checkout

A typical pattern is to return broad estimates from the block but precise per-carrier rates at checkout:

Logged-In Customer Data

When the shopper is logged in, the block renders customer info server-side via Liquid's customer global and includes it in request.body.rate.customer. This means:

  • customer.tags is available without needing the Shipping Rate Context checkout extension. The block delivers tags directly in the payload.

  • If prefill_customer_address is enabled and customer.default_address exists, the form inputs are pre-populated.

Limitations

  • Variant changes on PDP — the Product block snapshots the current variant at server-render time. If the shopper switches variants on the PDP, the payload still references the originally rendered variant until the page reloads.

  • One block per page — multiple instances on the same page work, but each makes its own request. There is no shared state.

  • No origin — the block does not provide request.body.rate.origin. Scripts that depend on it must source it elsewhere.

  • App proxy only — the request goes through the Shopify app proxy. Make sure your app proxy is configured in shopify.app.toml and the subpath in the block's JS asset matches.

Troubleshooting

Form submits but no rates appear

  • Confirm a shipping_rate script is active in DataJet.

  • Check the script ID in block settings matches the active script.

  • Inspect the script's run logs in DataJet — look for the request body and any errors.

Error message appears immediately

  • Open the browser Network tab and inspect the POST /apps/{proxy}/task/{id} response.

  • 404 usually means the app proxy subpath in the block's JS does not match shopify.app.toml's [app_proxy].subpath.

  • 5xx means the script threw — check DataJet's script logs.

Customer fields don't pre-fill

  • Pre-fill only works when the shopper is logged in and has a default address saved on their Shopify customer profile.

  • Confirm prefill_customer_address is checked in block settings.

Cart block sends wrong line items

  • The cart is fetched from /cart.js at submit time. If items look stale, force a hard reload to clear any cached cart state.

Last updated