Skip to main content

Use network access

You can use a Shopify Functions to create network requests and handle responses. In this tutorial, you'll use Cart and Checkout Validation Function API to query an external system for user-specific cart limits based on the email they enter at checkout.


In this tutorial, you'll learn how to do the following tasks:

  • Define a function that declares an HTTP request to an external system, where additional information is available.
  • Use the HTTP response to apply further logic to the function.


Anchor to Step 1: Create the validation functionStep 1: Create the validation function

To create your validation function, you can use Shopify CLI to generate a starter function, specify the inputs for your function using an input query, and implement your function logic using Javascript or Rust.

  1. Navigate to your app directory:

    Terminal

    cd <directory>
  2. Run the following command to create a new validation function:

    Terminal

    shopify app generate extension --template cart_checkout_validation --name validation-using-network-access
    Tip

    Shopify Functions support any language that compiles to WebAssembly (Wasm), such as Rust, AssemblyScript, or TinyGo. You specify the Wasm template option when you're using a language other than Rust and can conform to the Wasm API. Learn more about the Wasm API.

    1. Choose the language that you want to use. For this tutorial, you should select either Rust or JavaScript.

      Shopify defaults to Rust as the most performant and recommended language choice to stay within the platform limits. For more information, refer to language considerations.

      Terminal

      ? What would you like to work in?
      > (1) Rust
      (2) JavaScript
      (3) TypeScript
      (4) Wasm
  3. Configure the extension definition in shopify.extension.toml. Add [[targeting]] sections to set up the following targets:

    • cart.validations.generate.fetch: Declares a network HTTP request to an external system
    • cart.validations.generate.run: Apply logic based off the network HTTP response

    Add extension targets

    shopify.extension.toml

    api_version = "2025-04"

    [[extensions]]
    handle = "validation-using-network-access"
    name = "t:name"
    description = "t:description"
    type = "function"

    [[extensions.targeting]]
    # An extensibility identifier.
    target = "cart.validations.generate.fetch"
    # The local GraphQL file used for code generation.
    input_query = "src/cart_validations_generate_fetch.graphql"
    # The generated WASM export name for the given target.
    export = "cart_validations_generate_fetch"

    [[extensions.targeting]]
    # An extensibility identifier.
    target = "cart.validations.generate.run"
    # The local GraphQL file used for code generation.
    input_query = "src/cart_validations_generate_run.graphql"
    # The generated WASM export name for the given target.
    export = "cart_validations_generate_run"

    [extensions.build]
    command = "cargo build --target=wasm32-wasip1 --release"
    watch = [ "src/**/*.rs" ]
    path = "target/wasm32-wasip1/release/validation-using-network-access.wasm"
    api_version = "2024-04"

    [[extensions]]
    handle = "validation-using-network-access"
    name = "t:name"
    description = "t:description"
    type = "function"

    [[extensions.targeting]]
    # An extensibility identifier.
    target = "cart.validations.generate.fetch"
    # The local GraphQL file used for code generation.
    input_query = "src/cart_validations_generate_fetch.graphql"
    # The generated WASM export name for the given target.
    export = "cart-validations-generate-fetch"

    [[extensions.targeting]]
    # An extensibility identifier.
    target = "cart.validations.generate.run"
    # The local GraphQL file used for code generation.
    input_query = "src/cart_validations_generate_run.graphql"
    # The generated WASM export name for the given target.
    export = "cart-validations-generate-run"

    [extensions.build]
    command = ""
    path = "dist/function.wasm"
  4. Define entry points for the cart.validations.generate.fetch and cart.validations.generate.run targets:

    Entry points

    src/main.rs

    use shopify_function::prelude::*;
    use std::process;

    pub mod cart_validations_generate_fetch;
    pub mod cart_validations_generate_run;

    #[typegen("schema.graphql")]
    pub mod schema {
    #[query("src/cart_validations_generate_fetch.graphql")]
    pub mod cart_validations_generate_fetch {}

    #[query(
    "src/cart_validations_generate_run.graphql",
    custom_scalar_overrides = {
    "Input.fetchResult.jsonBody" => CartValidationsGenerateRunResult,
    }
    )]
    pub mod cart_validations_generate_run {}
    }

    fn main() {
    eprintln!("Please invoke a named export.");
    process::exit(1);
    }
    export * from './cart_validations_generate_fetch';
    export * from './cart_validations_generate_run';
  5. Update project dependencies and scripts. The Cargo.toml (Rust) and package.json (JavaScript) files contain metadata about your project. The files include project dependencies and scripts that let you run Shopify CLI commands using your package manager:

    Project dependencies and scripts

    Cargo.toml

    [package]
    name = "validation-using-network-access"
    version = "1.0.0"
    edition = "2021"

    [dependencies]
    shopify_function = "1.1.0"

    [profile.release]
    lto = true
    opt-level = 'z'
    strip = true
    {
    "name": "validation-using-network-access",
    "version": "0.0.1",
    "license": "UNLICENSED",
    "scripts": {
    "shopify": "npm exec -- shopify",
    "typegen": "npm exec -- shopify app function typegen",
    "build": "npm exec -- shopify app function build",
    "preview": "npm exec -- shopify app function run",
    "test": "vitest"
    },
    "codegen": {
    "schema": "schema.graphql",
    "documents": "src/*.graphql",
    "generates": {
    "./generated/api.ts": {
    "plugins": [
    "typescript",
    "typescript-operations"
    ]
    }
    },
    "config": {
    "omitOperationSuffix": true
    }
    },
    "devDependencies": {
    "vitest": "2.1.9"
    },
    "dependencies": {
    "@shopify/shopify_function": "2.0.0"
    }
    }

