On this page
As of October 14, 2026, both Shopify Scripts milestones have passed: editing and publishing stopped on April 15, and Scripts stopped executing on June 30. Review Shopify’s Customizations Report and identify any checkout behavior that depended on Scripts.
This guide explains how to assess affected customizations and use Shopify Functions for supported discount, delivery, and payment logic. Scripts handled checkout behavior; they did not implement post-purchase order edits such as changing a shipping address after an order was placed.

Quick Answer: Scripts → Functions in 60 Seconds
Current state: Shopify Scripts stopped executing on June 30, 2026. Shopify Functions provide supported APIs for checkout customizations such as discounts, delivery, and payment. Public apps that use Functions are available across plans; custom apps that contain Functions require Shopify Plus. Start with Shopify’s migration guide and the current CLI-generated extension template, then validate behavior in a development store.
What changed in 2026
Shopify’s announced dates have passed. The Script Editor became read-only on April 15, and Scripts stopped executing on June 30, 2026.
| Date | What Happens | Your Action |
|---|---|---|
| April 15, 2026 (passed) | Editing and publishing Shopify Scripts stopped. | New changes needed a supported replacement. |
| June 30, 2026 (passed) | Shopify Scripts stopped executing. | Audit any affected checkout logic and review its current replacement. |
If a store depended on a Script, its behavior stopped running after the cutoff unless it had been replaced or otherwise reconfigured. Check actual checkout behavior in the store; don’t assume every affected setting falls back in the same way.
Tip: Review the Customizations Report in Shopify Admin to inventory customizations affected by the Scripts sunset. Use Shopify’s current migration documentation to confirm the replacement for each one.
Functions vs Scripts: What Actually Changed
| Dimension | Shopify Scripts (deprecated) | Shopify Functions (replacement) |
|---|---|---|
| Language | Ruby DSL (Shopify-specific) | Rust or JavaScript, with current tooling guidance for the chosen runtime |
| Runtime | Sandboxed Ruby on Shopify infra | WebAssembly (WASM) runtime |
| Plan availability | Plus only | Public apps that use Functions are available across plans; custom apps with Functions require Plus |
| Editor | In-admin Script Editor | Local IDE + Shopify CLI |
| Versioning | None — live edits | Git-friendly — full version control |
| Testing | Manual in checkout | Local dev with shopify app dev, preview links |
| Deployment | Click “Save” in admin | shopify app deploy from terminal |
| Targets | Line items, shipping, payments | Discounts, Cart Transform, Validation, Delivery Customization, Payment Customization, Order Routing, Fulfillment Constraints, more |
The architectural shift matters: Scripts were Ruby customizations in the Script Editor; Functions are app extensions developed with Shopify’s supported tooling. Their extension targets and plan requirements differ, so use the migration documentation to map each Script to a supported replacement.
Mapping Your Scripts to the Right Function Type
A Script may map to a Function, a public app, a different supported setting, or no replacement. Use Shopify’s migration guide to check the correct option for each customization.
| Old Script Type | What It Did | New Function API | Function Target |
|---|---|---|---|
| Line Item Script | Apply discounts to specific products / customers / cart conditions | Cart & Checkout Discounts API | cart.lines.discounts.generate.run |
| Shipping Script (discount) | Free / discounted shipping based on cart rules | Cart & Checkout Discounts API | cart.delivery-options.discounts.generate.run |
| Shipping Script (hide / rename / reorder) | Hide a shipping rate above $X, rename “Standard” to “Free over $50” | Delivery Customization API | cart.delivery-options.transform.run |
| Payment Script | Hide PayPal for B2B, hide COD over $500, reorder methods | Payment Customization API | cart.payment-methods.transform.run |
| Cart-modifying Script (rare) | Bundle products, swap line items | Cart Transform API | cart.transform.run |
| Block-checkout Script | Reject cart if SKU mix invalid | Cart & Checkout Validation API | cart.validations.generate.run |
Several Scripts may map to one Function or require separate replacements. Confirm the mapping and behavior for each case before combining logic.

