Add configuration to your delivery options function
To create a user interface that merchants can use to configure a delivery customization function, use metafields. Metafields provide flexibility to Shopify Functions by storing settings that merchants can manage directly from their Shopify admin.
Anchor to What you'll learnWhat you'll learn
In this tutorial, you'll learn how to do the following tasks:
- Define what configuration settings will be surfaced to merchants.
- Read and use the merchant-defined values in your function.
In this tutorial, you'll use a metafield namespace that's accessible to any app so that the metafield namespace can be populated using the GraphiQL app. To make your function ready for production, you should update the metafield namespace to use a reserved prefix so that other apps can't use your metafield. You'll make this update in the next tutorial.
Anchor to RequirementsRequirements
- You've completed the Getting started with building delivery customizations tutorial.
Anchor to Step 1: Configure the functionStep 1: Configure the function
To make your function reusable, you can replace hardcoded values in your function with metafield values. You can update your input query to request a metafield value on the created delivery customization, which is the function owner for this function API. You can then use that value in your function logic.
-
Navigate to your function in
extensions/delivery-customization
.Terminal
cd extensions/delivery-customization -
Replace the code in the
src/run.graphql
file with the following code.This update to the input query adds a metafield from the
deliveryCustomization
object, which is the function owner.The query differs slightly in Rust and JavaScript due to code generation requirements.
run.graphql
src/run.graphql
query Input {cart {deliveryGroups {deliveryAddress {provinceCode}deliveryOptions {handletitle}}}deliveryCustomization {metafield(namespace: "delivery-customization", key: "function-configuration") {jsonValue}}}query RunInput {cart {deliveryGroups {deliveryAddress {provinceCode}deliveryOptions {handletitle}}}deliveryCustomization {metafield(namespace: "delivery-customization", key: "function-configuration") {jsonValue}}}query Input { cart { deliveryGroups { deliveryAddress { provinceCode } deliveryOptions { handle title } } } deliveryCustomization { metafield(namespace: "delivery-customization", key: "function-configuration") { jsonValue } } }
query RunInput { cart { deliveryGroups { deliveryAddress { provinceCode } deliveryOptions { handle title } } } deliveryCustomization { metafield(namespace: "delivery-customization", key: "function-configuration") { jsonValue } } }
-
If you're using JavaScript, then run the following command to regenerate types based on your input query:
Terminal
shopify app function typegen -
If you're using Rust replace the
src/main.rs
file with the following code that will convert the metafield into a data structure in the Rust program.Rust
src/main.rsuse shopify_function::prelude::*;use std::process;pub mod run;mod schema {pub mod run {}}fn main() {eprintln!("Please invoke a named export.");process::exit(1);} -
Replace the
src/run.rs
orsrc/run.js
file with the following code.This update includes parsing the JSON metafield value, and using values from that JSON in the function logic instead of hardcoded values.
This change is automatically reflected as long as you're running
dev
.File
src/run.rs
use crate::schema;use shopify_function::prelude::*;use shopify_function::Result;pub struct Configuration {state_province_code: String,message: String,}fn run(input: schema::run::Input) -> Result<schema::FunctionRunResult> {let no_changes = schema::FunctionRunResult { operations: vec![] };let config: &Configuration = match input.delivery_customization().metafield() {Some(metafield) => metafield.json_value(),None => return Ok(no_changes),};let to_rename = input.cart().delivery_groups().iter().filter(|group| {if let Some(address) = group.delivery_address() {address.province_code() == Some(&config.state_province_code)} else {false}}).flat_map(|group| group.delivery_options()).map(|option| schema::RenameOperation {delivery_option_handle: option.handle().to_string(),title: match option.title() {Some(title) => format!("{} - {}", title, config.message),None => config.message.clone(),},}).map(schema::Operation::DeliveryOptionRename).collect();Ok(schema::FunctionRunResult {operations: to_rename,})}// @ts-check/*** @typedef {import("../generated/api").RunInput} RunInput* @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult* @typedef {import("../generated/api").Operation} Operation*//*** @type {FunctionRunResult}*/const NO_CHANGES = {operations: [],};/*** @param {RunInput} input* @returns {FunctionRunResult}*/export function run(input) {// Define a type for your configuration, and parse it from the metafield/*** @type {{* stateProvinceCode: string* message: number* }}*/const configuration = input?.deliveryCustomization?.metafield?.jsonValue ?? {};if (!configuration.stateProvinceCode || !configuration.message) {return NO_CHANGES;}let toRename = input.cart.deliveryGroups.filter(group => group.deliveryAddress?.provinceCode &&// Use the configured province code instead of a hardcoded valuegroup.deliveryAddress.provinceCode == configuration.stateProvinceCode).flatMap(group => group.deliveryOptions).map(option => /** @type {Operation} */({deliveryOptionRename: {deliveryOptionHandle: option.handle,// Use the configured message instead of a hardcoded valuetitle: option.title ? `${option.title} - ${configuration.message}` : configuration.message}}));return {operations: toRename};};use crate::schema; use shopify_function::prelude::*; use shopify_function::Result; #[derive(Deserialize, Default, PartialEq)] #[shopify_function(rename_all = "camelCase")] pub struct Configuration { state_province_code: String, message: String, } #[shopify_function] fn run(input: schema::run::Input) -> Result<schema::FunctionRunResult> { let no_changes = schema::FunctionRunResult { operations: vec![] }; let config: &Configuration = match input.delivery_customization().metafield() { Some(metafield) => metafield.json_value(), None => return Ok(no_changes), }; let to_rename = input .cart() .delivery_groups() .iter() .filter(|group| { if let Some(address) = group.delivery_address() { address.province_code() == Some(&config.state_province_code) } else { false } }) .flat_map(|group| group.delivery_options()) .map(|option| schema::RenameOperation { delivery_option_handle: option.handle().to_string(), title: match option.title() { Some(title) => format!("{} - {}", title, config.message), None => config.message.clone(), }, }) .map(schema::Operation::DeliveryOptionRename) .collect(); Ok(schema::FunctionRunResult { operations: to_rename, }) }
// @ts-check /** * @typedef {import("../generated/api").RunInput} RunInput * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult * @typedef {import("../generated/api").Operation} Operation */ /** * @type {FunctionRunResult} */ const NO_CHANGES = { operations: [], }; /** * @param {RunInput} input * @returns {FunctionRunResult} */ export function run(input) { // Define a type for your configuration, and parse it from the metafield /** * @type {{ * stateProvinceCode: string * message: number * }} */ const configuration = input?.deliveryCustomization?.metafield?.jsonValue ?? {}; if (!configuration.stateProvinceCode || !configuration.message) { return NO_CHANGES; } let toRename = input.cart.deliveryGroups .filter(group => group.deliveryAddress?.provinceCode && // Use the configured province code instead of a hardcoded value group.deliveryAddress.provinceCode == configuration.stateProvinceCode) .flatMap(group => group.deliveryOptions) .map(option => /** @type {Operation} */({ deliveryOptionRename: { deliveryOptionHandle: option.handle, // Use the configured message instead of a hardcoded value title: option.title ? `${option.title} - ${configuration.message}` : configuration.message } })); return { operations: toRename }; };
Anchor to Step 2: Populate the delivery customization configuration metafieldStep 2: Populate the delivery customization configuration metafield
To populate the configuration metafield, you'll first use the deliveryCustomizations
query to confirm the delivery customization ID, and then use the metafieldsSet
mutation to populate the same metafield that you specified in the input query.
-
Open the Shopify GraphiQL app on your development store.
-
In the GraphiQL app, in the API Version field, select the 2025-07 version.
-
Execute the following query, and make note of the
id
value of the delivery customization that you created in the previous tutorial. For more information about global IDs, refer to Global IDs in Shopify APIs.delivery-customization-query.graphql
query {deliveryCustomizations(first: 100) {edges {node {id}}}} -
Execute the following mutation and replace
YOUR_CUSTOMIZATION_ID_HERE
with the full global ID of your delivery customization.The value of the metafield specifies that the function should add a message for the
NC
state/province code. You can adjust this to the state/province of your choice.metafield-set-mutation.graphql
mutation {metafieldsSet(metafields: [{ownerId: "YOUR_CUSTOMIZATION_ID_HERE"namespace: "delivery-customization"key: "function-configuration"value: "{ \"stateProvinceCode\": \"NC\", \"message\": \"May be delayed due to UFO attack\" }"type: "json"}]) {metafields {id}userErrors {message}}}You should receive a GraphQL response that includes the ID of the created metafield. If the response includes any messages under
userErrors
, then review the errors, check that your mutation andownerId
are correct, and try the request again.
Anchor to Step 3: Test the delivery customizationStep 3: Test the delivery customization
- Open your development store, build a cart, and proceed to checkout.
- Enter a delivery address that doesn't use the specified state/province code. You shouldn't see any additional messaging on the delivery options.
- Change your shipping address to use your chosen state/province code. Your delivery options should now have the additional messaging.

Anchor to Next stepsNext steps
- Build a delivery customization user interface with App Bridge.
- Learn how to use variables in your input query.