Personalization fee with lineUpdate
A Shopify cart transform Function that adds an engraving fee to the line price and title when a line item property is filled in. Shopify Plus.
- Function API
- Cart Transform
- Target
- cart.transform.run
- Operation
- lineUpdate
- Plan
- Shopify Plus or development store
- App scope
- write_cart_transforms
- API version
- 2026-10
- Last checked
In short
This cart transform Function adds a personalization fee, 10 in your store currency by default, to the unit price of any line with an engraving, and marks the title. The engraving comes from a line item property. It uses the lineUpdate operation of the cart.transform.run target, Cart Transform API version 2026-10, which Shopify allows only on Shopify Plus and development stores.
How to use it
- Add a text field to the product form: <input name="properties[Engraving]">.
- Create an app with the Shopify CLI, add the extension (shopify app generate extension --template cart_transform) and replace its files with the files below.
- Run the fixtures with shopify app function run --input tests/engraved-line.input.json, then deploy.
- Activate it with cartTransformCreate and set {"fee": 10, "titleSuffix": " (engraved)"} in its metafield if you want other values.
- Show item.title in the cart template so the new title appears.
Code
shopify.extension.tomlapi_version = "2026-10"
[[extensions]]
name = "Personalization fee with lineUpdate"
handle = "personalization-fee-line-update"
type = "function"
[[extensions.targeting]]
target = "cart.transform.run"
input_query = "src/cart_transform_run.graphql"
export = "cart-transform-run"
[extensions.build]
command = ""
path = "dist/function.wasm"
src/cart_transform_run.graphqlquery CartTransformRunInput {
presentmentCurrencyRate
cart {
lines {
id
engraving: attribute(key: "Engraving") {
value
}
cost {
amountPerQuantity {
amount
}
}
merchandise {
__typename
... on ProductVariant {
product {
title
}
}
}
}
}
cartTransform {
metafield(namespace: "$app", key: "function-configuration") {
jsonValue
}
}
}
src/cart_transform_run.js// @ts-check
/**
* @typedef {import("../generated/api").CartTransformRunInput} CartTransformRunInput
* @typedef {import("../generated/api").CartTransformRunResult} CartTransformRunResult
*/
/**
* Settings stored as JSON in the cart transform's metafield. The engraving
* text comes from a line item property named "Engraving" on the product form
* (<input name="properties[Engraving]">); change the attribute key in the
* query if your theme uses another name.
*/
const DEFAULT_CONFIG = {
// Added to each engraved unit, in the store's currency.
fee: 10,
// Appended to the product title on engraved lines.
titleSuffix: " (engraved)",
};
/**
* @param {CartTransformRunInput} input
* @returns {CartTransformRunResult}
*/
export function cartTransformRun(input) {
const config = { ...DEFAULT_CONFIG, ...input.cartTransform?.metafield?.jsonValue };
// Prices in the input and output are in the customer's currency.
const fee = config.fee * Number(input.presentmentCurrencyRate);
const operations = input.cart.lines
.filter((line) => line.merchandise.__typename === "ProductVariant" && (line.engraving?.value ?? "").trim() !== "")
.map((line) => {
const title = line.merchandise.__typename === "ProductVariant" ? line.merchandise.product.title : "";
// A fixed unit price replaces the variant price, so it includes the fee.
const unitPrice = Number(line.cost.amountPerQuantity.amount) + fee;
return {
lineUpdate: {
cartLineId: line.id,
title: `${title}${config.titleSuffix}`,
price: { adjustment: { fixedPricePerUnit: { amount: unitPrice.toFixed(2) } } },
},
};
});
return { operations };
}
Test input and expected output
tests/blank-engraving.input.json{
"presentmentCurrencyRate": "1.0",
"cart": {
"lines": [
{
"id": "gid://shopify/CartLine/1",
"engraving": { "value": " " },
"cost": { "amountPerQuantity": { "amount": "49.0" } },
"merchandise": { "__typename": "ProductVariant", "product": { "title": "Brass Pocket Watch" } }
}
]
},
"cartTransform": { "metafield": null }
}
tests/blank-engraving.output.json{ "operations": [] }
tests/converted-fee.input.json{
"presentmentCurrencyRate": "1.25",
"cart": {
"lines": [
{
"id": "gid://shopify/CartLine/1",
"engraving": { "value": "JS" },
"cost": { "amountPerQuantity": { "amount": "80.0" } },
"merchandise": { "__typename": "ProductVariant", "product": { "title": "Leather Wallet" } }
}
]
},
"cartTransform": { "metafield": { "jsonValue": { "fee": 8, "titleSuffix": " · monogrammed" } } }
}
tests/converted-fee.output.json{
"operations": [
{
"lineUpdate": {
"cartLineId": "gid://shopify/CartLine/1",
"title": "Leather Wallet · monogrammed",
"price": { "adjustment": { "fixedPricePerUnit": { "amount": "90.00" } } }
}
}
]
}
tests/engraved-line.input.json{
"presentmentCurrencyRate": "1.0",
"cart": {
"lines": [
{
"id": "gid://shopify/CartLine/1",
"engraving": { "value": "For Ada, 2026" },
"cost": { "amountPerQuantity": { "amount": "49.0" } },
"merchandise": { "__typename": "ProductVariant", "product": { "title": "Brass Pocket Watch" } }
},
{
"id": "gid://shopify/CartLine/2",
"engraving": null,
"cost": { "amountPerQuantity": { "amount": "49.0" } },
"merchandise": { "__typename": "ProductVariant", "product": { "title": "Brass Pocket Watch" } }
}
]
},
"cartTransform": { "metafield": null }
}
tests/engraved-line.output.json{
"operations": [
{
"lineUpdate": {
"cartLineId": "gid://shopify/CartLine/1",
"title": "Brass Pocket Watch (engraved)",
"price": { "adjustment": { "fixedPricePerUnit": { "amount": "59.00" } } }
}
}
]
}
Written by Lintel. MIT license: free to use in your own store and in client work.
Limits and edge cases
- lineUpdate is rejected on stores that aren’t on Shopify Plus or a development store.
- A line that’s also being expanded or merged can’t be updated; Shopify drops the lineUpdate.
- The fee is converted with presentmentCurrencyRate; prices in the input and output are in the customer’s currency.
- Titles changed by a Function show in the cart, checkout and notification emails only where the template uses item.title or line.title.
Questions
- How do I charge extra for engraving on Shopify?
- On Shopify Plus, a cart transform with lineUpdate can raise the price of the engraved line, as this example does. On other plans, add a separate fee product to the cart.
- Why not change the price in the theme?
- Theme prices are only display. The price customers pay is set server-side, so it has to come from a variant, a discount or a cart transform.
- What if my store isn’t on Shopify Plus?
- Use a separate “engraving fee” product that the theme adds to the cart together with the engraved item.
Shopify documentation
- Written for Function API version 2026-10 and checked against Shopify’s reference on the date shown. Shopify releases a new version every quarter.
- Custom apps that contain Functions only run on Shopify Plus stores; a public app with the same Function runs on any plan unless the page says otherwise.
- Every example ships with test fixtures, and we run them before publishing. Run them again after you change the code: shopify app function run.
- MIT license. Use the code in your own store or in client work.
Need this built, tested and maintained for your store? See Shopify Functions development.