Anchor to Step 2: Retrieve the latest GraphQL schemaStep 2: Retrieve the latest GraphQL schema

To use network access for Shopify Functions, you need to retrieve additional fields in the Cart and Checkout Validation Function API schema that relate to network access.

Run one of the following Shopify CLI commands to retrieve the latest GraphQL schema:

Terminal

shopify app function schema --path ./extensions/validation-using-network-access

Anchor to Step 3: Create a network request using fetchStep 3: Create a network request using fetch

The cart.validations.generate.fetch target, is used to declare a network request to an external system. You can use this target to fetch data required by your validation logic.

  1. Create a file called cart_validations_generate_fetch.graphql and define the input for the function. The input query can request any information available in the Cart and Checkout Validation Function API:

    cart_validations_generate_fetch.graphql

    src/cart_validations_generate_fetch.graphql

    query Input {
    cart {
    buyerIdentity {
    email
    isAuthenticated
    }
    cost {
    totalAmount {
    amount
    currencyCode
    }
    }
    }
    }
    query CartValidationGenerateFetchInput {
    cart {
    buyerIdentity {
    email
    isAuthenticated
    }
    cost {
    totalAmount {
    amount
    currencyCode
    }
    }
    }
    }

    The following example shows the resulting input from the query:

    Example

    input.json

    {
    "cart": {
    "buyerIdentity": {
    "email": "user@example.com",
    "isAuthenticated": false
    },
    "cost": {
    "totalAmount": {
    "amount": "1234.0",
    "currencyCode": "CAD"
    }
    }
    }
    }
  2. Navigate to your extension directory:

    Terminal

    cd ./extensions/validation-using-network-access
  3. If you're using JavaScript, then run the following command to regenerate types based on your input query:

    Terminal

    shopify app function typegen
  4. Create a file called src/cart_validations_generate_fetch.rs (Rust) or src/cart_validations_generate_fetch.js (JavaScript) and add the following code to the file:

    Create a network request

    src/cart_validations_generate_fetch.rs

    use super::schema;
    use shopify_function::prelude::*;
    use shopify_function::Result;
    use std::collections::BTreeMap;

    #[shopify_function]
    fn cart_validations_generate_fetch(input: schema::cart_validations_generate_fetch::Input) -> Result<schema::CartValidationsGenerateFetchResult> {
    let mut request: Option<schema::HttpRequest> = None;

    if let Some(buyer_identity) = &input.cart().buyer_identity() {
    if buyer_identity.email().is_some() {
    let http_request = build_request(&input);
    request = Some(http_request);
    }
    }

    Ok(schema::CartValidationsGenerateFetchResult { request })
    }

    fn build_request(input: &schema::cart_validations_generate_fetch::Input) -> schema::HttpRequest {
    static SERVER_URL: &'static str = "https://server_url.com/api";

    let json_body = JsonValue::Object(BTreeMap::from([(
    "cart".to_string(),
    JsonValue::Object(BTreeMap::from([
    (
    "buyerIdentity".to_string(),
    JsonValue::Object(BTreeMap::from([
    (
    "email".to_string(),
    JsonValue::String(
    input
    .cart()
    .buyer_identity()
    .and_then(|bi| bi.email().map(|s| s.to_string()))
    .unwrap_or_default(),
    ),
    ),
    (
    "isAuthenticated".to_string(),
    JsonValue::Boolean(
    input
    .cart()
    .buyer_identity()
    .map(|bi| *bi.is_authenticated())
    .unwrap_or_default(),
    ),
    ),
    ])),
    ),
    (
    "cost".to_string(),
    JsonValue::Object(BTreeMap::from([(
    "totalAmount".to_string(),
    JsonValue::Object(BTreeMap::from([(
    "amount".to_string(),
    JsonValue::Number(input.cart().cost().total_amount().amount().as_f64()),
    )])),
    )])),
    ),
    ])),
    )]));

    schema::HttpRequest {
    method: schema::HttpRequestMethod::Post,
    url: SERVER_URL.to_string(),
    headers: [schema::HttpRequestHeader {
    name: "accept".to_string(),
    value: "application/json".to_string(),
    }]
    .to_vec(),
    body: None,
    json_body: Some(json_body),
    policy: schema::HttpRequestPolicy {
    read_timeout_ms: 2000,
    },
    }
    }
    import { HttpRequestMethod } from "../generated/api"

    export function cartValidationsGenerateFetch(input) {
    let request = null

    if (input.cart.buyerIdentity?.email) {
    request = buildRequest(input)
    }
    return { request }
    }

    function buildRequest(input) {
    const SERVER_URL = "https://server_url.com/api";

    const body = {
    cart: {
    buyerIdentity: {
    email: input.cart.buyerIdentity?.email,
    isAuthenticated: input.cart.buyerIdentity?.isAuthenticated
    },
    cost: {
    totalAmount: {
    amount: input.cart.cost.totalAmount.amount,
    }
    }
    }
    }

    return {
    method: HttpRequestMethod.Post,
    url: SERVER_URL,
    headers: [
    { name: "accept", value: "application/json" }
    ],
    jsonBody: body,
    policy: {
    readTimeoutMs: 2000
    }
    };
    }

    The following example shows the output of the Function:

    Example

    output.json

    {
    "request": {
    "method": "POST",
    "url": "https://example.com/api",
    "headers": [
    {
    "name": "accept",
    "value": "application/json"
    }
    ],
    "jsonBody": {
    "cart": {
    "buyerIdentity": {
    "email": "user@example.com",
    "isAuthenticated": false
    },
    "cost": {
    "totalAmount": {
    "amount": "1234.0"
    }
    }
    }
    },
    "policy": {
    "readTimeoutMs": 2000
    }
    }
    }

