One-paragraph summary
Cart Transform is a Shopify Functions feature for modifying cart line items in real time via server-side WebAssembly functions. Three operations: lineExpand (split one line into many), lineUpdate (modify a line's price/properties), lineMerge (combine lines). Runtime constraints: 5 ms CPU per invocation, 250 KB output cap, deterministic execution (no network, no clock, no randomness). One Cart Transform function per store maximum. Used by personalizer apps to add per-item fees as clean cart line items instead of legacy hacks (hidden product variants, inflated single prices, order notes). Deploy via Shopify CLI; debug via Partner dashboard Function logs.
What Cart Transform is (architecturally)
Cart Transform is one of several function types in Shopify Functions — Shopify's serverless extensibility layer. It runs as a WebAssembly module on Shopify's edge infrastructure, executing on every cart mutation event: add to cart, remove, quantity change, discount applied. The function reads the current cart state as input and returns an array of operations to apply.
Architecturally:
- Runtime: WebAssembly (compiled from Rust or JavaScript via Shopify's toolchain)
- Trigger: Every cart mutation, before the cart state is returned to the customer's browser
- Input: JSON document describing current cart, current customer (if logged in), shop locale, and any function-specific configuration
- Output: JSON array of operations to apply (or empty array for no-op)
- Limits: 5 ms CPU per invocation, 250 KB output payload max, deterministic only (no external state)
- Deployment: Shopify CLI (`shopify app function deploy`), versioned, rolled out to stores via app subscription
The function doesn't have network access during execution. Any external data (customer tier, inventory state, currency conversion rates) must either be passed in as configuration at deploy time or stored in app metafields and read by the merchant's main app process (which then writes back to cart configuration that the function reads).
Function structure (Rust example)
Typical Rust function structure for adding a personalization fee:
use shopify_function::prelude::*;
use shopify_function::Result;
#[shopify_function]
fn cart_transform(input: input::ResponseData) -> Result<output::CartTransformRunResult> {
let operations = input.cart.lines.iter().filter_map(|line| {
// Find the "Personalization Fee" attribute we stamped at ATC time
let fee_attr = line.attribute.as_ref()
.filter(|a| a.key == "_personalization_fee");
match fee_attr {
Some(attr) => {
let fee = attr.value.parse::<f64>().unwrap_or(0.0);
if fee > 0.0 {
Some(output::CartOperation::Update(output::UpdateOperation {
cart_line_id: line.id.clone(),
price: Some(output::PriceAdjustment {
adjustment: output::PriceAdjustmentValue::FixedPricePerUnit(
output::FixedPricePerUnit {
amount: line.cost.amount_per_quantity.amount + fee,
}
),
}),
..Default::default()
}))
} else { None }
}
None => None,
}
}).collect();
Ok(output::CartTransformRunResult { operations })
}
Or equivalent in JavaScript (compiled via Shopify CLI to WebAssembly):
export function cartTransformRun(input) {
const operations = input.cart.lines
.map(line => {
const feeAttr = line.attribute?.find(a => a.key === '_personalization_fee');
if (!feeAttr) return null;
const fee = parseFloat(feeAttr.value) || 0;
if (fee <= 0) return null;
return {
update: {
cartLineId: line.id,
price: {
adjustment: {
fixedPricePerUnit: {
amount: line.cost.amountPerQuantity.amount + fee,
}
}
}
}
};
})
.filter(Boolean);
return { operations };
}
The Rust version compiles smaller and runs faster (typically under 1 ms); the JavaScript version is easier to write but compiles larger and runs slower (typically 2-4 ms). For the 5 ms budget, either works for most cart sizes; high-volume stores or complex transforms may need Rust to stay under budget.
The three operations: lineExpand, lineUpdate, lineMerge
lineUpdate — modify an existing line
Changes properties, price, quantity, or attributes of an existing cart line without changing the line's identity. The cart line ID stays the same; downstream systems see the same line with updated data. Use for:
- Adding a personalization fee to a base product price
- Updating line properties to include calculated values (total fees, design metadata)
- Setting a custom title for a customized product ("Custom Mug — Engraved")
lineExpand — split one line into many
Replaces one cart line with multiple new lines. The original line's ID disappears; new line IDs are created. Use for:
- Unpacking a bundle SKU into its component products at checkout
- Splitting a multi-side personalization (front + back design) into separate fee line items
- Converting a "build-your-own" parent product into individual configured items
Common gotcha: lineExpand changes line IDs, which can confuse downstream apps (analytics, abandoned cart recovery) that reference the original ID. Test carefully.
lineMerge — combine multiple lines into one
Replaces two or more cart lines with one. Used rarely. Most common case: collapsing duplicate items with identical configuration to clean up the cart display. Each merged line's properties are typically preserved as line item attributes on the resulting line.
Runtime constraints
CPU time: 5 ms per invocation
Each function call has a 5 ms CPU budget. Exceeding causes the function to be aborted and the operations array discarded (cart returns to its pre-function state, no error shown to the customer). For typical cart sizes (1-20 line items) with simple logic, this is generous. For complex transforms (50+ lines, conditional logic, heavy parsing), Rust is recommended.
Output payload: 250 KB max
The operations array must serialize to under 250 KB. For typical cases (10-50 operations) this is plenty. If you're generating per-character pricing or per-step fees on a configurator with 100+ steps, watch your output size.
Determinism
Given the same input, the function must return the same output. No randomness, no system clock access, no network calls, no environment variables. Shopify's runtime enforces this — non-deterministic functions are rejected at deploy time.
Single function per store
Per Shopify's published rule: "You can install a maximum of one cart transform function on each store." If your app installs a Cart Transform function, it claims the only slot. Subsequent apps installing Cart Transform fail or replace yours. This is the main reason multi-personalizer-app stacks are difficult.
Debugging
Local: Shopify CLI
Test functions locally against a JSON cart payload:
cd extensions/my-cart-transform shopify app function run --input test-cart.json
The CLI executes against the same WebAssembly runtime Shopify uses in production. For hot-reload during development:
shopify app function dev
Production: Partner dashboard Function logs
Every function invocation logs input, output, errors, and execution time to the Partner dashboard. Filter by store, function version, or error type. Logs retain ~7 days. For longer retention, export to your own observability stack via the Functions logging API.
Common production errors
| Error | Cause | Fix |
|---|---|---|
| Output exceeded 250 KB | Too many operations | Batch operations, skip no-op updates |
| CPU budget exceeded | Slow logic, large cart | Rewrite in Rust, optimize loops, early-return |
| Schema validation failed | Output JSON doesn't match expected shape | Re-run codegen, verify field names |
| Deterministic check failed | Function uses randomness/clock | Remove non-deterministic code paths |
Deployment
Deploy via Shopify CLI:
shopify app deploy
This compiles the function to WebAssembly, uploads to Shopify, and creates a new function version. The version is rolled out to stores when they upgrade your app subscription (or immediately for new installs). Existing stores stay on the previous version until they accept the new subscription terms (function versioning is tied to app version + scopes).
For staged rollouts, use the Functions versioning API to gate a new version to specific store IDs before promoting to all merchants.
Cart Transform vs other function types
Shopify Functions includes several function types — Cart Transform is one. Others:
- Discount Function — applies discount amounts to cart lines (no structural changes)
- Payment Customization Function — hides, sorts, or renames payment methods at checkout
- Delivery Customization Function — hides, renames, or reorders shipping options
- Checkout Validation Function — blocks checkout if cart state violates rules
- Fulfillment Constraints Function — restricts how line items can be grouped into fulfillments
Each function type has its own one-per-store or many-per-store rules. Cart Transform is one-per-store. Discount Function allows multiple. Always check the latest Shopify Functions documentation for current per-type rules.
When to build your own vs use an app
Build your own Cart Transform function if:
- You have unusual pricing logic no app supports (e.g. multi-store revenue sharing, complex multi-tier B2B pricing tied to your ERP)
- You're building a vertical-specific Shopify app and Cart Transform is core to it
- You have Rust/JS engineering capacity and ongoing maintenance bandwidth
Use an existing app's Cart Transform implementation if:
- You're doing standard personalization fees, bundle pricing, gift wrap upcharges
- You want no maintenance burden as Shopify deprecates / adds function types
- You'd rather focus on product/store growth than function debugging
For personalization-fee Cart Transform specifically, Print It My Way ships this on day one with no developer setup — install, configure pricing in the admin, done. The function is maintained and updated as Shopify's API evolves.
Skip the function development
If your Cart Transform need is personalization fees, Print It My Way ships a production-ready Cart Transform function with admin-side configuration. Free plan covers your first product.
Install Print It My Way — Free Plain-English Cart Transform guide →Frequently asked questions
What is the Shopify Cart Transform API?
Cart Transform is a Shopify Functions feature that lets approved apps modify the contents of a cart in real time using server-side functions. Three operations are supported: lineExpand (split one cart line into multiple), lineUpdate (modify properties or price of an existing line), and lineMerge (combine multiple lines into one). The function runs on Shopify's infrastructure as WebAssembly (Rust or JavaScript compiled), with deterministic output, sub-5ms execution time, and no network access during execution. Per Shopify's documentation, only one cart transform function can be active per store at a time.
What's the difference between lineExpand, lineUpdate, and lineMerge?
lineExpand splits one cart line into multiple — used for unpacking bundles into component products, or expanding a personalization line item into the base product plus separate fee lines. lineUpdate modifies an existing line's properties or price — used to add a personalization surcharge to a product line, or attach metadata. lineMerge combines two or more lines into one — rarely used, but valid for collapsing duplicate items with the same configuration. Most personalizer apps use lineUpdate to add fees, with lineExpand for the bundle case.
What are the rate limits for Cart Transform functions?
Per Shopify's Functions documentation: each function invocation must complete in under 5 ms of CPU time, return output under 250 KB, and execute deterministically (no randomness, no network calls, no clock access). The function runs on every cart mutation — add to cart, update quantity, remove, apply discount — so high-traffic stores see thousands of invocations per minute. Shopify's WebAssembly runtime sandboxes execution to prevent cost-recovery surprises, and you'll see throttling errors in your Partner dashboard if your function exceeds limits.
How do I debug a Cart Transform function locally?
Use the Shopify CLI: shopify app function run with a test cart JSON payload. The CLI executes your function against the same WebAssembly runtime Shopify uses in production, returning the output operations array. For step-debugging, use shopify app function dev to spin up a local Shopify mirror with hot-reload. Production debugging happens via the Partner dashboard's Functions log — every invocation logs input, output, and any errors. Output payloads above 250 KB are truncated in logs, so test with realistic cart sizes before deploying.
Can I install multiple Cart Transform functions on one store?
No. Per Shopify's documented constraint: "You can install a maximum of one cart transform function on each store." If a merchant tries to install a second app that uses Cart Transform, the second install must replace the first or the merchant has to choose. This is the main reason multi-app personalizer stacks (Bold + a separate fee app, Hulk + an upcharge app) typically can't both use Cart Transform — only one wins. Print It My Way takes this slot for stores using PIMW; if you switch personalizer apps, the previous app's Cart Transform releases the slot.
What can Cart Transform NOT do?
Cart Transform cannot: (1) make network calls during execution; (2) write to Shopify's storage; (3) modify the customer or shipping address; (4) charge the customer outside the cart line-item model; (5) execute longer than 5 ms of CPU time; (6) run on the order after checkout completion. For post-purchase logic, use webhooks. For external data, fetch it server-side before showing the customer and pass it as line item properties.
Cart Transform vs Discount Function — when to use which?
Cart Transform modifies the cart's line items (add a fee line, split a bundle, merge duplicates). Discount Function applies discount amounts to existing line items without modifying their structure. Use Cart Transform when you want to add new line items (personalization fees, sub-product splits). Use Discount Function when you want to reduce the price of existing lines (volume discounts, customer-tag pricing). The two can coexist (they're different function types, no one-per-store conflict between them), so you can have both Cart Transform for fees + Discount for promo codes simultaneously.