Scripts Deadline: Functions 마이그레이션 기한은 2026년 6월 30일입니다
Scripts Deadline: Functions 마이그레이션 기한은 2026년 6월 30일입니다
Scripts Deadline: Functions 마이그레이션 기한은 2026년 6월 30일입니다

2026년 4월 16일입니다. 어제인 4월 15일은 Shopify가 Script Editor를 영구적으로 폐쇄한 날이었습니다. 이제 새로운 Script를 생성하거나 게시할 수 없습니다. 이 기한은 사후 구매 로직에 가장 큰 타격을 줍니다. 배송지 주소 변경은 전체 수정된 주문의 30.2%를 차지하는 가장 주요한 사후 구매 수정사항이며(Revize, 2026), 이 수정을 여전히 처리하고 있는 모든 Script는 이제 그대로 동결되었습니다. 실행 종료까지는 75일이 남았으며, 기한은 2026년 6월 30일입니다.
지난 12개월 동안 이 마이그레이션을 "다음 스프린트"로 계속 미뤄온 Shopify Plus 개발자나 Plus 스토어를 운영하는 대행사라면 지금 문제가 생긴 것입니다. "해결하면 좋은" 수준의 문제가 아닙니다. "7월 1일 자정에 체크아웃이 작동하지 않는" 문제입니다. 대부분의 Plus 스토어는 수년에 걸쳐 5개에서 20개의 Script를 누적해 왔으며, 각 Script는 아무도 기억하지 못하는 할인 규칙, 배송 방법 숨기기, 또는 결제 게이트웨이를 백그라운드에서 조용히 구동하고 있습니다.
이 가이드는 지난 1월에 있었으면 좋았을 기술 마이그레이션 매뉴얼입니다. 전략뿐만 아니라 실제 코드를 다룹니다. 이 글을 다 읽을 때쯤이면 Shopify CLI로 Function을 스캐폴딩하고, 할인, 배송 맞춤화 및 결제 맞춤화를 위한 Rust 또는 JavaScript 로직을 작성하고, 태그가 지정된 일부 고객을 대상으로 안전하게 테스트하고, 기존 체크아웃을 망가뜨리지 않고 프로덕션에 배포하는 방법을 알게 될 것입니다.
이제 스토어에서 Scripts를 제거하고 Functions를 탑재해 보겠습니다.

빠른 답변: 60초 만에 Scripts에서 Functions로 전환하기
한 단락 요약: Shopify Scripts(Script Editor의 Ruby 코드, Plus 전용)가 Shopify Functions(Rust 또는 JavaScript로 작성된 WebAssembly 모듈, 모든 요금제에서 사용 가능)로 대체됩니다.
shopify app generate extension으로 Function을 스캐폴딩하고, 필요한 장바구니 데이터를 가져오는run.graphql쿼리를 작성한 다음, 작업(할인, 숨겨진 배송 방법 등)을 반환하는run.rs또는run.js파일을 작성합니다. 그 후shopify app deploy로 배포하고 Admin 또는 GraphQL 뮤테이션을 통해 활성화합니다. Functions는 컴파일된 WASM으로 5ms 미만의 대기 시간으로 실행되며 모든 요금제에서 작동합니다. 이는 Shopify가 앞으로 지원하는 유일한 맞춤화 경로입니다.
6월 30일에 실제로 바뀌는 것
코드를 만지기 전에 날짜를 명확히 하십시오. 중요한 날짜는 두 개가 있습니다.
날짜 | 발생하는 현상 | 수행할 작업 |
|---|---|---|
2026년 4월 15일 (지남) | Script Editor가 읽기 전용으로 전환됩니다. 신규 Script 생성 불가. 기존 Script 수정 불가. | 기존 Script는 여전히 실행됩니다. 지금 마이그레이션하거나 로직을 동결하십시오. |
2026년 6월 30일 | 모든 Shopify Scripts의 실행이 중단됩니다. 예외 없음. | 이 날짜 이전에 대체할 Function이 실제로 작동 중이어야 합니다. |
마이그레이션 결과는 이분법적입니다. 6월 30일까지 Function이 배포되어 체크아웃이 계속 작동하거나, 배포되지 않아 영향을 받는 모든 장바구니가 조용히 기본 가격, 기본 배송 요율 및 활성화된 모든 결제 방법으로 되돌아가거나 둘 중 하나입니다. 부분 점수는 없습니다. Script가 실행되거나 실행되지 않거나 둘 중 하나이며, 6월 30일 이후에는 실행되지 않습니다.
팁: Shopify admin에서
Settings → Checkout → Customizations Report를 여십시오. 스토어에서 활성화된 모든 Script, 작동 내용 및 권장되는 대체 Function 유형이 나열되어 있습니다. 거기서부터 시작하십시오.
Functions와 Scripts 비교: 실제로 변경된 사항
구분 | Shopify Scripts (지원 종료) | Shopify Functions (대체) |
|---|---|---|
언어 | Ruby DSL (Shopify 전용) | Rust, JavaScript, TypeScript |
런타임 | Shopify 인프라의 샌드박스형 Ruby | WebAssembly (WASM) — 5ms 미만 실행 |
요금제 가용성 | Plus 전용 | 모든 요금제 (독점 앱은 Plus 필요, 공개 앱은 제한 없음) |
에디터 | Admin 내 Script Editor | 로컬 IDE + Shopify CLI |
버전 관리 | 없음 — 실시간 즉시 수정 | Git 친화적 — 완전한 버전 관리 지원 |
테스트 | 체크아웃에서 수동 테스트 |
|
배포 | Admin에서 "저장" 클릭 | 터미널에서 |
대상 | 라인 아이템, 배송, 결제 | Discounts, Cart Transform, Validation, Delivery Customization, Payment Customization, Order Routing, Fulfillment Constraints 등 |
아키텍처의 전환이 중요합니다. Scripts가 "텍스트 영역에서 Ruby 코드를 미세 조정하는 것"이었다면, Functions는 "실제 앱을 작성하고, 버전을 관리하고, 로컬에서 테스트하고, 실제 CI 파이프라인을 통해 배포하는 것"입니다. 이는 진입 장벽이 더 높습니다. 또한 이는 가까운 미래에 체크아웃 로직을 위해 수행할 마지막 마이그레이션이 될 것입니다. Functions는 Scripts처럼 임시방편이 아닌 Shopify의 장기적인 약속입니다.
Scripts를 올바른 Function 유형에 매핑하기
현재 가지고 있는 모든 Script는 정확히 하나의 Function API에 매핑됩니다. 모니터에 고정해 두어야 할 매핑 테이블은 다음과 같습니다.
이전 Script 유형 | 기능 | 신규 Function API | Function 대상 |
|---|---|---|---|
Line Item Script | 특정 제품 / 고객 / 장바구니 조건에 할인 적용 | Cart & Checkout Discounts API |
|
Shipping Script (할인) | 장바구니 규칙에 따른 무료 / 할인 배송 | Cart & Checkout Discounts API |
|
Shipping Script (숨기기 / 이름 변경 / 순서 변경) | $X 초과 시 배송 옵션 숨기기, "Standard"를 "$50 이상 무료"로 이름 변경 | Delivery Customization API |
|
Payment Script | B2B 대상 PayPal 숨기기, $500 초과 시 COD 숨기기, 결제 수단 순서 변경 | Payment Customization API |
|
장바구니 수정 Script (드묾) | 제품 번들 구성, 라인 아이템 교체 | Cart Transform API |
|
체크아웃 차단 Script | SKU 조합이 잘못된 경우 장바구니 거부 | Cart & Checkout Validation API |
|
10개의 Script가 있는 경우 실제로는 3~5개의 Function만 빌드하게 될 가능성이 높습니다. 여러 개의 Script가 더 깔끔한 분기 로직을 가진 하나의 Function으로 통합되는 경우가 많기 때문입니다.

필수 조건: 로컬 개발 환경 설정
Function을 스캐폴딩하기 전에 로컬에 설치해야 할 세 가지가 있습니다. 터미널에서 다음 확인 작업을 실행하십시오.
1. Node.js 18+
node --version # Must be >= 18.0.0
node --version # Must be >= 18.0.0
버전이 낮다면 nvm을 통해 설치하거나 nodejs.org에서 다운로드하십시오.
2. Shopify CLI 3+
npm install -g @shopify/cli@latest shopify version # Should output 3.x or higher
npm install -g @shopify/cli@latest shopify version # Should output 3.x or higher
3. Rust 툴체인 (Rust로 Function을 작성하는 경우에만 필요)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup target add wasm32-wasip1 cargo --version
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup target add wasm32-wasip1 cargo --version
JavaScript Functions는 Rust가 필요하지 않습니다. 팀에 맞는 하나의 언어를 선택하여 유지하십시오. 두 언어를 혼용하면 유지 관리 리소스가 증가합니다.
4. 개발용 스토어
Partner 대시보드에 로그인하여 새 개발용 스토어를 생성하거나 기존 스토어를 사용하십시오. 프로덕션에 적용하기 전에 이 스토어에 Functions를 배포하여 테스트하게 됩니다.
첫 번째 Function 스캐폴딩하기
CLI가 대부분의 보일러플레이트를 생성해 줍니다. 원하는 디렉터리 내부에서 다음을 실행하십시오.
# Create a new Shopify app (skip if you already have one) shopify app init my-checkout-functions cd my-checkout-functions # Generate a Function extension shopify app generate extension
# Create a new Shopify app (skip if you already have one) shopify app init my-checkout-functions cd my-checkout-functions # Generate a Function extension shopify app generate extension
CLI가 프롬프트를 통해 안내합니다. 할인 Function의 경우 다음을 선택합니다.
유형: Function
템플릿:
discount(또는cart_checkout_validation,delivery_customization,payment_customization등)언어: Rust 또는 JavaScript
이름:
volume-discount-fn등
이렇게 하면 다음 구조를 가진 extensions/volume-discount-fn/이 생성됩니다.
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.md
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.md
지속적으로 편집하게 될 세 가지 파일은 .toml (설정), .graphql (입력), 그리고 .rs / .js (로직)입니다. 그게 전부입니다.
튜토리얼 1: Line Item Script 대체하기 (수량 할인)
기존 Script가 장바구니에 특정 컬렉션의 제품이 5개 이상 있을 때마다 주문 소계에 10% 할인을 제공했다고 가정해 보겠습니다. 다음은 이에 해당하는 Function입니다.
단계 1.1: 구성 (shopify.extension.toml)
api_version = "2026-01" [[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-wasip1 --release" path = "target/wasm32-wasip1/release/volume-discount-fn.wasm" watch = ["src/**/*.rs"]
api_version = "2026-01" [[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-wasip1 --release" path = "target/wasm32-wasip1/release/volume-discount-fn.wasm" watch = ["src/**/*.rs"]
단계 1.2: 입력 쿼리 (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 } }
query Input { cart { lines { id quantity cost { subtotalAmount { amount } } merchandise { ... on ProductVariant { product { inAnyCollection(ids: ["gid://shopify/Collection/123456789"]) } } } } } discount { discountClasses } }
팁: Functions는 쿼리한 데이터만 볼 수 있습니다. GraphQL을 최소한으로 유지하십시오. 생략하는 필드가 많을수록 실행 속도가 빨라지고 비용이 절감됩니다.
단계 1.3: 로직 (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, }], }, )], }) }
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, }], }, )], }) }
단계 1.4: 테스트, 배포, 활성화
# 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:
# 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 { discountAutomaticAppCreate( automaticAppDiscount: { title: "Volume Discount (5+ collection items)" functionHandle: "volume-discount-fn" discountClasses: [ORDER] startsAt: "2026-04-16T00:00:00Z" } ) { automaticAppDiscount { discountId } userErrors { field message } } }
mutation { discountAutomaticAppCreate( automaticAppDiscount: { title: "Volume Discount (5+ collection items)" functionHandle: "volume-discount-fn" discountClasses: [ORDER] startsAt: "2026-04-16T00:00:00Z" } ) { automaticAppDiscount { discountId } userErrors { field message } } }
완료되었습니다. 이제 Function이 가동되고 버전 관리되며 기존 Script를 완전히 대체합니다.
튜토리얼 2: Shipping Script 대체하기 (장바구니 임계값 초과 시 배송 옵션 숨기기)
자주 사용되는 Script 패턴: "대형 주문의 값비싼 익일 배송을 방지하기 위해 장바구니 소계가 $500를 초과하면 특급 배송을 숨깁니다." 다음은 Delivery Customization Function 버전입니다.
단계 2.1: 스캐폴딩
shopify app generate extension --template delivery_customization --name hide-express-fn
shopify app generate extension --template delivery_customization --name hide-express-fn
단계 2.2: 입력 쿼리 (src/run.graphql)
query Input { cart { cost { subtotalAmount { amount } } deliveryGroups { deliveryOptions { handle title } } } }
query Input { cart { cost { subtotalAmount { amount } } deliveryGroups { deliveryOptions { handle title } } } }
단계 2.3: 로직 (src/run.js — JavaScript 버전)
// @ts-check /** * @typedef {import("../generated/api").RunInput} RunInput * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult */ const NO_CHANGES = { operations: [] }; const THRESHOLD = 500.0; const HIDE_TITLES = ["Express", "Overnight"]; /** * @param {RunInput} input * @returns {FunctionRunResult} */ export function run(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) => ({ hide: { deliveryOptionHandle: opt.handle }, })) ); return { operations }; }
// @ts-check /** * @typedef {import("../generated/api").RunInput} RunInput * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult */ const NO_CHANGES = { operations: [] }; const THRESHOLD = 500.0; const HIDE_TITLES = ["Express", "Overnight"]; /** * @param {RunInput} input * @returns {FunctionRunResult} */ export function run(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) => ({ hide: { deliveryOptionHandle: opt.handle }, })) ); return { operations }; }
단계 2.4: Admin을 통해 활성화 (GraphQL 불필요)
Delivery Customizations에는 내장된 Admin UI가 있습니다. shopify app deploy 실행 후:
Settings → Shipping and delivery로 이동합니다.
하단의 Customizations 섹션으로 스크롤합니다.
Add customization 클릭 → 빌드한 Function을 선택합니다.
저장합니다.
이제 숨기기 규칙이 프로덕션에서 작동합니다. 뮤테이션을 실행할 필요가 없습니다.