Anchor to Step 4: Handle the network requestStep 4: Handle the network request

The HTTP request is managed by Shopify, as set up by the cart.validations.generate.fetch target. The HTTP response will be provided to the next step. The example in this section shows how to handle a network request from a Shopify Function, and includes information about the following areas:

The example server incorporates the following business logic:

  • If the total cart amount exceeds 1,000, then the server must validate the following conditions:
    • The buyer is authenticated. If not, then the server must return a validation error.
    • The buyer provides an email that authorizes them to place an order. If not, then the server must return a validation error.
  • If none of these conditions are met, then the server shouldn't return validation errors.

Business logic example

const handle = (body) => {
let input = JSON.parse(body)

if (parseFloat(input.cart.cost.totalAmount.amount) > 1000.0) {
if (!input.cart.buyerIdentity.isAuthenticated) {
return json({
operations: [{
validationAdd: {
errors: [{
message: "There is an order maximum of 1,000 USD for non-authenticated buyers",
target: "cart"
}]
}
}]
});
}

if (!input.cart.buyerIdentity.email.includes('+allowed@')) {
return json({
operations: [{
validationAdd: {
errors: [{
message: "There is an order maximum of 1,000 USD for buyers without established order history",
target: "cart"
}]
}
}]
});
}
}

return json({ operations: [] });
};