Prerequisite: Set Up Your Local Dev Environment
Before scaffolding any Function, you need three things installed locally. Run these checks in your terminal.
1. Node.js
Use a Node.js version supported by the current Shopify CLI release; check the CLI’s installation requirements before setting up the project.
node --version
# Check the version required by your installed Shopify CLIIf older, install via nvm or download from nodejs.org.
2. Current Shopify CLI
Install the current Shopify CLI version and check it with shopify version. For the current Rust Function crate (2.0.0 and later), Shopify documents CLI 3.85.0 or higher.
3. Rust toolchain (only if you’ll write Functions in Rust)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
cargo --versionJavaScript Functions don’t need Rust. Pick one language for your team and stick with it — mixing both adds maintenance overhead.
4. A development store
Either log into your Partner dashboard and create a new development store, or use an existing one. You’ll deploy Functions to this store before promoting to production.
Scaffolding Your First Function
The CLI does most of the boilerplate. Inside any directory:
# Create a new Shopify app (skip if you already have one)
shopify app init --name my-checkout-functions
cd my-checkout-functions
# Generate a Function extension
shopify app generate extensionThe CLI walks you through prompts. For a discount Function, you’d pick:
- Type: Function
- Template:
discount(orcart_checkout_validation,delivery_customization,payment_customization, etc.) - Language: Rust or JavaScript
- Name: something like
volume-discount-fn
This creates extensions/volume-discount-fn/ with:
extensions/volume-discount-fn/
├── shopify.extension.toml # Function config — targets, build, version
├── src/
│ ├── cart_lines_discounts_generate_run.graphql # Input query
│ └── cart_lines_discounts_generate_run.rs # Function logic
├── Cargo.toml # Rust dependencies (Rust only)
└── README.mdThe three files you’ll edit constantly are the .toml (config), the .graphql (input), and the .rs / .js (logic). That’s it.
Tutorial 1: Replacing a Line Item Script (Volume Discount)
Say your old Script gave 10% off the order subtotal whenever the cart had 5+ units of a specific collection. Here’s the Function equivalent.
Step 1.1: The configuration (shopify.extension.toml)
api_version = "2026-10"
[[extensions]]
name = "volume-discount-fn"
handle = "volume-discount-fn"
type = "function"
[[extensions.targeting]]
target = "cart.lines.discounts.generate.run"
input_query = "src/cart_lines_discounts_generate_run.graphql"
export = "cart_lines_discounts_generate_run"
[extensions.build]
command = "cargo build --target=wasm32-unknown-unknown --release"
path = "target/wasm32-unknown-unknown/release/volume-discount-fn.wasm"
watch = ["src/**/*.rs"]Step 1.2: The input query (src/cart_lines_discounts_generate_run.graphql)
query Input {
cart {
lines {
id
quantity
cost {
subtotalAmount {
amount
}
}
merchandise {
... on ProductVariant {
product {
inAnyCollection(ids: ["gid://shopify/Collection/123456789"])
}
}
}
}
}
discount {
discountClasses
}
}Tip: Functions only see the data you query. Keep the GraphQL minimal — every field you skip means a faster, cheaper execution.
Step 1.3: The logic (src/cart_lines_discounts_generate_run.rs)
use super::schema;
use shopify_function::prelude::*;
use shopify_function::Result;
#[shopify_function]
fn cart_lines_discounts_generate_run(
input: schema::cart_lines_discounts_generate_run::Input,
) -> Result<schema::CartLinesDiscountsGenerateRunResult> {
// Bail if discount class doesn't match
let has_order_discount = input
.discount()
.discount_classes()
.contains(&schema::DiscountClass::Order);
if !has_order_discount {
return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
}
// Sum quantities of items in the target collection
let qualifying_qty: i64 = input
.cart()
.lines()
.iter()
.filter(|line| {
if let schema::Merchandise::ProductVariant(v) = line.merchandise() {
*v.product().in_any_collection()
} else {
false
}
})
.map(|line| *line.quantity())
.sum();
if qualifying_qty < 5 {
return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });
}
// Apply 10% off the order subtotal
Ok(schema::CartLinesDiscountsGenerateRunResult {
operations: vec![schema::CartOperation::OrderDiscountsAdd(
schema::OrderDiscountsAddOperation {
selection_strategy: schema::OrderDiscountSelectionStrategy::First,
candidates: vec![schema::OrderDiscountCandidate {
targets: vec![schema::OrderDiscountCandidateTarget::OrderSubtotal(
schema::OrderSubtotalTarget {
excluded_cart_line_ids: vec![],
},
)],
message: Some("Volume discount: 10% off".to_string()),
value: schema::OrderDiscountCandidateValue::Percentage(
schema::Percentage { value: Decimal(10.0) }
),
conditions: None,
associated_discount_code: None,
}],
},
)],
})
} Step 1.4: Test, deploy, activate
# Local development with hot reload
shopify app dev
# When ready, deploy
shopify app deploy
# In the GraphiQL panel that opens (press `g` in the dev terminal),
# create the automatic discount that uses your Function:mutation CreateAutomaticDiscount($startsAt: DateTime!) {
discountAutomaticAppCreate(
automaticAppDiscount: {
title: "Volume Discount (5+ collection items)"
functionHandle: "volume-discount-fn"
discountClasses: [ORDER]
startsAt: $startsAt
}
) {
automaticAppDiscount { discountId }
userErrors { field message }
}
} In GraphiQL’s Query Variables pane, set startsAt to a future ISO 8601 timestamp. Review any additional required fields for your API version before running the mutation.
That creates the automatic discount that calls the deployed Function; the Function replaces the Script logic for this use case.
Tutorial 2: Replacing a Shipping Script (Hide Method Above Cart Threshold)
A common Script: “Hide Express Shipping when cart subtotal is over $500 to prevent expensive overnight on big orders.” Here’s the Delivery Customization Function version.
Tutorials 2 and 3 use the current JavaScript templates. Keep their generated targets and src/index.js exports, set api_version = "2026-10" in each extension’s shopify.extension.toml, and run shopify app function typegen in each extension directory after replacing its input query.
Step 2.1: Scaffold
shopify app generate extension --template delivery_customization --flavor vanilla-js --name hide-express-fnStep 2.2: Input query (src/cart_delivery_options_transform_run.graphql)
query CartDeliveryOptionsTransformRunInput {
cart {
cost {
subtotalAmount {
amount
}
}
deliveryGroups {
deliveryOptions {
handle
title
}
}
}
} Step 2.3: Logic (src/cart_delivery_options_transform_run.js)
// @ts-check
/**
* @typedef {import("../generated/api").CartDeliveryOptionsTransformRunInput} CartDeliveryOptionsTransformRunInput
* @typedef {import("../generated/api").CartDeliveryOptionsTransformRunResult} CartDeliveryOptionsTransformRunResult
*/
/** @type {CartDeliveryOptionsTransformRunResult} */
const NO_CHANGES = { operations: [] };
const THRESHOLD = 500.0;
const HIDE_TITLES = ["Express", "Overnight"];
/**
* @param {CartDeliveryOptionsTransformRunInput} input
* @returns {CartDeliveryOptionsTransformRunResult}
*/
export function cartDeliveryOptionsTransformRun(input) {
const subtotal = parseFloat(input.cart.cost.subtotalAmount.amount);
if (subtotal < THRESHOLD) return NO_CHANGES;
const operations = input.cart.deliveryGroups.flatMap((group) =>
group.deliveryOptions
.filter((opt) => HIDE_TITLES.some((t) => (opt.title ?? "").includes(t)))
.map((opt) => ({
deliveryOptionHide: { deliveryOptionHandle: opt.handle },
}))
);
return { operations };
} Step 2.4: Create and activate the delivery customization
After shopify app deploy and installing the app on the target store, run deliveryCustomizationCreate through that app’s authenticated Admin GraphQL client with the write_delivery_customizations scope. Set the functionHandle variable to the extension’s handle in shopify.extension.toml (for example, hide-express-fn).
mutation CreateDeliveryCustomization($functionHandle: String!) {
deliveryCustomizationCreate(deliveryCustomization: {
functionHandle: $functionHandle
title: "Hide express shipping above cart threshold"
enabled: true
}) {
deliveryCustomization { id enabled }
userErrors { field message }
}
}Confirm the response returns an enabled customization and no userErrors, then check Settings → Shipping and delivery → Delivery customizations and test checkout behavior. Deployment supplies the Function; this mutation creates and enables the store’s customization.