튜토리얼 3: Payment Script 대체하기 (B2B 고객 대상 COD 숨기기)
기존 Script: "B2B 태그가 지정된 고객에게는 Cash on Delivery를 숨깁니다." 다음은 Payment Customization 버전입니다.
단계 3.1: 스캐폴딩
shopify app generate extension --template payment_customization --name hide-cod-b2b-fn
shopify app generate extension --template payment_customization --name hide-cod-b2b-fn
단계 3.2: 입력 쿼리
query Input { cart { buyerIdentity { customer { hasTags(tags: [{ tag: "B2B" }]) { tag hasTag } } } } paymentMethods { id name } }
query Input { cart { buyerIdentity { customer { hasTags(tags: [{ tag: "B2B" }]) { tag hasTag } } } } paymentMethods { id name } }
단계 3.3: 로직 (src/run.js)
const NO_CHANGES = { operations: [] }; export function run(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: [{ hide: { paymentMethodId: codMethod.id } }], }; }
const NO_CHANGES = { operations: [] }; export function run(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: [{ hide: { paymentMethodId: codMethod.id } }], }; }
단계 3.4: 활성화
Payment Customizations 역시 Settings → Payments → Customizations 하단에 Admin UI가 존재합니다. 배송 맞춤화와 동일한 흐름으로 진행하여 Function을 선택하고 저장하면 완료됩니다.
이 글을 읽으시는 분들을 위해 — 사후 구매(Post-Purchase)에 대한 조언
이 블로그가 Revize 블로그이므로 간단히 짚고 넘어가겠습니다. Revize는 Functions가 다룰 수 없는 부분을 처리합니다. 주문이 완료된 후 고객이 품목을 추가하거나, 옵션을 변경하거나, 배송지 주소를 수정하거나, 누락한 할인을 적용하고 싶어 할 때가 있습니다. Functions는 체크아웃 단계에서 작동하지만, Revize는 그 이후 단계에서 작동합니다. Functions는 장바구니에 무엇이 허용되는지 결정하고, Revize는 고객과 지원 팀이 주문 취소 및 재주문 과정 없이 사후에 주문을 수정할 수 있도록 지원합니다. 이는 다음 두 부류의 스토어에 매우 중요합니다. 수동 수정이 불가능할 정도로 주문량이 많은 스토어(당연히 Plus 운영자 및 고처리량 Advanced 스토어 포함), 그리고 고객 경험을 브랜드의 핵심 가치로 삼아 "죄송하지만 변경이 불가능합니다"라는 이메일로 인해 재구매 기회를 잃고 싶지 않은 스토어입니다.
만약 마이그레이션 계획이 Scripts → Functions만 다루고 사후 구매 주문 수정 문제를 정리하지 않았다면, 약 3주일 후에 "이게 왜 이렇게 어렵지?"라는 다음 장벽에 부딪히게 될 것입니다. 최근에 발행된 주문 관리 가이드에 사후 체크아웃에 대한 전체 플레이북이 설명되어 있습니다.
다시 마이그레이션 이야기로 돌아가겠습니다.
테스트 전략: 태그 지정 고객 패턴
Functions에는 Admin에서 토글할 수 있는 "초안 모드"가 존재하지 않습니다. 전문적인 테스트 패턴은 신규 Function의 대상을 특정 고객 태그로 제한하고, 기존 Script와 신규 Function을 병렬로 실행하면서 태그 지정 사용자에 대해 동일한 결과가 산출되는지 검증한 후 전환하는 것입니다.
단계 1: 테스트 사용자 태그 지정
Customers에서 내부 테스트 계정 2~3개에 FN-TESTER 태그를 추가합니다.
단계 2: 태그 존재 여부에 따라 Function 분기 처리
// At the top of your run function 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); // Fall through to existing Script } // New Function logic only runs for tagged users
// At the top of your run function 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); // Fall through to existing Script } // New Function logic only runs for tagged users
단계 3: 입력 쿼리에 hasAnyTag 추가
cart { buyerIdentity { customer { hasAnyTag(tags: ["FN-TESTER"]) } } }
cart { buyerIdentity { customer { hasAnyTag(tags: ["FN-TESTER"]) } } }
단계 4: 체크아웃에서 검증
태그가 지정된 사용자로 로그인하여 체크아웃을 진행하며 Function이 실행되는지 확인합니다. 태그가 없는 사용자로 로그인하여 기존 Script가 정상 작동하는지 확인합니다. 며칠 동안 양쪽의 일치 여부가 확인되면 태그 검사 로직을 제거하여 모든 사용자에게 Function을 제공합니다.
단계 5: 기존 Script 게시 취소
Apps → Script Editor → [대상 Script] → Unpublish로 이동합니다. 게시를 취소하면 이제 Function이 유일한 로직 소스가 됩니다.
확장 가능한 배포 워크플로
개발자의 개별 노트북에서 직접 배포하는 방식을 계속 사용하지 마십시오. 한두 개의 Script를 마이그레이션한 후에는 실제 CI 파이프라인을 구축해야 합니다.
최소 기능의 배포 워크플로 구성
# .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: '20' } - uses: dtolnay/rust-toolchain@stable with: { targets: wasm32-wasip1 } - run: npm install -g @shopify/cli@latest - run: shopify app deploy --force env: SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}
# .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: '20' } - uses: dtolnay/rust-toolchain@stable with: { targets: wasm32-wasip1 } - run: npm install -g @shopify/cli@latest - run: shopify app deploy --force env: SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}
Partner 대시보드의 Settings → Tokens에서 파트너 토큰을 생성하십시오. 이제 main 브랜치로 병합될 때마다 Functions가 자동으로 배포됩니다. 더 이상 슬랙에서 배포 여부를 물어볼 필요가 없습니다.
버전 관리 및 롤백
shopify app deploy는 버전이 지정된 스냅샷을 생성합니다. 롤백이 필요한 경우:
shopify app versions list shopify app release --version <previous-version-id
shopify app versions list shopify app release --version <previous-version-id
과거 코드를 기억해 직접 붙여넣어야 했던 Scripts의 롤백 방식과 비교하면 완전히 달라진 시스템입니다.

대부분의 팀이 범하는 실수
지난 한 해 동안 여러 Plus 고객의 마이그레이션을 지원하면서 자주 발견된 5가지 실수 유형입니다.
1. Functions를 Scripts와 1:1 대응으로 여기는 것. 그렇지 않습니다. 단 하나의 Function이 더 깔끔한 분기 처리를 통해 3개의 기존 Script를 대체할 수 있습니다. 작성 전에 시스템 차원에서 Scripts를 감사하십시오.
2. 읽기 권한 범위(scope) 누락. 많은 Functions가 read_customers, read_orders, 또는 write_discounts 권한을 필요로 합니다. shopify.app.toml의 scopes에 추가하고 앱을 다시 인증하십시오. 그렇지 않으면 입력 쿼리가 null을 반환합니다.
3. 일치 테스트 없이 전 고객 대상으로 즉시 배포하는 것. 코드가 완벽해 보이더라도 예외적인 경우(빈 장바구니, 기프트 카드, 스토어 크레딧, B2B 초안 등) 문제가 드러날 수 있습니다. 태그 기반 출시 프로세스를 적용하면 이틀이 더 소요되지만 장애 발생을 사전에 막을 수 있습니다.
4. Customizations Report를 건너뛰는 것. 현재 작동 중인 로직의 목록을 확보할 수 있는 가장 확실한 도구입니다. 기억에 의존하지 말고 해당 보고서를 기준으로 마이그레이션을 시작하십시오.
5. 컬렉션 ID 및 고객 태그 하드코딩. 마케터나 운영자가 변경할 수 있는 변수가 필요한 경우 메타필드를 활용한 Function 구성을 구성하십시오. CLI에서 메타필드 기반의 설정을 스캐폴딩할 수 있습니다. 자세한 내용은 Shopify 가이드를 참조하십시오.
남은 75일간의 마이그레이션 체크리스트
6월 30일까지 무리 없이 마칠 수 있는 주차별 실행 계획안입니다.
주차 | 실행 과제 |
|---|---|
1주 차 (이번 주) | Customizations Report를 확인합니다. 모든 Script 목록을 작성하고 직접 구축할지, 공개 앱을 사용할지, 로직을 제거할지 결정합니다. |
2~3주 차 | 로컬 개발 환경을 설정합니다. 첫 번째 Function을 스캐폴딩하고 가장 간단한 Script(대개 결제 수단 숨기기 규칙)부터 마이그레이션을 시작합니다. |
4~6주 차 | 할인 관련 Scripts를 마이그레이션합니다. Discounts API는 지원하는 기능이 광범위하므로 시간이 가장 오래 걸립니다. 태그 기반 테스트를 철저히 병행합니다. |
7~8주 차 | 배송 관련 Scripts를 마이그레이션합니다. Admin에서 Delivery Customizations를 활성화합니다. |
9~10주 차 | CI 파이프라인을 구축합니다. 모든 배포 작업을 개별 노트북 환경에서 제거하고 시스템화합니다. |
11주 차 (6월 중순) | 최종 양방향 일치 검증을 마칩니다. 기존의 모든 Scripts 게시를 취소합니다. 약 2주간 Functions로만 스토어를 테스트 운영해 봅니다. |
6월 30일 | 기존 시스템의 종료일입니다. 선제적으로 처리를 마쳤으므로 아무 장애 없이 일상이 유지됩니다. |
이번 주에 시작하면 여유 일정을 확보할 수 있습니다. 6월에 시작하면 여유 일정이 없습니다.