Every request is accompanied by a verification header, x-shopify-request-jwt. This header contains a JSON Web Token (JWT) that has been signed using the secret client key of the app. This token includes specific claims that assist in validating that the request was sent from Shopify.

JWT example

routes/api.js

const authenticate = async (request) => {
const requestJwtHeader = request.headers.get("x-shopify-request-jwt");
const requestIdHeader = request.headers.get("x-shopify-request-id");

const secretKey = process.env.APP_SECRET;
// Include the headers explicitly specified in the HttpRequest of the fetch target.
const includedVerificationHeaders = process.env.JWT_HEADERS.split(",");
const shopId = parseInt(process.env.JWT_SHOP_ID);

// Validate the JWT signature and ensure it hasn't expired.
const decoded = jwt.verify(requestJwtHeader, secretKey);

// Validate the JWT claims. The following checks are optional, but they enhance the authenticity of the request.

// Validate the method
const method = request.method;
if (decoded.method !== method) {
throw new Error("JWT invalid method.");
}

// Validate the URL
const fullUrl = request.url;
const url_sha256 = await hashWithSHA256(fullUrl);
if (decoded.url_sha256 !== url_sha256) {
throw new Error("JWT invalid url.");
}

// Validate the headers
const headers = Array.from(request.headers);
const canonicalHeaders = headers
.filter(([k, v]) => includedVerificationHeaders.includes(k.toLowerCase()))
.map(([k, v]) => `${k.toLowerCase()}:${v}`)
.sort()
.join(',');
const headersSha256 = await hashWithSHA256(canonicalHeaders);
if (decoded.headers_sha256 !== headersSha256) {
throw new Error("JWT invalid headers.");
}

// Validate the body
const body = await request.text();
if (body) {
const body_sha256 = await hashWithSHA256(body);
if (decoded.body_sha256 !== body_sha256) {
throw new Error("JWT invalid body.");
}
}

// Validate the issuer Shop
if (decoded.iss !== shopId) {
throw new Error("JWT invalid issuer shop.");
}

// Validate the request ID. Each request ID is unique and can be used as a measure to prevent replay attacks.
if (decoded.x_shopify_request_id !== requestIdHeader) {
throw new Error("JWT invalid x_shopify_request_id.");
}

return body;
};