Tutorial 3: Replacing a Payment Script (Hide COD for B2B)
Old Script: “Hide Cash on Delivery for any customer tagged ‘B2B’.” Here’s the Payment Customization version.
Step 3.1: Scaffold
shopify app generate extension --template payment_customization --flavor vanilla-js --name hide-cod-b2b-fnStep 3.2: Input query (src/cart_payment_methods_transform_run.graphql)
query CartPaymentMethodsTransformRunInput {
cart {
buyerIdentity {
customer {
hasTags(tags: ["B2B"]) {
tag
hasTag
}
}
}
}
paymentMethods {
id
name
}
} Step 3.3: Logic (src/cart_payment_methods_transform_run.js)
// @ts-check
/**
* @typedef {import("../generated/api").CartPaymentMethodsTransformRunInput} CartPaymentMethodsTransformRunInput
* @typedef {import("../generated/api").CartPaymentMethodsTransformRunResult} CartPaymentMethodsTransformRunResult
*/
/** @type {CartPaymentMethodsTransformRunResult} */
const NO_CHANGES = { operations: [] };
/**
* @param {CartPaymentMethodsTransformRunInput} input
* @returns {CartPaymentMethodsTransformRunResult}
*/
export function cartPaymentMethodsTransformRun(input) {
const tagCheck = input.cart?.buyerIdentity?.customer?.hasTags?.[0];
const isB2B = tagCheck?.hasTag === true;
if (!isB2B) return NO_CHANGES;
const codMethod = input.paymentMethods.find((pm) =>
pm.name.toLowerCase().includes("cash on delivery")
);
if (!codMethod) return NO_CHANGES;
return {
operations: [{ paymentMethodHide: { paymentMethodId: codMethod.id } }],
};
} Step 3.4: Create and activate the payment customization
After deploying and installing the app on the target store, run paymentCustomizationCreate through that app’s authenticated Admin GraphQL client with the write_payment_customizations scope. Set functionHandle to the payment extension’s handle in shopify.extension.toml (for example, hide-cod-b2b-fn).
mutation CreatePaymentCustomization($functionHandle: String!) {
paymentCustomizationCreate(paymentCustomization: {
functionHandle: $functionHandle
title: "Hide Cash on Delivery for B2B-tagged customers"
enabled: true
}) {
paymentCustomization { id enabled }
userErrors { field message }
}
}Check that the response contains an enabled payment customization with no userErrors. Verify it under Settings → Payments → Payment customizations, then test checkout with B2B-tagged and untagged customers.
Since You’re Reading This — A Word on Post-Purchase
For changes after checkout, Revize provides eligible customer self-service before fulfillment through the Order Status page or a confirmation-email link during the merchant-set editing window. Product switching and add-on products require Pro; staff-side editing is in development. Customers review any additional balance and pay through Shopify checkout. The Shopify Order Management 2026: The Plus Operator Playbook covers Shopify’s native post-checkout workflow.
Back to the migration.
Testing Strategy: The Tagged Customer Pattern
Use a development store and representative input fixtures to test the replacement against your documented requirements or saved Script test results. Scripts no longer execute, so a live parallel Script/Function test is no longer available. A customer-tag gate can limit a test Function to internal accounts while you check its behavior; keep production rollout separate from these tests.
Step 1: Tag your test users
In Customers, add the tag FN-TESTER to two or three internal accounts.
Step 2: Branch the Function on tag presence
// Rust example: return this target's empty result for non-testers
let is_tester = input
.cart()
.buyer_identity()
.and_then(|bi| bi.customer())
.map(|c| c.has_any_tag())
.unwrap_or(&false);
if !*is_tester {
return Ok(default_result); // No operations from this Function
}
// New Function logic only runs for tagged usersStep 3: Add hasAnyTag to your input query
cart {
buyerIdentity {
customer {
hasAnyTag(tags: ["FN-TESTER"])
}
}
} Step 4: Verify in checkout
Log in as a tagged user and verify qualifying and non-qualifying carts, discount combinations, and delivery/payment options. Repeat with an untagged user to confirm this Function returns no operations. Compare the results with the expected behavior captured from the old logic; include guest checkout in your tests before removing the gate.
Step 5: Review production activation
Activate the tested replacement through the appropriate discount, delivery, or payment configuration. Record the deployed app version and rollback plan, then remove obsolete Script instructions from your team’s documentation.
Deployment Workflow That Actually Scales
Don’t deploy from a developer’s laptop forever. Once you’ve migrated one or two Scripts, set up a real CI pipeline.
The minimum viable workflow
# .github/workflows/deploy-functions.yml
name: Deploy Shopify Functions
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22' }
- uses: dtolnay/rust-toolchain@stable
with: { targets: wasm32-unknown-unknown }
- run: npm install -g @shopify/cli@latest
- run: shopify app deploy --force
env:
SHOPIFY_APP_AUTOMATION_TOKEN: ${{ secrets.SHOPIFY_APP_AUTOMATION_TOKEN }}Create an App Automation Token in Shopify’s Dev Dashboard and store it as the repository secret shown above. The current CLI requires Node.js 22.12 or newer. Associate the project with its app configuration before using CI; add your test and release-review steps before deployment. This example deploys on a push to main, so adapt the trigger to your team’s release policy.
Versioning and rollback
shopify app deploy creates a versioned snapshot. To roll back:
shopify app versions list
shopify app release --version <previous-version-id>Compared to Scripts — where rollback meant “remember the old code and paste it back in” — this is night and day.