자주 묻는 질문 (FAQ)
Functions를 사용하려면 Shopify Plus 요금제가 반드시 필요한가요?
독점 Function(Custom Functions)은 Shopify Plus가 필요하지만, 공개 앱 형태로 제공되는 Functions는 모든 요금제에서 동작합니다. Plus를 이용 중이 아닌 경우 두 가지 방법이 있습니다. 앱스토어에서 해당 기능을 구현한 공개 앱을 찾아 설치하거나, 직접 맞춤형 Functions를 개발하기 위해 Plus로 업그레이드하는 것입니다. 기존에 Scripts를 쓰고 있던 규모의 고객들은 대개 이미 Plus를 이용 중이므로 실질적인 차이는 거의 없습니다.
TypeScript로 Functions를 작성할 수 있나요?
네, TypeScript는 기본적으로 완전 지원되며 CLI에서 스캐폴딩을 제공합니다. shopify app generate extension 실행 시 "JavaScript"를 선택하면 생성되는 프로젝트 폴더에 import("../generated/api") 기반의 타입 정의가 포함됩니다. 필요한 경우 파일 확장자를 .ts로 변경하고 tsconfig.json 파일을 추가하십시오. 최종적으로 빌드되는 WASM 결과물은 개발 언어에 관계없이 동일합니다.
Functions의 속도는 기존 Scripts 대비 얼마나 빠른가요?
Functions는 일반적으로 5ms 미만으로 실행되며, 이는 기존 Ruby Scripts보다 대단히 빠른 속도입니다. WebAssembly로 컴파일되어 고도로 최적화된 런타임에서 작동하므로, Shopify는 엄격한 5ms 제한 정책을 적용하고 있습니다. 코드 실행에 5ms가 초과되면 해당 연산은 무시되며 결과가 반환되지 않습니다. 실제로 잘 작성된 Function은 약 1~2ms 수준에서 처리됩니다. 성능 한계치가 Scripts보다 압도적으로 높습니다.
Function에서 외부 API를 호출할 수 있나요?
아니요, Functions는 외부 네트워크 요청을 보낼 수 없습니다. 입력된 장바구니 데이터를 바탕으로 연산 후 작업을 출력하는 구조의 순수 함수적 형태로 작동합니다. 만약 CRM 조회 결과나 실시간 재고 정보 등의 외부 데이터가 필요하다면, 사전에 해당 데이터를 메타필드에 기록해 두거나 다른 기술 규격(App Proxy, 웹훅, 백엔드 조회가 결합된 Cart Transform 등)을 결합해야 합니다. 이는 기존 Scripts 로직을 마이그레이션할 때 설계 구조의 수정이 필요한 가장 대표적인 원인입니다.
Cart Transform과 Discounts의 차이는 무엇인가요?
Discounts는 가격의 할인 규칙을 결정하며, Cart Transform은 장바구니에 담긴 상품 구성 자체를 변경합니다. 10% 할인, 무료 배송, 1+1 행사 등은 Discounts API를 사용합니다. 두 제품을 하나의 단일 라인 번들로 결합하거나, 단일 에디션을 여러 개별 품목으로 쪼개는 행위 등은 Cart Transform API 영역에 속합니다. 과거 Scripts는 이 두 목적을 혼재해 사용하는 경우가 잦았는데, 마이그레이션 시에는 이를 각각 전용 Function으로 엄격히 구분하여 설계하십시오.
개발 스토어가 없어도 로컬에서 Function 테스트가 가능한가요?
cargo test(Rust) 또는 npm test(JS)를 활용해 독립적인 단위 테스트(Unit Test)를 수행할 수 있지만, 통합 테스트(Integration Test)에는 반드시 개발용 스토어가 수반되어야 합니다. CLI 명령어인 shopify app function run을 통해 준비된 샘플 입력 데이터 파일을 모의 대조하는 작업은 상시 가능하나, 최종 체크아웃 시의 엔드투엔드 동향 검증을 위해서는 실제 제품 카트가 구성된 개발 스토어가 필요합니다.
같은 유형의 Function을 여러 개 동시에 적용해 둘 수 있나요?
네, 동일한 작동 대상(Target)에 여러 개의 Function을 병렬 배치할 수 있으며 일정한 우선순위 및 규칙에 따라 순차 실행됩니다. 할인의 경우 Shopify의 할인 조합(stacking) 규칙을 따릅니다. 배송이나 결제 맞춤화의 경우 각각의 Function이 반환하는 변경 사항이 순차적으로 다음 단계의 입력에 연쇄 반영되는 형태입니다. 구조적 단순성을 위해서는 가급적 한 목표당 하나의 Function 통합을 권장합니다.
Function 배포 후 기존 Script는 어떻게 되나요?
Apps → Script Editor에서 기존 Script를 비활성화하기 전까지는 두 시스템이 병렬로 실행됩니다. 이는 마이그레이션 기간 동안의 비교 테스트를 위한 의도된 설계입니다. 안정적인 동작이 최종 확인되면 수동으로 이전 Script의 게시를 중단하십시오. 참고로 2026년 6월 30일이 경과하면 게시 여부와 무관하게 모든 Scripts 작동은 전면 차단됩니다.
마이그레이션 과정에서 검색 엔진 최적화(SEO)나 테마 구조에 변화가 발생하나요?
아니요, Functions는 순수하게 서버 영역의 체크아웃 단계에서 구동되므로 사용자 웹 테마 영역이나 제품 상세 페이지 등에 일절 개입하지 않습니다. 체크아웃 화면상의 할인 정보 표기, 배송 목록 변경, 결제 수단 필터링 등의 데이터 연산 작업만 수행할 뿐이며, 기존 사이트 및 SEO 환경은 완전히 원본 보존됩니다.
기존에 개별 속성(Custom properties)을 포함해 사용하던 Input.line_items 기반 Script는 어떻게 마이그레이션하나요?
장바구니 행의 attribute 필드를 GraphQL 인프라를 통해 요청하여 동일하게 해당 값을 확보할 수 있습니다. 데이터 수집 대상인 lines 하위에 attribute(key: "your-key") { value } 명세를 포함하십시오. Ruby 메서드 방식의 처리 형태가 GraphQL 방식으로 바뀔 뿐, 데이터에 직접 접근하는 논리는 완전히 일치합니다.
주문 분석 추적 태그(Order tag)를 임의로 심는 동작도 Function을 통해 설정할 수 있나요?
아니요, Functions는 자체적으로 특정 DB에 데이터를 기입하거나 외부 웹훅을 연동하여 트리거를 발생시킬 수 없습니다. 현재 카트 영역의 정보 제어 연산만 담당합니다. 주문 생성 이후 발생해야 할 자동 태그 생성 및 후속 마케팅 활동은 Shopify Flow를 구성하여 주문 완료(Order Created) 시점의 이벤트와 매핑해 연동하는 구조를 사용하십시오. 많은 비즈니스가 금액 감액을 담당하는 Function과 내부 정리를 담당하는 Flow를 결합해 복합 워크플로를 구축해 활용하고 있습니다.
직접 코딩을 하는 방법 외에 미리 패키징된 기성 솔루션을 이용하는 대안이 있나요?
네, Shopify App Store에 Functions를 상용화해 편의성을 제공하고 있는 외부 공개 앱들이 다수 존재합니다. "discount function", "delivery customization", "payment customization" 등의 검색 키워드를 입력해 찾아보실 수 있습니다. 대중적인 동작(구매량에 따른 볼륨 디스카운트, 고객 등급별 특정 결제 수단 차단, 일정 액수 이상 무료 배송 보장 등)의 범위 내라면, 직접 개발에 자원을 소모하는 것 대비 이미 배포된 기성 앱을 활용하는 편이 소요 기간 및 개발 원가를 크게 줄여주는 현명한 선택이 될 수 있습니다. 완전히 차별화된 고유 업무 시나리오 구현이 필요할 때만 인하우스 빌드를 결정하십시오.
기한인 6월 30일을 넘기게 되면 어떻게 처리되나요?
모든 Scripts 작동은 즉시 불통 상태가 되며, 별도의 하위 호환 구조나 임시 유예 조치는 제공되지 않습니다. 각 Script가 기여하고 있던 가치 사슬들(특정 할인 적용, 위험 배송 수단 마스킹 처리, 특정 결제 수단 한정 노출 등)은 즉시 스토어 기본 설정값으로 환원됩니다. 7월 1일 자정(UTC 기준)을 기해 모든 통제력이 기본 설정값으로 리셋됩니다. 해당 업무 처리가 필수 요소인 경우 안전성을 고려하여 기한보다 훨씬 전에 배포를 끝마치는 로드맵을 수립하십시오. 대부분의 프로젝트는 정합성 테스트 등으로 인해 설계 당시보다 더 많은 일수가 최종 소요되는 경향이 있습니다.
현재 설치된 Script Editor 앱을 완전히 제거해도 괜찮을까요?
2026년 6월 30일 일정이 지난 뒤 삭제 처리를 완료하시면 되며, 별도 처리가 없어도 향후 Shopify가 자체 비활성화를 진행할 예정입니다. 더 이상 실코드가 작동하지 않는 시점부터는 에디터 자체가 의미를 상실하기 때문입니다. 마이그레이션 검증 및 실사용 배포가 안전하게 완료된 상황이라면, 지금 바로 모든 작업을 정지하고 앱을 수동 영구 삭제하셔도 Functions 구동에는 영향이 없습니다.
이번 주에 당장 시작해야 할 일
단지 정보 확인 수준으로 이 글을 넘기지 마십시오. 다가오는 7일 동안 아래의 네 가지 행동을 실행하십시오.
1. Customizations Report를 즉시 내려받으십시오. Settings → Checkout → Customizations Report 위치로 이동해 내역을 추출하십시오. 이것이 구현해야 할 백로그 목록입니다.
2. 개발 공간 인프라를 구축하십시오. Node 18+, Shopify CLI 및 필요할 경우 Rust 환경을 구축하십시오. 터미널 창에 shopify version을 입력해 정상 구동을 확인하십시오. 30분이면 충분합니다.
3. 초소형 Function 하나를 직접 배포해 보십시오. 리스크가 거의 없는 단순 결제 수단 비활성화 제어 등을 선정해 마이그레이션 플로우 전체를 한 바퀴 돌려봅니다. 이를 통해 빌드 툴체인의 실제 실행 과정을 명확히 숙지할 수 있습니다.
4. 앞으로의 8주간 캘린더에 고정 일정을 확보하십시오. 마이그레이션 과업은 일상 스프린트의 남는 여유 시간에 간헐적으로 진행할 수 없습니다. 매주 화요일 및 목요일 오후 등 정기 세션을 할당하고 우선순위를 높여 진행하십시오.
그동안 무사히 마이그레이션을 마친 기업들의 공통적인 행동 데이터가 있습니다. '2주간의 조사 및 준비 단계, 3주간의 코딩 수행 및 실배포 과정, 마지막 1주간의 다각도 일치화 조정 과정'을 거쳐 전체 약 6주가 필요합니다. 현재 남은 일정은 10주 정도입니다. 아직은 시간의 여유가 있는 편이며, 이 예비 시간을 지연 처리에 쓰지 말고 밀도 높은 교차 코드 리뷰와 엄격한 품질 점검(QA)에 집중 투자하십시오.
체크아웃 영역의 정리가 마무리되면 대다수의 운영 팀이 해결해야 할 과제는 사후 주문 편집(주소지 오류 긴급 변경, 변심 교환, 배송 지연 품목 정리 등) 영역입니다. 대규모 오더 처리를 감당하고 있는 Plus 비즈니스 또는 독보적인 만족도를 지향하는 스토어라면, Shopify App Store에 상주 중인 Revize가 현재 개발하시려는 Functions 인프라의 훌륭한 파트너가 될 것입니다.
도움이 되는 리소스
관련 아티클
2026년 8월 업데이트 기준. Revize는 고객 스스로 주문 완료 후 직접 정보를 정정 및 수정(배송지 수정, 상품 크기/색상 교환, 주문 취소, 스토어 환불/크레딧 회수 등)할 수 있도록 전담하는 독립 포털 솔루션입니다. 고객 지원 채널의 혼잡을 사전에 차단하고 스스로 문제를 해결하도록 안내하는 Shopify 주문 수정 자동화 방안을 확인하시거나, Shopify App Store에서 Revize를 직접 검색하십시오.
2026년 4월 16일입니다. 어제인 4월 15일은 Shopify가 Script Editor를 영구적으로 폐쇄한 날이었습니다. 이제 새로운 Script를 생성하거나 게시할 수 없습니다. 이 기한은 사후 구매 로직에 가장 큰 타격을 줍니다. 배송지 주소 변경은 전체 수정된 주문의 30.2%를 차지하는 가장 주요한 사후 구매 수정사항이며(Revize, 2026), 이 수정을 여전히 처리하고 있는 모든 Script는 이제 그대로 동결되었습니다. 실행 종료까지는 75일이 남았으며, 기한은 2026년 6월 30일입니다.
지난 12개월 동안 이 마이그레이션을 "다음 스프린트"로 계속 미뤄온 Shopify Plus 개발자나 Plus 스토어를 운영하는 대행사라면 지금 문제가 생긴 것입니다. "해결하면 좋은" 수준의 문제가 아닙니다. "7월 1일 자정에 체크아웃이 작동하지 않는" 문제입니다. 대부분의 Plus 스토어는 수년에 걸쳐 5개에서 20개의 Script를 누적해 왔으며, 각 Script는 아무도 기억하지 못하는 할인 규칙, 배송 방법 숨기기, 또는 결제 게이트웨이를 백그라운드에서 조용히 구동하고 있습니다.
이 가이드는 지난 1월에 있었으면 좋았을 기술 마이그레이션 매뉴얼입니다. 전략뿐만 아니라 실제 코드를 다룹니다. 이 글을 다 읽을 때쯤이면 Shopify CLI로 Function을 스캐폴딩하고, 할인, 배송 맞춤화 및 결제 맞춤화를 위한 Rust 또는 JavaScript 로직을 작성하고, 태그가 지정된 일부 고객을 대상으로 안전하게 테스트하고, 기존 체크아웃을 망가뜨리지 않고 프로덕션에 배포하는 방법을 알게 될 것입니다.
이제 스토어에서 Scripts를 제거하고 Functions를 탑재해 보겠습니다.