const hashWithSHA256 = async (input) => {
const encoder = new TextEncoder();
const data = encoder.encode(input);
const hashBuffer = await subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
};
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOjY4NTY4NDE2NTY3LCJleHAiOjE2OTMyMzczOTEsInhfc2hvcGlmeV9yZXF1ZXN0X2lkIjoiZWNhMjEyNDMtYzA1OC00OWZmLThiMzUtYmUxYTAwNjE2MDRkIiwibWV0aG9kIjoiUE9TVCIsInVybF9zaGEyNTYiOiIxYmI1NmMwZGI1NjA4NDExZGJjZGFhOTAyZWVjMjM4NzgxZjU4NjNmMjQxMDA3NGQ4MDRjYTg5MWYwODFiN2RiIiwiaGVhZGVyc19zaGEyNTYiOiIxZjEyM2Q3YTI0MzFmYWVhMmEwMzljM2VmMGZhZDRmYmQxYzRkNzVmYWVlOWY3ZTQ3OTE4OTJiOGVmZjNmNmVmIiwiYm9keV9zaGEyNTYiOiI5Yjk3NzZkODY2ZWU3NTI2ZmNlOTJiYzlmZjI4OGNjYTIxNjg1MGZmNGE0ZmNhMGJlMzc0ZTMzYjAyNzBiMjI3In0.lce8eWBd-EnxZ6fwvasWGODR0S8nKEGv5Qb2s1teNyE
// Header
{
"alg": "HS256"
}

// Payload
{
"iss": 68568416567,
"exp": 1693237391,
"x_shopify_request_id": "eca21243-c058-49ff-8b35-be1a0061604d",
"method": "POST",
"url_sha256": "1bb56c0db5608411dbcdaa902eec238781f5863f2410074d804ca891f081b7db",
"headers_sha256": "1f123d7a2431faea2a039c3ef0fad4fbd1c4d75faee9f7e4791892b8eff3f6ef",
"body_sha256": "9b9776d866ee7526fce92bc9ff288cca216850ff4a4fca0be374e33b0270b227"
}

This guide offers a detailed, step-by-step process for creating a Remix app designed to handle network requests. Specifically, it focuses on handling POST requests that are accessible via the /api path.

  1. First, you need to create a new Remix app. You can do this by running the following command in your terminal:

Terminal

npx create-remix@latest --template remix-run/remix/templates/remix-javascript
  1. Next, navigate to your newly created Remix app directory and install the necessary dependencies. In this case, we will be installing jsonwebtoken:

Terminal