What Most Teams Get Wrong
These are common migration pitfalls to check before releasing a Function.
1. Treating Functions like a 1:1 Script port. They’re not. A single Function can replace three Scripts with cleaner branching. Audit your Scripts as a system before rewriting.
2. Confusing app scopes with Function input data. A Function reads the fields exposed by its input query and configured metafields; it does not gain Admin API access by adding read_customers or read_orders. Add app scopes only for separate Admin API operations, and check the Function API’s input and configuration rules for the extension you are building.
3. Skipping parity tests. Compare the Function with the old Script using representative carts and relevant edge cases before release. Use a staged rollout only when your extension and configuration support one.
4. Skipping the Customizations Report. It’s the single best inventory of what you actually have running. Don’t migrate from memory — migrate from the report.
5. Hardcoding collection IDs and customer tags. Use Function configuration via metafields if you need merchant-tunable values. The CLI can scaffold metafield-backed config — see Shopify’s docs on Function configuration.
Migration Checklist
Use this checklist to review the current state of a Scripts migration.
| Step | Action |
|---|---|
| Inventory | Use Shopify’s Customizations Report and migration documentation to identify affected logic. |
| Map | For each Script, confirm whether a Shopify Function, public app, or another supported setting is the right replacement. |
| Build and validate | Use the current Shopify CLI template and test the behavior in a development store against representative carts. |
| Deploy | Follow Shopify’s current deployment and activation steps for the specific Function target. |
| Confirm | Check live checkout behavior and remove obsolete Script references from internal documentation and workflows. |
Because the execution cutoff has passed, prioritize an audit of live checkout behavior and any remaining replacement work.