빠른 답변: 60초 만에 Scripts에서 Functions로 전환하기
한 단락 요약: Shopify Scripts(Script Editor의 Ruby 코드, Plus 전용)가 Shopify Functions(Rust 또는 JavaScript로 작성된 WebAssembly 모듈, 모든 요금제에서 사용 가능)로 대체됩니다.
shopify app generate extension으로 Function을 스캐폴딩하고, 필요한 장바구니 데이터를 가져오는run.graphql쿼리를 작성한 다음, 작업(할인, 숨겨진 배송 방법 등)을 반환하는run.rs또는run.js파일을 작성합니다. 그 후shopify app deploy로 배포하고 Admin 또는 GraphQL 뮤테이션을 통해 활성화합니다. Functions는 컴파일된 WASM으로 5ms 미만의 대기 시간으로 실행되며 모든 요금제에서 작동합니다. 이는 Shopify가 앞으로 지원하는 유일한 맞춤화 경로입니다.
6월 30일에 실제로 바뀌는 것
코드를 만지기 전에 날짜를 명확히 하십시오. 중요한 날짜는 두 개가 있습니다.
날짜 | 발생하는 현상 | 수행할 작업 |
|---|---|---|
2026년 4월 15일 (지남) | Script Editor가 읽기 전용으로 전환됩니다. 신규 Script 생성 불가. 기존 Script 수정 불가. | 기존 Script는 여전히 실행됩니다. 지금 마이그레이션하거나 로직을 동결하십시오. |
2026년 6월 30일 | 모든 Shopify Scripts의 실행이 중단됩니다. 예외 없음. | 이 날짜 이전에 대체할 Function이 실제로 작동 중이어야 합니다. |
마이그레이션 결과는 이분법적입니다. 6월 30일까지 Function이 배포되어 체크아웃이 계속 작동하거나, 배포되지 않아 영향을 받는 모든 장바구니가 조용히 기본 가격, 기본 배송 요율 및 활성화된 모든 결제 방법으로 되돌아가거나 둘 중 하나입니다. 부분 점수는 없습니다. Script가 실행되거나 실행되지 않거나 둘 중 하나이며, 6월 30일 이후에는 실행되지 않습니다.
팁: Shopify admin에서
Settings → Checkout → Customizations Report를 여십시오. 스토어에서 활성화된 모든 Script, 작동 내용 및 권장되는 대체 Function 유형이 나열되어 있습니다. 거기서부터 시작하십시오.
Functions와 Scripts 비교: 실제로 변경된 사항
구분 | Shopify Scripts (지원 종료) | Shopify Functions (대체) |
|---|---|---|
언어 | Ruby DSL (Shopify 전용) | Rust, JavaScript, TypeScript |
런타임 | Shopify 인프라의 샌드박스형 Ruby | WebAssembly (WASM) — 5ms 미만 실행 |
요금제 가용성 | Plus 전용 | 모든 요금제 (독점 앱은 Plus 필요, 공개 앱은 제한 없음) |
에디터 | Admin 내 Script Editor | 로컬 IDE + Shopify CLI |
버전 관리 | 없음 — 실시간 즉시 수정 | Git 친화적 — 완전한 버전 관리 지원 |
테스트 | 체크아웃에서 수동 테스트 |
|
배포 | Admin에서 "저장" 클릭 | 터미널에서 |
대상 | 라인 아이템, 배송, 결제 | Discounts, Cart Transform, Validation, Delivery Customization, Payment Customization, Order Routing, Fulfillment Constraints 등 |
아키텍처의 전환이 중요합니다. Scripts가 "텍스트 영역에서 Ruby 코드를 미세 조정하는 것"이었다면, Functions는 "실제 앱을 작성하고, 버전을 관리하고, 로컬에서 테스트하고, 실제 CI 파이프라인을 통해 배포하는 것"입니다. 이는 진입 장벽이 더 높습니다. 또한 이는 가까운 미래에 체크아웃 로직을 위해 수행할 마지막 마이그레이션이 될 것입니다. Functions는 Scripts처럼 임시방편이 아닌 Shopify의 장기적인 약속입니다.
Scripts를 올바른 Function 유형에 매핑하기
현재 가지고 있는 모든 Script는 정확히 하나의 Function API에 매핑됩니다. 모니터에 고정해 두어야 할 매핑 테이블은 다음과 같습니다.
이전 Script 유형 | 기능 | 신규 Function API | Function 대상 |
|---|---|---|---|
Line Item Script | 특정 제품 / 고객 / 장바구니 조건에 할인 적용 | Cart & Checkout Discounts API |
|
Shipping Script (할인) | 장바구니 규칙에 따른 무료 / 할인 배송 | Cart & Checkout Discounts API |
|
Shipping Script (숨기기 / 이름 변경 / 순서 변경) | $X 초과 시 배송 옵션 숨기기, "Standard"를 "$50 이상 무료"로 이름 변경 | Delivery Customization API |
|
Payment Script | B2B 대상 PayPal 숨기기, $500 초과 시 COD 숨기기, 결제 수단 순서 변경 | Payment Customization API |
|
장바구니 수정 Script (드묾) | 제품 번들 구성, 라인 아이템 교체 | Cart Transform API |
|
체크아웃 차단 Script | SKU 조합이 잘못된 경우 장바구니 거부 | Cart & Checkout Validation API |
|
10개의 Script가 있는 경우 실제로는 3~5개의 Function만 빌드하게 될 가능성이 높습니다. 여러 개의 Script가 더 깔끔한 분기 로직을 가진 하나의 Function으로 통합되는 경우가 많기 때문입니다.

필수 조건: 로컬 개발 환경 설정
Function을 스캐폴딩하기 전에 로컬에 설치해야 할 세 가지가 있습니다. 터미널에서 다음 확인 작업을 실행하십시오.
1. Node.js 18+
node --version # Must be >= 18.0.0
버전이 낮다면 nvm을 통해 설치하거나 nodejs.org에서 다운로드하십시오.
2. Shopify CLI 3+
npm install -g @shopify/cli@latest shopify version # Should output 3.x or higher
3. Rust 툴체인 (Rust로 Function을 작성하는 경우에만 필요)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup target add wasm32-wasip1 cargo --version
JavaScript Functions는 Rust가 필요하지 않습니다. 팀에 맞는 하나의 언어를 선택하여 유지하십시오. 두 언어를 혼용하면 유지 관리 리소스가 증가합니다.
4. 개발용 스토어
Partner 대시보드에 로그인하여 새 개발용 스토어를 생성하거나 기존 스토어를 사용하십시오. 프로덕션에 적용하기 전에 이 스토어에 Functions를 배포하여 테스트하게 됩니다.
첫 번째 Function 스캐폴딩하기
CLI가 대부분의 보일러플레이트를 생성해 줍니다. 원하는 디렉터리 내부에서 다음을 실행하십시오.
# Create a new Shopify app (skip if you already have one) shopify app init my-checkout-functions cd my-checkout-functions # Generate a Function extension shopify app generate extension
CLI가 프롬프트를 통해 안내합니다. 할인 Function의 경우 다음을 선택합니다.
유형: Function
템플릿:
discount(또는cart_checkout_validation,delivery_customization,payment_customization등)언어: Rust 또는 JavaScript
이름:
volume-discount-fn등
이렇게 하면 다음 구조를 가진 extensions/volume-discount-fn/이 생성됩니다.
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.md
지속적으로 편집하게 될 세 가지 파일은 .toml (설정), .graphql (입력), 그리고 .rs / .js (로직)입니다. 그게 전부입니다.
튜토리얼 1: Line Item Script 대체하기 (수량 할인)
기존 Script가 장바구니에 특정 컬렉션의 제품이 5개 이상 있을 때마다 주문 소계에 10% 할인을 제공했다고 가정해 보겠습니다. 다음은 이에 해당하는 Function입니다.
단계 1.1: 구성 (shopify.extension.toml)
api_version = "2026-01" [[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-wasip1 --release" path = "target/wasm32-wasip1/release/volume-discount-fn.wasm" watch = ["src/**/*.rs"]
단계 1.2: 입력 쿼리 (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 } }
팁: Functions는 쿼리한 데이터만 볼 수 있습니다. GraphQL을 최소한으로 유지하십시오. 생략하는 필드가 많을수록 실행 속도가 빨라지고 비용이 절감됩니다.
단계 1.3: 로직 (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, }], }, )], }) }
단계 1.4: 테스트, 배포, 활성화
# 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 { discountAutomaticAppCreate( automaticAppDiscount: { title: "Volume Discount (5+ collection items)" functionHandle: "volume-discount-fn" discountClasses: [ORDER] startsAt: "2026-04-16T00:00:00Z" } ) { automaticAppDiscount { discountId } userErrors { field message } } }
완료되었습니다. 이제 Function이 가동되고 버전 관리되며 기존 Script를 완전히 대체합니다.
튜토리얼 2: Shipping Script 대체하기 (장바구니 임계값 초과 시 배송 옵션 숨기기)
자주 사용되는 Script 패턴: "대형 주문의 값비싼 익일 배송을 방지하기 위해 장바구니 소계가 $500를 초과하면 특급 배송을 숨깁니다." 다음은 Delivery Customization Function 버전입니다.
단계 2.1: 스캐폴딩
shopify app generate extension --template delivery_customization --name hide-express-fn
단계 2.2: 입력 쿼리 (src/run.graphql)
query Input { cart { cost { subtotalAmount { amount } } deliveryGroups { deliveryOptions { handle title } } } }
단계 2.3: 로직 (src/run.js — JavaScript 버전)
// @ts-check /** * @typedef {import("../generated/api").RunInput} RunInput * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult */ const NO_CHANGES = { operations: [] }; const THRESHOLD = 500.0; const HIDE_TITLES = ["Express", "Overnight"]; /** * @param {RunInput} input * @returns {FunctionRunResult} */ export function run(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) => ({ hide: { deliveryOptionHandle: opt.handle }, })) ); return { operations }; }
단계 2.4: Admin을 통해 활성화 (GraphQL 불필요)
Delivery Customizations에는 내장된 Admin UI가 있습니다. shopify app deploy 실행 후:
Settings → Shipping and delivery로 이동합니다.
하단의 Customizations 섹션으로 스크롤합니다.
Add customization 클릭 → 빌드한 Function을 선택합니다.
저장합니다.
이제 숨기기 규칙이 프로덕션에서 작동합니다. 뮤테이션을 실행할 필요가 없습니다.

튜토리얼 3: Payment Script 대체하기 (B2B 고객 대상 COD 숨기기)
기존 Script: "B2B 태그가 지정된 고객에게는 Cash on Delivery를 숨깁니다." 다음은 Payment Customization 버전입니다.
단계 3.1: 스캐폴딩
shopify app generate extension --template payment_customization --name hide-cod-b2b-fn
단계 3.2: 입력 쿼리
query Input { cart { buyerIdentity { customer { hasTags(tags: [{ tag: "B2B" }]) { tag hasTag } } } } paymentMethods { id name } }
단계 3.3: 로직 (src/run.js)
const NO_CHANGES = { operations: [] }; export function run(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: [{ hide: { paymentMethodId: codMethod.id } }], }; }
단계 3.4: 활성화
Payment Customizations 역시 Settings → Payments → Customizations 하단에 Admin UI가 존재합니다. 배송 맞춤화와 동일한 흐름으로 진행하여 Function을 선택하고 저장하면 완료됩니다.
이 글을 읽으시는 분들을 위해 — 사후 구매(Post-Purchase)에 대한 조언
이 블로그가 Revize 블로그이므로 간단히 짚고 넘어가겠습니다. Revize는 Functions가 다룰 수 없는 부분을 처리합니다. 주문이 완료된 후 고객이 품목을 추가하거나, 옵션을 변경하거나, 배송지 주소를 수정하거나, 누락한 할인을 적용하고 싶어 할 때가 있습니다. Functions는 체크아웃 단계에서 작동하지만, Revize는 그 이후 단계에서 작동합니다. Functions는 장바구니에 무엇이 허용되는지 결정하고, Revize는 고객과 지원 팀이 주문 취소 및 재주문 과정 없이 사후에 주문을 수정할 수 있도록 지원합니다. 이는 다음 두 부류의 스토어에 매우 중요합니다. 수동 수정이 불가능할 정도로 주문량이 많은 스토어(당연히 Plus 운영자 및 고처리량 Advanced 스토어 포함), 그리고 고객 경험을 브랜드의 핵심 가치로 삼아 "죄송하지만 변경이 불가능합니다"라는 이메일로 인해 재구매 기회를 잃고 싶지 않은 스토어입니다.
만약 마이그레이션 계획이 Scripts → Functions만 다루고 사후 구매 주문 수정 문제를 정리하지 않았다면, 약 3주일 후에 "이게 왜 이렇게 어렵지?"라는 다음 장벽에 부딪히게 될 것입니다. 최근에 발행된 주문 관리 가이드에 사후 체크아웃에 대한 전체 플레이북이 설명되어 있습니다.
다시 마이그레이션 이야기로 돌아가겠습니다.
테스트 전략: 태그 지정 고객 패턴
Functions에는 Admin에서 토글할 수 있는 "초안 모드"가 존재하지 않습니다. 전문적인 테스트 패턴은 신규 Function의 대상을 특정 고객 태그로 제한하고, 기존 Script와 신규 Function을 병렬로 실행하면서 태그 지정 사용자에 대해 동일한 결과가 산출되는지 검증한 후 전환하는 것입니다.
단계 1: 테스트 사용자 태그 지정
Customers에서 내부 테스트 계정 2~3개에 FN-TESTER 태그를 추가합니다.
단계 2: 태그 존재 여부에 따라 Function 분기 처리
// At the top of your run function 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); // Fall through to existing Script } // New Function logic only runs for tagged users
단계 3: 입력 쿼리에 hasAnyTag 추가
cart { buyerIdentity { customer { hasAnyTag(tags: ["FN-TESTER"]) } } }
단계 4: 체크아웃에서 검증
태그가 지정된 사용자로 로그인하여 체크아웃을 진행하며 Function이 실행되는지 확인합니다. 태그가 없는 사용자로 로그인하여 기존 Script가 정상 작동하는지 확인합니다. 며칠 동안 양쪽의 일치 여부가 확인되면 태그 검사 로직을 제거하여 모든 사용자에게 Function을 제공합니다.
단계 5: 기존 Script 게시 취소
Apps → Script Editor → [대상 Script] → Unpublish로 이동합니다. 게시를 취소하면 이제 Function이 유일한 로직 소스가 됩니다.
확장 가능한 배포 워크플로
개발자의 개별 노트북에서 직접 배포하는 방식을 계속 사용하지 마십시오. 한두 개의 Script를 마이그레이션한 후에는 실제 CI 파이프라인을 구축해야 합니다.
최소 기능의 배포 워크플로 구성
# .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: '20' } - uses: dtolnay/rust-toolchain@stable with: { targets: wasm32-wasip1 } - run: npm install -g @shopify/cli@latest - run: shopify app deploy --force env: SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}
Partner 대시보드의 Settings → Tokens에서 파트너 토큰을 생성하십시오. 이제 main 브랜치로 병합될 때마다 Functions가 자동으로 배포됩니다. 더 이상 슬랙에서 배포 여부를 물어볼 필요가 없습니다.
버전 관리 및 롤백
shopify app deploy는 버전이 지정된 스냅샷을 생성합니다. 롤백이 필요한 경우:
shopify app versions list shopify app release --version <previous-version-id
과거 코드를 기억해 직접 붙여넣어야 했던 Scripts의 롤백 방식과 비교하면 완전히 달라진 시스템입니다.