npm i jsonwebtoken
  1. Create the following files:
  • .env: This file houses the environment variables for your application.

  • routes/api.js: Outlines the action for the /api path to manage the incoming request. The code authenticates the request by verifying the JWT, and then executes the the associated business logic.

    Server files

    # Found in the Partner Dashboard
    APP_SECRET=0123456789abcdef0123456789abcdef

    # JWT headers for verification
    JWT_HEADERS=accept,content-type

    # JWT shop ID
    JWT_SHOP_ID=12345678
    import { json } from "@remix-run/node";
    import jwt from "jsonwebtoken";
    import { TextEncoder } from "util";
    import { subtle } from "crypto";

    export const action = async ({ request }) => {
    if (request.method.toUpperCase() !== 'POST') {
    return json({ error: 'Invalid request method. Only POST requests are allowed.' }, { status: 405 });
    }

    let body;

    try {
    body = await authenticate(request);
    } catch (err) {
    return json({ error: err.message }, { status: 401 });
    }

    return handle(body);
    };

    const authenticate = async (request) => {
    const requestJwtHeader = request.headers.get("x-shopify-request-jwt");
    const requestIdHeader = request.headers.get("x-shopify-request-id");

    const secretKey = process.env.APP_SECRET;
    // Include the headers explicitly specified in the HttpRequest of the fetch target.
    const includedVerificationHeaders = process.env.JWT_HEADERS.split(",");
    const shopId = parseInt(process.env.JWT_SHOP_ID);

    // Validate the JWT signature and ensure it hasn't expired.
    const decoded = jwt.verify(requestJwtHeader, secretKey);

    // Validate the JWT claims. The following checks are optional, but they enhance the authenticity of the request.

    // Validate the method
    const method = request.method;
    if (decoded.method !== method) {
    throw new Error("JWT invalid method.");
    }

    // Validate the URL
    const fullUrl = request.url;
    const url_sha256 = await hashWithSHA256(fullUrl);
    if (decoded.url_sha256 !== url_sha256) {
    throw new Error("JWT invalid url.");
    }

    // Validate the headers
    const headers = Array.from(request.headers);
    const canonicalHeaders = headers
    .filter(([k, v]) => includedVerificationHeaders.includes(k.toLowerCase()))
    .map(([k, v]) => `${k.toLowerCase()}:${v}`)
    .sort()
    .join(',');
    const headersSha256 = await hashWithSHA256(canonicalHeaders);
    if (decoded.headers_sha256 !== headersSha256) {
    throw new Error("JWT invalid headers.");
    }

    // Validate the body
    const body = await request.text();
    if (body) {
    const body_sha256 = await hashWithSHA256(body);
    if (decoded.body_sha256 !== body_sha256) {
    throw new Error("JWT invalid body.");
    }
    }

    // Validate the issuer Shop
    if (decoded.iss !== shopId) {
    throw new Error("JWT invalid issuer shop.");
    }

    // Validate the request ID. Each request ID is unique and can be used as a measure to prevent replay attacks.
    if (decoded.x_shopify_request_id !== requestIdHeader) {
    throw new Error("JWT invalid x_shopify_request_id.");
    }

    return body;
    };

    const hashWithSHA256 = async (input) => {
    const encoder = new TextEncoder();
    const data = encoder.encode(input);
    const hashBuffer = await subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
    };

    const handle = (body) => {
    let input = JSON.parse(body)

    if (parseFloat(input.cart.cost.totalAmount.amount) > 1000.0) {
    if (!input.cart.buyerIdentity.isAuthenticated) {
    return json({
    operations: [{
    validationAdd: {
    errors: [{
    message: "There is an order maximum of $1,000 for non-authenticated buyers",
    target: "cart"
    }]
    }
    }]
    });
    }

    if (!input.cart.buyerIdentity.email.includes('+allowed@')) {
    return json({
    operations: [{
    validationAdd: {
    errors: [{
    message: "There is an order maximum of $1,000 for buyers without established order history",
    target: "cart"
    }]
    }
    }]
    });
    }
    }

    return json({ operations: [] });
    };
  1. Execute the following command in your terminal to start the server:

    Terminal

    shopify app dev

The server used needs to be accessible on the public internet.


Anchor to Step 5: Create the validation logicStep 5: Create the validation logic

The cart.validations.generate.run target, exported as cart_validations_generate_run, is used to apply logic based off the network response from an external system.

In the following example, the function takes the server response and returns it to Checkout as it is already formatted for validation errors. In a more complex use case, you could apply additional local logic in your function.

  1. Create a file called cart_validations_generate_run.graphql and define the input for the function. The GraphQL query takes the server response data as input.

cart_validations_generate_run.graphql

src/cart_validations_generate_run.graphql

query CartValidationsGenerateRunInput {
fetchResult {
status
dateHeader: header(name: "date") {
value
}
cacheControlHeader: header(name: "cache-control") {
value
}
jsonBody
}
}
query Input {
fetchResult {
status
dateHeader: header(name: "date") {
value
}
cacheControlHeader: header(name: "cache-control") {
value
}
jsonBody
}
}

The following example shows the resulting input to the query:

Example

input.json

{
"fetchResult": {
"status": 200,
"dateHeader": {
"value": "Mon, 08 Jul 2024 19:24:21 GMT"
},
"cacheControlHeader": {
"value": "max-age=604800"
},
"jsonBody": {
"operations": [{
"validationAdd": {
"errors": [{
"message": "There is an order maximum of $1,000 for non-authenticated customers",
"target": "cart"
}]
}
}]
}
}
}
  1. If you're using JavaScript, then run the following command to regenerate types based on your input query:

    Terminal

    shopify app function typegen
  2. Create a file called src/cart_validations_generate_run.rs (Rust) or src/cart_validations_generate_run.js (JavaScript) and add the following code to the file:

Create the validation logic

src/cart_validations_generate_run.rs

use super::schema;
use shopify_function::prelude::*;
use shopify_function::Result;

#[shopify_function]
fn cart_validations_generate_run(input: schema::cart_validations_generate_run::Input) -> Result<schema::CartValidationsGenerateRunResult> {
let Some(fetch_result) = input.fetch_result() else {
// Optimization for when there are no requests.
// In this simple example, there are no fallbacks, but there is room to implement one if needed.
// See fetch.rs.
return Ok(schema::CartValidationsGenerateRunResult { operations: vec![] });
};

// When the server returns an unexpected response.
// Optionally: Apply a local fallback error message.
if *fetch_result.status() != 200 {
panic!("Server response unprocessable (status)");
}

let json_body = fetch_result.json_body().expect("Missing response body");

return Ok(json_body.clone());
}
export function cartValidationsGenerateRun(input) {
const fetchResult = input.fetchResult;

if (!fetchResult) {
return { operations: [] };
}

if (fetchResult.status !== 200) {
throw new Error("Server response unprocessable (status)");
}

if (!fetchResult.jsonBody) {
throw new Error("Server response unprocessable (body)");
}

return fetchResult.jsonBody;
}

The following example shows the output of the Function:

Example

output.json

{
"operations": [
{
"validationAdd": {
"errors": [
{
"message": "There is an order maximum of $1,000 for non-authenticated customers",
"target": "cart"
}
]
}
}
]
}

Anchor to Step 6: Preview the function on a development storeStep 6: Preview the function on a development store

To test your function, you need to make it available to your development store.

  1. If you're developing a function in a language other than JavaScript or TypeScript, ensure you have configured build.watch in your function extension configuration.
  1. Navigate back to your app root:

    Terminal

    cd ../..
  1. Use the Shopify CLI dev command to start app preview:

    Terminal

    shopify app dev

    You can keep the preview running as you work on your function. When you make changes to a watched file, Shopify CLI rebuilds your function and updates the function extension's drafts, so you can immediately test your changes.

  2. Follow the CLI prompts to preview your app, and install it on your development store.


Anchor to Step 7: Activate the validationStep 7: Activate the validation

  1. From the Shopify admin, go to Settings > Checkout.

  2. In the Checkout Rules section of the page click Add rule.

    A dialog opens and shows the validation-using-network-access function that you just deployed.

  3. To add a validation, click Add rule and select the validation.

  4. Click Activate to activate the validation.

  5. Click on Save.

  6. Optional: Control how checkout behaves when encountering runtime exceptions by clicking on the validation and selecting or deselecting Allow all customers to submit checkout.


Anchor to Step 8: Test the validationStep 8: Test the validation

  1. From your online store, without logging in, create a cart with more then $1,000 in merchandise.
  2. Proceed to Checkout and verify that a warning message displays.
  3. Verify that checkout progress is blocked. Clicking the Continue to shipping button shouldn't redirect the user.
  4. Using the Storefront API cartLinesAdd mutation, confirm that the mutation's userErrors field contains the function's error message, and that executing the mutation was unsuccessful.
  1. Open your terminal where shopify app dev is running, and review your function executions.

    When testing functions on development stores, the output of dev includes executions of your functions, any debug logging you have added to them, and a link to a local file with the full function execution details.

  2. In a new terminal window, use the Shopify CLI app function replay command to replay a function execution locally, and debug your function without the need to re-trigger the function execution on Shopify.

Terminal

shopify app function replay
  1. Select the function execution from the top of the list. Press q to quit when you are finished debugging.

Anchor to Step 9: View the network access logsStep 9: View the network access logs

To view network access logs, including request execution times and caching information, use Shopify CLI log streaming.



Was this page helpful?