Frequently Asked Questions
Do I need Shopify Plus to use Functions?
Public apps that use Functions can be used across Shopify plans, subject to requirements for the specific Function API. Custom apps containing Functions require Shopify Plus; check the target’s plan requirements before choosing an implementation path.
Can I write Functions in TypeScript?
Shopify’s Functions tooling supports JavaScript. TypeScript can be used with an appropriate build pipeline, but follow the current language and template guidance for the specific Function target rather than assuming the CLI scaffolds TypeScript directly.
How fast are Functions compared to Scripts?
Shopify Functions run in a constrained WebAssembly runtime. Check the current limits for your Function target in Shopify’s documentation; don’t rely on a universal execution-time benchmark when planning the implementation.
Can a Function call an external API?
Some Function APIs support network access through a fetch target, with restricted availability and Shopify enablement. Shopify performs the declared HTTP request and passes its result to the run target. Don’t assume an ordinary Function can call an arbitrary API; check the target’s availability and use pre-populated metafields when network access isn’t available.
What’s the difference between Cart Transform and Discounts?
Discounts apply price changes; Cart Transform changes the cart’s contents. Use the Discounts API to apply a 10% off, free shipping, or BOGO. Use Cart Transform to bundle two products into one line item, or split one variant into multiple. Many old Scripts mixed both concerns — when migrating, separate them into two Functions.
How do I test a Function locally without a dev store?
You can run unit tests with cargo test (Rust) or npm test (JS), but full integration testing requires a dev store. The CLI provides shopify app function run which executes your Function against a sample input file — useful for fast iteration. But to confirm checkout behavior end-to-end, you need a real store with a real cart.
Can I have multiple Functions of the same type?
Yes, subject to the API’s activation limits. Discount Functions run concurrently without knowledge of each other, and Shopify combines their candidates according to the discount nodes’ combination rules. Check the Discount Function API and the delivery/payment target documentation instead of assuming outputs feed sequentially into another Function.
What happens to my Script after I deploy the Function?
The execution cutoff has passed; any required behavior needs a supported replacement or reconfiguration. If a Script still appears in Admin, treat it as a legacy record and use current Shopify migration guidance to confirm whether any replacement or cleanup action is needed.
Will the migration affect my SEO or theme?
No — Functions execute server-side at checkout and never touch your theme or product pages. They only modify discounts, shipping options, and payment methods at checkout. Your storefront, product templates, and SEO are completely untouched.
How do I migrate a Script that uses Input.line_items with custom properties?
Custom properties are accessible via the attribute field on cart lines in the GraphQL input. Add attribute(key: "your-key") { value } inside the lines selection. The Function reads it identically to how Scripts read line item properties — just through GraphQL instead of Ruby method calls.
What about analytics and order tags? Can Functions write data?
Functions cannot write order tags or trigger webhooks themselves — they only return operations on the current cart. For tagging or downstream workflows, use Shopify Flow triggered by the order creation event. Many merchants combine a Function (for the discount) with a Flow (for tagging “VOLUME-DISCOUNT-APPLIED” on the order).
Is there a public app I can install instead of building a custom Function?
Yes — the Shopify App Store has dozens of apps that wrap Functions for common use cases. Search for “discount function,” “delivery customization,” or “payment customization.” For straightforward use cases (volume discounts, hide payment methods by tag, free shipping over X), an existing app may save you days of dev work. Reserve custom Functions for logic that’s truly unique to your business.
What if a Script was still in use after June 30?
Published Scripts were deactivated at the cutoff. Review the affected store’s discount, delivery, and payment behavior, prioritize missing rules, and validate the replacement with representative carts before release.
Can I delete the Script Editor app entirely?
Use Shopify’s current admin and migration guidance to decide whether any legacy Script Editor entry needs cleanup. Shopify Functions are app extensions and are managed separately from legacy Scripts.
Next steps
- Review the Customizations Report and Shopify’s current migration guide.
- Test the relevant checkout cases in a development store.
- Confirm which Functions or public apps support the required logic on your plan.
- Verify each production replacement and document its owner and configuration.
Keep checkout-rule testing separate from post-purchase editing tests. For each system, record the responsible app, enabled features, and fulfillment/payment behavior so future changes have a clear owner.
Resources
- Shopify Functions overview — Shopify Developer Docs
- Migrating from Shopify Scripts to Functions
- Build a Discount Function tutorial
- Delivery Customization Function API reference
- Payment Customization Function API reference
- Shopify CLI documentation
Related reading: Shopify Order Editing Statistics (2026) · How to Let Customers Edit Their Own Orders on Shopify (2026)