대부분의 팀이 범하는 실수
지난 한 해 동안 여러 Plus 고객의 마이그레이션을 지원하면서 자주 발견된 5가지 실수 유형입니다.
1. Functions를 Scripts와 1:1 대응으로 여기는 것. 그렇지 않습니다. 단 하나의 Function이 더 깔끔한 분기 처리를 통해 3개의 기존 Script를 대체할 수 있습니다. 작성 전에 시스템 차원에서 Scripts를 감사하십시오.
2. 읽기 권한 범위(scope) 누락. 많은 Functions가 read_customers, read_orders, 또는 write_discounts 권한을 필요로 합니다. shopify.app.toml의 scopes에 추가하고 앱을 다시 인증하십시오. 그렇지 않으면 입력 쿼리가 null을 반환합니다.
3. 일치 테스트 없이 전 고객 대상으로 즉시 배포하는 것. 코드가 완벽해 보이더라도 예외적인 경우(빈 장바구니, 기프트 카드, 스토어 크레딧, B2B 초안 등) 문제가 드러날 수 있습니다. 태그 기반 출시 프로세스를 적용하면 이틀이 더 소요되지만 장애 발생을 사전에 막을 수 있습니다.
4. Customizations Report를 건너뛰는 것. 현재 작동 중인 로직의 목록을 확보할 수 있는 가장 확실한 도구입니다. 기억에 의존하지 말고 해당 보고서를 기준으로 마이그레이션을 시작하십시오.
5. 컬렉션 ID 및 고객 태그 하드코딩. 마케터나 운영자가 변경할 수 있는 변수가 필요한 경우 메타필드를 활용한 Function 구성을 구성하십시오. CLI에서 메타필드 기반의 설정을 스캐폴딩할 수 있습니다. 자세한 내용은 Shopify 가이드를 참조하십시오.
남은 75일간의 마이그레이션 체크리스트
6월 30일까지 무리 없이 마칠 수 있는 주차별 실행 계획안입니다.
주차 | 실행 과제 |
|---|---|
1주 차 (이번 주) | Customizations Report를 확인합니다. 모든 Script 목록을 작성하고 직접 구축할지, 공개 앱을 사용할지, 로직을 제거할지 결정합니다. |
2~3주 차 | 로컬 개발 환경을 설정합니다. 첫 번째 Function을 스캐폴딩하고 가장 간단한 Script(대개 결제 수단 숨기기 규칙)부터 마이그레이션을 시작합니다. |
4~6주 차 | 할인 관련 Scripts를 마이그레이션합니다. Discounts API는 지원하는 기능이 광범위하므로 시간이 가장 오래 걸립니다. 태그 기반 테스트를 철저히 병행합니다. |
7~8주 차 | 배송 관련 Scripts를 마이그레이션합니다. Admin에서 Delivery Customizations를 활성화합니다. |
9~10주 차 | CI 파이프라인을 구축합니다. 모든 배포 작업을 개별 노트북 환경에서 제거하고 시스템화합니다. |
11주 차 (6월 중순) | 최종 양방향 일치 검증을 마칩니다. 기존의 모든 Scripts 게시를 취소합니다. 약 2주간 Functions로만 스토어를 테스트 운영해 봅니다. |
6월 30일 | 기존 시스템의 종료일입니다. 선제적으로 처리를 마쳤으므로 아무 장애 없이 일상이 유지됩니다. |
이번 주에 시작하면 여유 일정을 확보할 수 있습니다. 6월에 시작하면 여유 일정이 없습니다.

자주 묻는 질문 (FAQ)
Functions를 사용하려면 Shopify Plus 요금제가 반드시 필요한가요?
독점 Function(Custom Functions)은 Shopify Plus가 필요하지만, 공개 앱 형태로 제공되는 Functions는 모든 요금제에서 동작합니다. Plus를 이용 중이 아닌 경우 두 가지 방법이 있습니다. 앱스토어에서 해당 기능을 구현한 공개 앱을 찾아 설치하거나, 직접 맞춤형 Functions를 개발하기 위해 Plus로 업그레이드하는 것입니다. 기존에 Scripts를 쓰고 있던 규모의 고객들은 대개 이미 Plus를 이용 중이므로 실질적인 차이는 거의 없습니다.
TypeScript로 Functions를 작성할 수 있나요?
네, TypeScript는 기본적으로 완전 지원되며 CLI에서 스캐폴딩을 제공합니다. shopify app generate extension 실행 시 "JavaScript"를 선택하면 생성되는 프로젝트 폴더에 import("../generated/api") 기반의 타입 정의가 포함됩니다. 필요한 경우 파일 확장자를 .ts로 변경하고 tsconfig.json 파일을 추가하십시오. 최종적으로 빌드되는 WASM 결과물은 개발 언어에 관계없이 동일합니다.
Functions의 속도는 기존 Scripts 대비 얼마나 빠른가요?
Functions는 일반적으로 5ms 미만으로 실행되며, 이는 기존 Ruby Scripts보다 대단히 빠른 속도입니다. WebAssembly로 컴파일되어 고도로 최적화된 런타임에서 작동하므로, Shopify는 엄격한 5ms 제한 정책을 적용하고 있습니다. 코드 실행에 5ms가 초과되면 해당 연산은 무시되며 결과가 반환되지 않습니다. 실제로 잘 작성된 Function은 약 1~2ms 수준에서 처리됩니다. 성능 한계치가 Scripts보다 압도적으로 높습니다.
Function에서 외부 API를 호출할 수 있나요?
아니요, Functions는 외부 네트워크 요청을 보낼 수 없습니다. 입력된 장바구니 데이터를 바탕으로 연산 후 작업을 출력하는 구조의 순수 함수적 형태로 작동합니다. 만약 CRM 조회 결과나 실시간 재고 정보 등의 외부 데이터가 필요하다면, 사전에 해당 데이터를 메타필드에 기록해 두거나 다른 기술 규격(App Proxy, 웹훅, 백엔드 조회가 결합된 Cart Transform 등)을 결합해야 합니다. 이는 기존 Scripts 로직을 마이그레이션할 때 설계 구조의 수정이 필요한 가장 대표적인 원인입니다.
Cart Transform과 Discounts의 차이는 무엇인가요?
Discounts는 가격의 할인 규칙을 결정하며, Cart Transform은 장바구니에 담긴 상품 구성 자체를 변경합니다. 10% 할인, 무료 배송, 1+1 행사 등은 Discounts API를 사용합니다. 두 제품을 하나의 단일 라인 번들로 결합하거나, 단일 에디션을 여러 개별 품목으로 쪼개는 행위 등은 Cart Transform API 영역에 속합니다. 과거 Scripts는 이 두 목적을 혼재해 사용하는 경우가 잦았는데, 마이그레이션 시에는 이를 각각 전용 Function으로 엄격히 구분하여 설계하십시오.
개발 스토어가 없어도 로컬에서 Function 테스트가 가능한가요?
cargo test(Rust) 또는 npm test(JS)를 활용해 독립적인 단위 테스트(Unit Test)를 수행할 수 있지만, 통합 테스트(Integration Test)에는 반드시 개발용 스토어가 수반되어야 합니다. CLI 명령어인 shopify app function run을 통해 준비된 샘플 입력 데이터 파일을 모의 대조하는 작업은 상시 가능하나, 최종 체크아웃 시의 엔드투엔드 동향 검증을 위해서는 실제 제품 카트가 구성된 개발 스토어가 필요합니다.
같은 유형의 Function을 여러 개 동시에 적용해 둘 수 있나요?
네, 동일한 작동 대상(Target)에 여러 개의 Function을 병렬 배치할 수 있으며 일정한 우선순위 및 규칙에 따라 순차 실행됩니다. 할인의 경우 Shopify의 할인 조합(stacking) 규칙을 따릅니다. 배송이나 결제 맞춤화의 경우 각각의 Function이 반환하는 변경 사항이 순차적으로 다음 단계의 입력에 연쇄 반영되는 형태입니다. 구조적 단순성을 위해서는 가급적 한 목표당 하나의 Function 통합을 권장합니다.
Function 배포 후 기존 Script는 어떻게 되나요?
Apps → Script Editor에서 기존 Script를 비활성화하기 전까지는 두 시스템이 병렬로 실행됩니다. 이는 마이그레이션 기간 동안의 비교 테스트를 위한 의도된 설계입니다. 안정적인 동작이 최종 확인되면 수동으로 이전 Script의 게시를 중단하십시오. 참고로 2026년 6월 30일이 경과하면 게시 여부와 무관하게 모든 Scripts 작동은 전면 차단됩니다.
마이그레이션 과정에서 검색 엔진 최적화(SEO)나 테마 구조에 변화가 발생하나요?
아니요, Functions는 순수하게 서버 영역의 체크아웃 단계에서 구동되므로 사용자 웹 테마 영역이나 제품 상세 페이지 등에 일절 개입하지 않습니다. 체크아웃 화면상의 할인 정보 표기, 배송 목록 변경, 결제 수단 필터링 등의 데이터 연산 작업만 수행할 뿐이며, 기존 사이트 및 SEO 환경은 완전히 원본 보존됩니다.
기존에 개별 속성(Custom properties)을 포함해 사용하던 Input.line_items 기반 Script는 어떻게 마이그레이션하나요?
장바구니 행의 attribute 필드를 GraphQL 인프라를 통해 요청하여 동일하게 해당 값을 확보할 수 있습니다. 데이터 수집 대상인 lines 하위에 attribute(key: "your-key") { value } 명세를 포함하십시오. Ruby 메서드 방식의 처리 형태가 GraphQL 방식으로 바뀔 뿐, 데이터에 직접 접근하는 논리는 완전히 일치합니다.
주문 분석 추적 태그(Order tag)를 임의로 심는 동작도 Function을 통해 설정할 수 있나요?
아니요, Functions는 자체적으로 특정 DB에 데이터를 기입하거나 외부 웹훅을 연동하여 트리거를 발생시킬 수 없습니다. 현재 카트 영역의 정보 제어 연산만 담당합니다. 주문 생성 이후 발생해야 할 자동 태그 생성 및 후속 마케팅 활동은 Shopify Flow를 구성하여 주문 완료(Order Created) 시점의 이벤트와 매핑해 연동하는 구조를 사용하십시오. 많은 비즈니스가 금액 감액을 담당하는 Function과 내부 정리를 담당하는 Flow를 결합해 복합 워크플로를 구축해 활용하고 있습니다.
직접 코딩을 하는 방법 외에 미리 패키징된 기성 솔루션을 이용하는 대안이 있나요?
네, Shopify App Store에 Functions를 상용화해 편의성을 제공하고 있는 외부 공개 앱들이 다수 존재합니다. "discount function", "delivery customization", "payment customization" 등의 검색 키워드를 입력해 찾아보실 수 있습니다. 대중적인 동작(구매량에 따른 볼륨 디스카운트, 고객 등급별 특정 결제 수단 차단, 일정 액수 이상 무료 배송 보장 등)의 범위 내라면, 직접 개발에 자원을 소모하는 것 대비 이미 배포된 기성 앱을 활용하는 편이 소요 기간 및 개발 원가를 크게 줄여주는 현명한 선택이 될 수 있습니다. 완전히 차별화된 고유 업무 시나리오 구현이 필요할 때만 인하우스 빌드를 결정하십시오.
기한인 6월 30일을 넘기게 되면 어떻게 처리되나요?
모든 Scripts 작동은 즉시 불통 상태가 되며, 별도의 하위 호환 구조나 임시 유예 조치는 제공되지 않습니다. 각 Script가 기여하고 있던 가치 사슬들(특정 할인 적용, 위험 배송 수단 마스킹 처리, 특정 결제 수단 한정 노출 등)은 즉시 스토어 기본 설정값으로 환원됩니다. 7월 1일 자정(UTC 기준)을 기해 모든 통제력이 기본 설정값으로 리셋됩니다. 해당 업무 처리가 필수 요소인 경우 안전성을 고려하여 기한보다 훨씬 전에 배포를 끝마치는 로드맵을 수립하십시오. 대부분의 프로젝트는 정합성 테스트 등으로 인해 설계 당시보다 더 많은 일수가 최종 소요되는 경향이 있습니다.
현재 설치된 Script Editor 앱을 완전히 제거해도 괜찮을까요?
2026년 6월 30일 일정이 지난 뒤 삭제 처리를 완료하시면 되며, 별도 처리가 없어도 향후 Shopify가 자체 비활성화를 진행할 예정입니다. 더 이상 실코드가 작동하지 않는 시점부터는 에디터 자체가 의미를 상실하기 때문입니다. 마이그레이션 검증 및 실사용 배포가 안전하게 완료된 상황이라면, 지금 바로 모든 작업을 정지하고 앱을 수동 영구 삭제하셔도 Functions 구동에는 영향이 없습니다.
이번 주에 당장 시작해야 할 일
단지 정보 확인 수준으로 이 글을 넘기지 마십시오. 다가오는 7일 동안 아래의 네 가지 행동을 실행하십시오.
1. Customizations Report를 즉시 내려받으십시오. Settings → Checkout → Customizations Report 위치로 이동해 내역을 추출하십시오. 이것이 구현해야 할 백로그 목록입니다.
2. 개발 공간 인프라를 구축하십시오. Node 18+, Shopify CLI 및 필요할 경우 Rust 환경을 구축하십시오. 터미널 창에 shopify version을 입력해 정상 구동을 확인하십시오. 30분이면 충분합니다.
3. 초소형 Function 하나를 직접 배포해 보십시오. 리스크가 거의 없는 단순 결제 수단 비활성화 제어 등을 선정해 마이그레이션 플로우 전체를 한 바퀴 돌려봅니다. 이를 통해 빌드 툴체인의 실제 실행 과정을 명확히 숙지할 수 있습니다.
4. 앞으로의 8주간 캘린더에 고정 일정을 확보하십시오. 마이그레이션 과업은 일상 스프린트의 남는 여유 시간에 간헐적으로 진행할 수 없습니다. 매주 화요일 및 목요일 오후 등 정기 세션을 할당하고 우선순위를 높여 진행하십시오.
그동안 무사히 마이그레이션을 마친 기업들의 공통적인 행동 데이터가 있습니다. '2주간의 조사 및 준비 단계, 3주간의 코딩 수행 및 실배포 과정, 마지막 1주간의 다각도 일치화 조정 과정'을 거쳐 전체 약 6주가 필요합니다. 현재 남은 일정은 10주 정도입니다. 아직은 시간의 여유가 있는 편이며, 이 예비 시간을 지연 처리에 쓰지 말고 밀도 높은 교차 코드 리뷰와 엄격한 품질 점검(QA)에 집중 투자하십시오.
체크아웃 영역의 정리가 마무리되면 대다수의 운영 팀이 해결해야 할 과제는 사후 주문 편집(주소지 오류 긴급 변경, 변심 교환, 배송 지연 품목 정리 등) 영역입니다. 대규모 오더 처리를 감당하고 있는 Plus 비즈니스 또는 독보적인 만족도를 지향하는 스토어라면, Shopify App Store에 상주 중인 Revize가 현재 개발하시려는 Functions 인프라의 훌륭한 파트너가 될 것입니다.
도움이 되는 리소스
관련 아티클
2026년 8월 업데이트 기준. Revize는 고객 스스로 주문 완료 후 직접 정보를 정정 및 수정(배송지 수정, 상품 크기/색상 교환, 주문 취소, 스토어 환불/크레딧 회수 등)할 수 있도록 전담하는 독립 포털 솔루션입니다. 고객 지원 채널의 혼잡을 사전에 차단하고 스스로 문제를 해결하도록 안내하는 Shopify 주문 수정 자동화 방안을 확인하시거나, Shopify App Store에서 Revize를 직접 검색하십시오.
2026년 4월 16일입니다. 어제인 4월 15일은 Shopify가 Script Editor를 영구적으로 폐쇄한 날이었습니다. 이제 새로운 Script를 생성하거나 게시할 수 없습니다. 이 기한은 사후 구매 로직에 가장 큰 타격을 줍니다. 배송지 주소 변경은 전체 수정된 주문의 30.2%를 차지하는 가장 주요한 사후 구매 수정사항이며(Revize, 2026), 이 수정을 여전히 처리하고 있는 모든 Script는 이제 그대로 동결되었습니다. 실행 종료까지는 75일이 남았으며, 기한은 2026년 6월 30일입니다.
지난 12개월 동안 이 마이그레이션을 "다음 스프린트"로 계속 미뤄온 Shopify Plus 개발자나 Plus 스토어를 운영하는 대행사라면 지금 문제가 생긴 것입니다. "해결하면 좋은" 수준의 문제가 아닙니다. "7월 1일 자정에 체크아웃이 작동하지 않는" 문제입니다. 대부분의 Plus 스토어는 수년에 걸쳐 5개에서 20개의 Script를 누적해 왔으며, 각 Script는 아무도 기억하지 못하는 할인 규칙, 배송 방법 숨기기, 또는 결제 게이트웨이를 백그라운드에서 조용히 구동하고 있습니다.
이 가이드는 지난 1월에 있었으면 좋았을 기술 마이그레이션 매뉴얼입니다. 전략뿐만 아니라 실제 코드를 다룹니다. 이 글을 다 읽을 때쯤이면 Shopify CLI로 Function을 스캐폴딩하고, 할인, 배송 맞춤화 및 결제 맞춤화를 위한 Rust 또는 JavaScript 로직을 작성하고, 태그가 지정된 일부 고객을 대상으로 안전하게 테스트하고, 기존 체크아웃을 망가뜨리지 않고 프로덕션에 배포하는 방법을 알게 될 것입니다.
이제 스토어에서 Scripts를 제거하고 Functions를 탑재해 보겠습니다.

빠른 답변: 60초 만에 Scripts에서 Functions로 전환하기
한 단락 요약: Shopify Scripts(Script Editor의 Ruby 코드, Plus 전용)가 Shopify Functions(Rust 또는 JavaScript로 작성된 WebAssembly 모듈, 모든 요금제에서 사용 가능)로 대체됩니다.
shopify app generate extension으로 Function을 스캐폴딩하고, 필요한 장바구니 데이터를 가져오는run.graphql쿼리를 작성한 다음, 작업(할인, 숨겨진 배송 방법 등)을 반환하는run.rs또는run.js파일을 작성합니다. 그 후shopify app deploy로 배포하고 Admin 또는 GraphQL 뮤테이션을 통해 활성화합니다. Functions는 컴파일된 WASM으로 5ms 미만의 대기 시간으로 실행되며 모든 요금제에서 작동합니다. 이는 Shopify가 앞으로 지원하는 유일한 맞춤화 경로입니다.
6월 30일에 실제로 바뀌는 것
코드를 만지기 전에 날짜를 명확히 하십시오. 중요한 날짜는 두 개가 있습니다.
날짜 | 발생하는 현상 | 수행할 작업 |
|---|---|---|
2026년 4월 15일 (지남) | Script Editor가 읽기 전용으로 전환됩니다. 신규 Script 생성 불가. 기존 Script 수정 불가. | 기존 Script는 여전히 실행됩니다. 지금 마이그레이션하거나 로직을 동결하십시오. |
2026년 6월 30일 | 모든 Shopify Scripts의 실행이 중단됩니다. 예외 없음. | 이 날짜 이전에 대체할 Function이 실제로 작동 중이어야 합니다. |
마이그레이션 결과는 이분법적입니다. 6월 30일까지 Function이 배포되어 체크아웃이 계속 작동하거나, 배포되지 않아 영향을 받는 모든 장바구니가 조용히 기본 가격, 기본 배송 요율 및 활성화된 모든 결제 방법으로 되돌아가거나 둘 중 하나입니다. 부분 점수는 없습니다. Script가 실행되거나 실행되지 않거나 둘 중 하나이며, 6월 30일 이후에는 실행되지 않습니다.
팁: Shopify admin에서
Settings → Checkout → Customizations Report를 여십시오. 스토어에서 활성화된 모든 Script, 작동 내용 및 권장되는 대체 Function 유형이 나열되어 있습니다. 거기서부터 시작하십시오.
Functions와 Scripts 비교: 실제로 변경된 사항
구분 | Shopify Scripts (지원 종료) | Shopify Functions (대체) |
|---|---|---|
언어 | Ruby DSL (Shopify 전용) | Rust, JavaScript, TypeScript |
런타임 | Shopify 인프라의 샌드박스형 Ruby | WebAssembly (WASM) — 5ms 미만 실행 |
요금제 가용성 | Plus 전용 | 모든 요금제 (독점 앱은 Plus 필요, 공개 앱은 제한 없음) |
에디터 | Admin 내 Script Editor | 로컬 IDE + Shopify CLI |
버전 관리 | 없음 — 실시간 즉시 수정 | Git 친화적 — 완전한 버전 관리 지원 |
테스트 | 체크아웃에서 수동 테스트 |
|
배포 | Admin에서 "저장" 클릭 | 터미널에서 |
대상 | 라인 아이템, 배송, 결제 | Discounts, Cart Transform, Validation, Delivery Customization, Payment Customization, Order Routing, Fulfillment Constraints 등 |
아키텍처의 전환이 중요합니다. Scripts가 "텍스트 영역에서 Ruby 코드를 미세 조정하는 것"이었다면, Functions는 "실제 앱을 작성하고, 버전을 관리하고, 로컬에서 테스트하고, 실제 CI 파이프라인을 통해 배포하는 것"입니다. 이는 진입 장벽이 더 높습니다. 또한 이는 가까운 미래에 체크아웃 로직을 위해 수행할 마지막 마이그레이션이 될 것입니다. Functions는 Scripts처럼 임시방편이 아닌 Shopify의 장기적인 약속입니다.
Scripts를 올바른 Function 유형에 매핑하기
현재 가지고 있는 모든 Script는 정확히 하나의 Function API에 매핑됩니다. 모니터에 고정해 두어야 할 매핑 테이블은 다음과 같습니다.
이전 Script 유형 | 기능 | 신규 Function API | Function 대상 |
|---|---|---|---|
Line Item Script | 특정 제품 / 고객 / 장바구니 조건에 할인 적용 | Cart & Checkout Discounts API |
|
Shipping Script (할인) | 장바구니 규칙에 따른 무료 / 할인 배송 | Cart & Checkout Discounts API |
|
Shipping Script (숨기기 / 이름 변경 / 순서 변경) | $X 초과 시 배송 옵션 숨기기, "Standard"를 "$50 이상 무료"로 이름 변경 | Delivery Customization API |
|
Payment Script | B2B 대상 PayPal 숨기기, $500 초과 시 COD 숨기기, 결제 수단 순서 변경 | Payment Customization API |
|
장바구니 수정 Script (드묾) | 제품 번들 구성, 라인 아이템 교체 | Cart Transform API |
|
체크아웃 차단 Script | SKU 조합이 잘못된 경우 장바구니 거부 | Cart & Checkout Validation API |
|
10개의 Script가 있는 경우 실제로는 3~5개의 Function만 빌드하게 될 가능성이 높습니다. 여러 개의 Script가 더 깔끔한 분기 로직을 가진 하나의 Function으로 통합되는 경우가 많기 때문입니다.

필수 조건: 로컬 개발 환경 설정
Function을 스캐폴딩하기 전에 로컬에 설치해야 할 세 가지가 있습니다. 터미널에서 다음 확인 작업을 실행하십시오.
1. Node.js 18+
node --version # Must be >= 18.0.0
버전이 낮다면 nvm을 통해 설치하거나 nodejs.org에서 다운로드하십시오.
2. Shopify CLI 3+
npm install -g @shopify/cli@latest shopify version # Should output 3.x or higher
3. Rust 툴체인 (Rust로 Function을 작성하는 경우에만 필요)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup target add wasm32-wasip1 cargo --version
JavaScript Functions는 Rust가 필요하지 않습니다. 팀에 맞는 하나의 언어를 선택하여 유지하십시오. 두 언어를 혼용하면 유지 관리 리소스가 증가합니다.
4. 개발용 스토어
Partner 대시보드에 로그인하여 새 개발용 스토어를 생성하거나 기존 스토어를 사용하십시오. 프로덕션에 적용하기 전에 이 스토어에 Functions를 배포하여 테스트하게 됩니다.
첫 번째 Function 스캐폴딩하기
CLI가 대부분의 보일러플레이트를 생성해 줍니다. 원하는 디렉터리 내부에서 다음을 실행하십시오.
# Create a new Shopify app (skip if you already have one) shopify app init my-checkout-functions cd my-checkout-functions # Generate a Function extension shopify app generate extension
CLI가 프롬프트를 통해 안내합니다. 할인 Function의 경우 다음을 선택합니다.
유형: Function
템플릿:
discount(또는cart_checkout_validation,delivery_customization,payment_customization등)언어: Rust 또는 JavaScript
이름:
volume-discount-fn등
이렇게 하면 다음 구조를 가진 extensions/volume-discount-fn/이 생성됩니다.
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.md
지속적으로 편집하게 될 세 가지 파일은 .toml (설정), .graphql (입력), 그리고 .rs / .js (로직)입니다. 그게 전부입니다.
튜토리얼 1: Line Item Script 대체하기 (수량 할인)
기존 Script가 장바구니에 특정 컬렉션의 제품이 5개 이상 있을 때마다 주문 소계에 10% 할인을 제공했다고 가정해 보겠습니다. 다음은 이에 해당하는 Function입니다.
단계 1.1: 구성 (shopify.extension.toml)
api_version = "2026-01" [[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-wasip1 --release" path = "target/wasm32-wasip1/release/volume-discount-fn.wasm" watch = ["src/**/*.rs"]
단계 1.2: 입력 쿼리 (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 } }
팁: Functions는 쿼리한 데이터만 볼 수 있습니다. GraphQL을 최소한으로 유지하십시오. 생략하는 필드가 많을수록 실행 속도가 빨라지고 비용이 절감됩니다.
단계 1.3: 로직 (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, }], }, )], }) }
단계 1.4: 테스트, 배포, 활성화
# 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 { discountAutomaticAppCreate( automaticAppDiscount: { title: "Volume Discount (5+ collection items)" functionHandle: "volume-discount-fn" discountClasses: [ORDER] startsAt: "2026-04-16T00:00:00Z" } ) { automaticAppDiscount { discountId } userErrors { field message } } }
완료되었습니다. 이제 Function이 가동되고 버전 관리되며 기존 Script를 완전히 대체합니다.
튜토리얼 2: Shipping Script 대체하기 (장바구니 임계값 초과 시 배송 옵션 숨기기)
자주 사용되는 Script 패턴: "대형 주문의 값비싼 익일 배송을 방지하기 위해 장바구니 소계가 $500를 초과하면 특급 배송을 숨깁니다." 다음은 Delivery Customization Function 버전입니다.
단계 2.1: 스캐폴딩
shopify app generate extension --template delivery_customization --name hide-express-fn
단계 2.2: 입력 쿼리 (src/run.graphql)
query Input { cart { cost { subtotalAmount { amount } } deliveryGroups { deliveryOptions { handle title } } } }
단계 2.3: 로직 (src/run.js — JavaScript 버전)
// @ts-check /** * @typedef {import("../generated/api").RunInput} RunInput * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult */ const NO_CHANGES = { operations: [] }; const THRESHOLD = 500.0; const HIDE_TITLES = ["Express", "Overnight"]; /** * @param {RunInput} input * @returns {FunctionRunResult} */ export function run(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) => ({ hide: { deliveryOptionHandle: opt.handle }, })) ); return { operations }; }
단계 2.4: Admin을 통해 활성화 (GraphQL 불필요)
Delivery Customizations에는 내장된 Admin UI가 있습니다. shopify app deploy 실행 후:
Settings → Shipping and delivery로 이동합니다.
하단의 Customizations 섹션으로 스크롤합니다.
Add customization 클릭 → 빌드한 Function을 선택합니다.
저장합니다.
이제 숨기기 규칙이 프로덕션에서 작동합니다. 뮤테이션을 실행할 필요가 없습니다.

튜토리얼 3: Payment Script 대체하기 (B2B 고객 대상 COD 숨기기)
기존 Script: "B2B 태그가 지정된 고객에게는 Cash on Delivery를 숨깁니다." 다음은 Payment Customization 버전입니다.
단계 3.1: 스캐폴딩
shopify app generate extension --template payment_customization --name hide-cod-b2b-fn
단계 3.2: 입력 쿼리
query Input { cart { buyerIdentity { customer { hasTags(tags: [{ tag: "B2B" }]) { tag hasTag } } } } paymentMethods { id name } }
단계 3.3: 로직 (src/run.js)
const NO_CHANGES = { operations: [] }; export function run(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: [{ hide: { paymentMethodId: codMethod.id } }], }; }
단계 3.4: 활성화
Payment Customizations 역시 Settings → Payments → Customizations 하단에 Admin UI가 존재합니다. 배송 맞춤화와 동일한 흐름으로 진행하여 Function을 선택하고 저장하면 완료됩니다.
이 글을 읽으시는 분들을 위해 — 사후 구매(Post-Purchase)에 대한 조언
이 블로그가 Revize 블로그이므로 간단히 짚고 넘어가겠습니다. Revize는 Functions가 다룰 수 없는 부분을 처리합니다. 주문이 완료된 후 고객이 품목을 추가하거나, 옵션을 변경하거나, 배송지 주소를 수정하거나, 누락한 할인을 적용하고 싶어 할 때가 있습니다. Functions는 체크아웃 단계에서 작동하지만, Revize는 그 이후 단계에서 작동합니다. Functions는 장바구니에 무엇이 허용되는지 결정하고, Revize는 고객과 지원 팀이 주문 취소 및 재주문 과정 없이 사후에 주문을 수정할 수 있도록 지원합니다. 이는 다음 두 부류의 스토어에 매우 중요합니다. 수동 수정이 불가능할 정도로 주문량이 많은 스토어(당연히 Plus 운영자 및 고처리량 Advanced 스토어 포함), 그리고 고객 경험을 브랜드의 핵심 가치로 삼아 "죄송하지만 변경이 불가능합니다"라는 이메일로 인해 재구매 기회를 잃고 싶지 않은 스토어입니다.
만약 마이그레이션 계획이 Scripts → Functions만 다루고 사후 구매 주문 수정 문제를 정리하지 않았다면, 약 3주일 후에 "이게 왜 이렇게 어렵지?"라는 다음 장벽에 부딪히게 될 것입니다. 최근에 발행된 주문 관리 가이드에 사후 체크아웃에 대한 전체 플레이북이 설명되어 있습니다.
다시 마이그레이션 이야기로 돌아가겠습니다.
테스트 전략: 태그 지정 고객 패턴
Functions에는 Admin에서 토글할 수 있는 "초안 모드"가 존재하지 않습니다. 전문적인 테스트 패턴은 신규 Function의 대상을 특정 고객 태그로 제한하고, 기존 Script와 신규 Function을 병렬로 실행하면서 태그 지정 사용자에 대해 동일한 결과가 산출되는지 검증한 후 전환하는 것입니다.
단계 1: 테스트 사용자 태그 지정
Customers에서 내부 테스트 계정 2~3개에 FN-TESTER 태그를 추가합니다.
단계 2: 태그 존재 여부에 따라 Function 분기 처리
// At the top of your run function 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); // Fall through to existing Script } // New Function logic only runs for tagged users
단계 3: 입력 쿼리에 hasAnyTag 추가
cart { buyerIdentity { customer { hasAnyTag(tags: ["FN-TESTER"]) } } }
단계 4: 체크아웃에서 검증
태그가 지정된 사용자로 로그인하여 체크아웃을 진행하며 Function이 실행되는지 확인합니다. 태그가 없는 사용자로 로그인하여 기존 Script가 정상 작동하는지 확인합니다. 며칠 동안 양쪽의 일치 여부가 확인되면 태그 검사 로직을 제거하여 모든 사용자에게 Function을 제공합니다.
단계 5: 기존 Script 게시 취소
Apps → Script Editor → [대상 Script] → Unpublish로 이동합니다. 게시를 취소하면 이제 Function이 유일한 로직 소스가 됩니다.
확장 가능한 배포 워크플로
개발자의 개별 노트북에서 직접 배포하는 방식을 계속 사용하지 마십시오. 한두 개의 Script를 마이그레이션한 후에는 실제 CI 파이프라인을 구축해야 합니다.
최소 기능의 배포 워크플로 구성
# .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: '20' } - uses: dtolnay/rust-toolchain@stable with: { targets: wasm32-wasip1 } - run: npm install -g @shopify/cli@latest - run: shopify app deploy --force env: SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}
Partner 대시보드의 Settings → Tokens에서 파트너 토큰을 생성하십시오. 이제 main 브랜치로 병합될 때마다 Functions가 자동으로 배포됩니다. 더 이상 슬랙에서 배포 여부를 물어볼 필요가 없습니다.
버전 관리 및 롤백
shopify app deploy는 버전이 지정된 스냅샷을 생성합니다. 롤백이 필요한 경우:
shopify app versions list shopify app release --version <previous-version-id
과거 코드를 기억해 직접 붙여넣어야 했던 Scripts의 롤백 방식과 비교하면 완전히 달라진 시스템입니다.

대부분의 팀이 범하는 실수
지난 한 해 동안 여러 Plus 고객의 마이그레이션을 지원하면서 자주 발견된 5가지 실수 유형입니다.
1. Functions를 Scripts와 1:1 대응으로 여기는 것. 그렇지 않습니다. 단 하나의 Function이 더 깔끔한 분기 처리를 통해 3개의 기존 Script를 대체할 수 있습니다. 작성 전에 시스템 차원에서 Scripts를 감사하십시오.
2. 읽기 권한 범위(scope) 누락. 많은 Functions가 read_customers, read_orders, 또는 write_discounts 권한을 필요로 합니다. shopify.app.toml의 scopes에 추가하고 앱을 다시 인증하십시오. 그렇지 않으면 입력 쿼리가 null을 반환합니다.
3. 일치 테스트 없이 전 고객 대상으로 즉시 배포하는 것. 코드가 완벽해 보이더라도 예외적인 경우(빈 장바구니, 기프트 카드, 스토어 크레딧, B2B 초안 등) 문제가 드러날 수 있습니다. 태그 기반 출시 프로세스를 적용하면 이틀이 더 소요되지만 장애 발생을 사전에 막을 수 있습니다.
4. Customizations Report를 건너뛰는 것. 현재 작동 중인 로직의 목록을 확보할 수 있는 가장 확실한 도구입니다. 기억에 의존하지 말고 해당 보고서를 기준으로 마이그레이션을 시작하십시오.
5. 컬렉션 ID 및 고객 태그 하드코딩. 마케터나 운영자가 변경할 수 있는 변수가 필요한 경우 메타필드를 활용한 Function 구성을 구성하십시오. CLI에서 메타필드 기반의 설정을 스캐폴딩할 수 있습니다. 자세한 내용은 Shopify 가이드를 참조하십시오.
남은 75일간의 마이그레이션 체크리스트
6월 30일까지 무리 없이 마칠 수 있는 주차별 실행 계획안입니다.
주차 | 실행 과제 |
|---|---|
1주 차 (이번 주) | Customizations Report를 확인합니다. 모든 Script 목록을 작성하고 직접 구축할지, 공개 앱을 사용할지, 로직을 제거할지 결정합니다. |
2~3주 차 | 로컬 개발 환경을 설정합니다. 첫 번째 Function을 스캐폴딩하고 가장 간단한 Script(대개 결제 수단 숨기기 규칙)부터 마이그레이션을 시작합니다. |
4~6주 차 | 할인 관련 Scripts를 마이그레이션합니다. Discounts API는 지원하는 기능이 광범위하므로 시간이 가장 오래 걸립니다. 태그 기반 테스트를 철저히 병행합니다. |
7~8주 차 | 배송 관련 Scripts를 마이그레이션합니다. Admin에서 Delivery Customizations를 활성화합니다. |
9~10주 차 | CI 파이프라인을 구축합니다. 모든 배포 작업을 개별 노트북 환경에서 제거하고 시스템화합니다. |
11주 차 (6월 중순) | 최종 양방향 일치 검증을 마칩니다. 기존의 모든 Scripts 게시를 취소합니다. 약 2주간 Functions로만 스토어를 테스트 운영해 봅니다. |
6월 30일 | 기존 시스템의 종료일입니다. 선제적으로 처리를 마쳤으므로 아무 장애 없이 일상이 유지됩니다. |
이번 주에 시작하면 여유 일정을 확보할 수 있습니다. 6월에 시작하면 여유 일정이 없습니다.

자주 묻는 질문 (FAQ)
Functions를 사용하려면 Shopify Plus 요금제가 반드시 필요한가요?
독점 Function(Custom Functions)은 Shopify Plus가 필요하지만, 공개 앱 형태로 제공되는 Functions는 모든 요금제에서 동작합니다. Plus를 이용 중이 아닌 경우 두 가지 방법이 있습니다. 앱스토어에서 해당 기능을 구현한 공개 앱을 찾아 설치하거나, 직접 맞춤형 Functions를 개발하기 위해 Plus로 업그레이드하는 것입니다. 기존에 Scripts를 쓰고 있던 규모의 고객들은 대개 이미 Plus를 이용 중이므로 실질적인 차이는 거의 없습니다.
TypeScript로 Functions를 작성할 수 있나요?
네, TypeScript는 기본적으로 완전 지원되며 CLI에서 스캐폴딩을 제공합니다. shopify app generate extension 실행 시 "JavaScript"를 선택하면 생성되는 프로젝트 폴더에 import("../generated/api") 기반의 타입 정의가 포함됩니다. 필요한 경우 파일 확장자를 .ts로 변경하고 tsconfig.json 파일을 추가하십시오. 최종적으로 빌드되는 WASM 결과물은 개발 언어에 관계없이 동일합니다.
Functions의 속도는 기존 Scripts 대비 얼마나 빠른가요?
Functions는 일반적으로 5ms 미만으로 실행되며, 이는 기존 Ruby Scripts보다 대단히 빠른 속도입니다. WebAssembly로 컴파일되어 고도로 최적화된 런타임에서 작동하므로, Shopify는 엄격한 5ms 제한 정책을 적용하고 있습니다. 코드 실행에 5ms가 초과되면 해당 연산은 무시되며 결과가 반환되지 않습니다. 실제로 잘 작성된 Function은 약 1~2ms 수준에서 처리됩니다. 성능 한계치가 Scripts보다 압도적으로 높습니다.
Function에서 외부 API를 호출할 수 있나요?
아니요, Functions는 외부 네트워크 요청을 보낼 수 없습니다. 입력된 장바구니 데이터를 바탕으로 연산 후 작업을 출력하는 구조의 순수 함수적 형태로 작동합니다. 만약 CRM 조회 결과나 실시간 재고 정보 등의 외부 데이터가 필요하다면, 사전에 해당 데이터를 메타필드에 기록해 두거나 다른 기술 규격(App Proxy, 웹훅, 백엔드 조회가 결합된 Cart Transform 등)을 결합해야 합니다. 이는 기존 Scripts 로직을 마이그레이션할 때 설계 구조의 수정이 필요한 가장 대표적인 원인입니다.
Cart Transform과 Discounts의 차이는 무엇인가요?
Discounts는 가격의 할인 규칙을 결정하며, Cart Transform은 장바구니에 담긴 상품 구성 자체를 변경합니다. 10% 할인, 무료 배송, 1+1 행사 등은 Discounts API를 사용합니다. 두 제품을 하나의 단일 라인 번들로 결합하거나, 단일 에디션을 여러 개별 품목으로 쪼개는 행위 등은 Cart Transform API 영역에 속합니다. 과거 Scripts는 이 두 목적을 혼재해 사용하는 경우가 잦았는데, 마이그레이션 시에는 이를 각각 전용 Function으로 엄격히 구분하여 설계하십시오.
개발 스토어가 없어도 로컬에서 Function 테스트가 가능한가요?
cargo test(Rust) 또는 npm test(JS)를 활용해 독립적인 단위 테스트(Unit Test)를 수행할 수 있지만, 통합 테스트(Integration Test)에는 반드시 개발용 스토어가 수반되어야 합니다. CLI 명령어인 shopify app function run을 통해 준비된 샘플 입력 데이터 파일을 모의 대조하는 작업은 상시 가능하나, 최종 체크아웃 시의 엔드투엔드 동향 검증을 위해서는 실제 제품 카트가 구성된 개발 스토어가 필요합니다.
같은 유형의 Function을 여러 개 동시에 적용해 둘 수 있나요?
네, 동일한 작동 대상(Target)에 여러 개의 Function을 병렬 배치할 수 있으며 일정한 우선순위 및 규칙에 따라 순차 실행됩니다. 할인의 경우 Shopify의 할인 조합(stacking) 규칙을 따릅니다. 배송이나 결제 맞춤화의 경우 각각의 Function이 반환하는 변경 사항이 순차적으로 다음 단계의 입력에 연쇄 반영되는 형태입니다. 구조적 단순성을 위해서는 가급적 한 목표당 하나의 Function 통합을 권장합니다.
Function 배포 후 기존 Script는 어떻게 되나요?
Apps → Script Editor에서 기존 Script를 비활성화하기 전까지는 두 시스템이 병렬로 실행됩니다. 이는 마이그레이션 기간 동안의 비교 테스트를 위한 의도된 설계입니다. 안정적인 동작이 최종 확인되면 수동으로 이전 Script의 게시를 중단하십시오. 참고로 2026년 6월 30일이 경과하면 게시 여부와 무관하게 모든 Scripts 작동은 전면 차단됩니다.
마이그레이션 과정에서 검색 엔진 최적화(SEO)나 테마 구조에 변화가 발생하나요?
아니요, Functions는 순수하게 서버 영역의 체크아웃 단계에서 구동되므로 사용자 웹 테마 영역이나 제품 상세 페이지 등에 일절 개입하지 않습니다. 체크아웃 화면상의 할인 정보 표기, 배송 목록 변경, 결제 수단 필터링 등의 데이터 연산 작업만 수행할 뿐이며, 기존 사이트 및 SEO 환경은 완전히 원본 보존됩니다.
기존에 개별 속성(Custom properties)을 포함해 사용하던 Input.line_items 기반 Script는 어떻게 마이그레이션하나요?
장바구니 행의 attribute 필드를 GraphQL 인프라를 통해 요청하여 동일하게 해당 값을 확보할 수 있습니다. 데이터 수집 대상인 lines 하위에 attribute(key: "your-key") { value } 명세를 포함하십시오. Ruby 메서드 방식의 처리 형태가 GraphQL 방식으로 바뀔 뿐, 데이터에 직접 접근하는 논리는 완전히 일치합니다.
주문 분석 추적 태그(Order tag)를 임의로 심는 동작도 Function을 통해 설정할 수 있나요?
아니요, Functions는 자체적으로 특정 DB에 데이터를 기입하거나 외부 웹훅을 연동하여 트리거를 발생시킬 수 없습니다. 현재 카트 영역의 정보 제어 연산만 담당합니다. 주문 생성 이후 발생해야 할 자동 태그 생성 및 후속 마케팅 활동은 Shopify Flow를 구성하여 주문 완료(Order Created) 시점의 이벤트와 매핑해 연동하는 구조를 사용하십시오. 많은 비즈니스가 금액 감액을 담당하는 Function과 내부 정리를 담당하는 Flow를 결합해 복합 워크플로를 구축해 활용하고 있습니다.
직접 코딩을 하는 방법 외에 미리 패키징된 기성 솔루션을 이용하는 대안이 있나요?
네, Shopify App Store에 Functions를 상용화해 편의성을 제공하고 있는 외부 공개 앱들이 다수 존재합니다. "discount function", "delivery customization", "payment customization" 등의 검색 키워드를 입력해 찾아보실 수 있습니다. 대중적인 동작(구매량에 따른 볼륨 디스카운트, 고객 등급별 특정 결제 수단 차단, 일정 액수 이상 무료 배송 보장 등)의 범위 내라면, 직접 개발에 자원을 소모하는 것 대비 이미 배포된 기성 앱을 활용하는 편이 소요 기간 및 개발 원가를 크게 줄여주는 현명한 선택이 될 수 있습니다. 완전히 차별화된 고유 업무 시나리오 구현이 필요할 때만 인하우스 빌드를 결정하십시오.
기한인 6월 30일을 넘기게 되면 어떻게 처리되나요?
모든 Scripts 작동은 즉시 불통 상태가 되며, 별도의 하위 호환 구조나 임시 유예 조치는 제공되지 않습니다. 각 Script가 기여하고 있던 가치 사슬들(특정 할인 적용, 위험 배송 수단 마스킹 처리, 특정 결제 수단 한정 노출 등)은 즉시 스토어 기본 설정값으로 환원됩니다. 7월 1일 자정(UTC 기준)을 기해 모든 통제력이 기본 설정값으로 리셋됩니다. 해당 업무 처리가 필수 요소인 경우 안전성을 고려하여 기한보다 훨씬 전에 배포를 끝마치는 로드맵을 수립하십시오. 대부분의 프로젝트는 정합성 테스트 등으로 인해 설계 당시보다 더 많은 일수가 최종 소요되는 경향이 있습니다.
현재 설치된 Script Editor 앱을 완전히 제거해도 괜찮을까요?
2026년 6월 30일 일정이 지난 뒤 삭제 처리를 완료하시면 되며, 별도 처리가 없어도 향후 Shopify가 자체 비활성화를 진행할 예정입니다. 더 이상 실코드가 작동하지 않는 시점부터는 에디터 자체가 의미를 상실하기 때문입니다. 마이그레이션 검증 및 실사용 배포가 안전하게 완료된 상황이라면, 지금 바로 모든 작업을 정지하고 앱을 수동 영구 삭제하셔도 Functions 구동에는 영향이 없습니다.
이번 주에 당장 시작해야 할 일
단지 정보 확인 수준으로 이 글을 넘기지 마십시오. 다가오는 7일 동안 아래의 네 가지 행동을 실행하십시오.
1. Customizations Report를 즉시 내려받으십시오. Settings → Checkout → Customizations Report 위치로 이동해 내역을 추출하십시오. 이것이 구현해야 할 백로그 목록입니다.
2. 개발 공간 인프라를 구축하십시오. Node 18+, Shopify CLI 및 필요할 경우 Rust 환경을 구축하십시오. 터미널 창에 shopify version을 입력해 정상 구동을 확인하십시오. 30분이면 충분합니다.
3. 초소형 Function 하나를 직접 배포해 보십시오. 리스크가 거의 없는 단순 결제 수단 비활성화 제어 등을 선정해 마이그레이션 플로우 전체를 한 바퀴 돌려봅니다. 이를 통해 빌드 툴체인의 실제 실행 과정을 명확히 숙지할 수 있습니다.
4. 앞으로의 8주간 캘린더에 고정 일정을 확보하십시오. 마이그레이션 과업은 일상 스프린트의 남는 여유 시간에 간헐적으로 진행할 수 없습니다. 매주 화요일 및 목요일 오후 등 정기 세션을 할당하고 우선순위를 높여 진행하십시오.
그동안 무사히 마이그레이션을 마친 기업들의 공통적인 행동 데이터가 있습니다. '2주간의 조사 및 준비 단계, 3주간의 코딩 수행 및 실배포 과정, 마지막 1주간의 다각도 일치화 조정 과정'을 거쳐 전체 약 6주가 필요합니다. 현재 남은 일정은 10주 정도입니다. 아직은 시간의 여유가 있는 편이며, 이 예비 시간을 지연 처리에 쓰지 말고 밀도 높은 교차 코드 리뷰와 엄격한 품질 점검(QA)에 집중 투자하십시오.
체크아웃 영역의 정리가 마무리되면 대다수의 운영 팀이 해결해야 할 과제는 사후 주문 편집(주소지 오류 긴급 변경, 변심 교환, 배송 지연 품목 정리 등) 영역입니다. 대규모 오더 처리를 감당하고 있는 Plus 비즈니스 또는 독보적인 만족도를 지향하는 스토어라면, Shopify App Store에 상주 중인 Revize가 현재 개발하시려는 Functions 인프라의 훌륭한 파트너가 될 것입니다.
도움이 되는 리소스
관련 아티클
2026년 8월 업데이트 기준. Revize는 고객 스스로 주문 완료 후 직접 정보를 정정 및 수정(배송지 수정, 상품 크기/색상 교환, 주문 취소, 스토어 환불/크레딧 회수 등)할 수 있도록 전담하는 독립 포털 솔루션입니다. 고객 지원 채널의 혼잡을 사전에 차단하고 스스로 문제를 해결하도록 안내하는 Shopify 주문 수정 자동화 방안을 확인하시거나, Shopify App Store에서 Revize를 직접 검색하십시오.



