--- title: Add configuration to your location rule function description: Use metafields to make your location rule function configurable. source_url: html: >- https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration md: >- https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md --- ExpandOn this page * [What you'll learn](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#what-youll-learn) * [Requirements](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#requirements) * [Step 1: Define the configuration metafield](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#step-1-define-the-configuration-metafield) * [Step 2: Configure the function](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#step-2-configure-the-function) * [Next steps](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#next-steps) # Add configuration to your location rule function You can use metafields to store configuration values for your location rule function. Metafields provide greater flexibility to use functions, and are a prerequisite to creating a merchant user interface for configuring functions. Beta Location rules is a new feature that's only available by request. Reach out to [Shopify Plus Support](https://help.shopify.com/en/support/plus) to know more about your eligibility and the requirements for the beta program. *** ## What you'll learn In this tutorial, you'll learn how to do the following tasks: * Add configuration to your location rule function using metafields. * Use a TOML declarative custom data definition to define configuration metafields. * Add metafields to your GraphQL input query. * Use metafield values in your function logic. *** ## Requirements * You've completed the [Getting started with building location rules](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/build-location-rule-function) tutorial. *** ## Step 1: Define the configuration metafield To make your function reusable, you can replace hardcoded values in your function with metafield values. Use TOML declarative custom data definitions to define a metafield that will store the preferred country configuration for your location rule function. 1. In your app's `shopify.app.toml` file, add the following metafield definition: ## shopify.app.toml ```toml [order_routing_location_rule.metafields.app.preferred-country] type = "json" name = "Preferred Country" description = "The preferred country for location routing" access.admin = "merchant_read_write" ``` This TOML configuration creates a metafield definition that: * Stores JSON data containing the preferred country configuration * Allows merchants to configure location routing preferences through the admin * Is automatically available across all stores where your app is installed * Uses the app-scoped namespace to ensure data ownership and security *** ## Step 2: Configure the function Now that you've defined the metafield structure with TOML, you can update your function to read from it. Update your [input query](https://shopify.dev/docs/apps/build/functions/input-queries/metafields-for-input-queries) to request a metafield value on the created location rule, which is the [function owner](https://shopify.dev/docs/apps/build/functions/input-queries/metafields-for-input-queries#how-it-works) for this function API. You can then use that value in your function logic. 1. Navigate to your function in `extensions/location-rule`. ## Terminal ```terminal cd extensions/location-rule ``` 2. Replace the code in the `src/run.graphql` file with the following code. This update to the input query adds a metafield from the `locationRule` object, which is the function owner. The query differs slightly in Rust and JavaScript due to code generation requirements. ## run.graphql ## src/run.graphql ##### Rust input query ```graphql query Input { locationRule { metafield(namespace: "$app", key: "preferred-country") { jsonValue }, } fulfillmentGroups { handle inventoryLocationHandles } locations { handle address { countryCode } } } ``` ##### JavaScript input query ```graphql query RunInput { locationRule { metafield(namespace: "$app", key: "preferred-country") { jsonValue }, } fulfillmentGroups { handle inventoryLocationHandles } locations { handle address { countryCode } } } ``` 3. If you're using JavaScript, then run the following command to regenerate types based on your input query: ## Terminal ```terminal shopify app function typegen ``` 4. If you're using Rust, replace the `src/main.rs` file with the following code that converts the metafield into a data structure in the Rust program. ## main.rs src/main.rs ```rust use shopify_function::prelude::*; pub mod run; #[typegen("schema.graphql")] pub mod schema { #[query("src/run.graphql", custom_scalar_overrides = { "Input.locationRule.metafield.jsonValue" => super::run::Configuration, } )] pub mod run {} } fn main() { log!("Please invoke a named export."); std::process::abort(); } ``` 5. Replace the `src/run.rs` or `src/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 ##### Rust ```rust use super::schema; use shopify_function::prelude::*; use shopify_function::Result; use crate::schema::Handle; // Create a structure that matches the JSON structure that you'll use for your configuration #[derive( Deserialize, Default, PartialEq)] #[shopify_function(rename_all = "camelCase")] pub struct Configuration { preferred_country_code: String, } #[shopify_function] fn run(input: schema::run::Input) -> Result { let no_changes = schema::FunctionRunResult { operations: vec![] }; // Get the configuration from the metafield on your function owner let config: Configuration = match input.location_rule().metafield() { Some(metafield) => metafield.json_value(), None => return Ok(no_changes), }; // Load the fulfillment groups and generate the rank operations for each one using // the configured preferred country code instead of a hardcoded value let operations = input .fulfillment_groups() .into_iter() .map(|fulfillment_group| schema::Operation { fulfillment_group_location_ranking_add: build_rank_operation(fulfillment_group, &input.locations(), config.preferred_country_code.clone()), }) .collect(); // Return the operations Ok(schema::CartFulfillmentGroupsLocationRankingsGenerateRunResult { operations }) } fn build_rank_operation( input: &schema::run::input::FulfillmentGroups, locations: &[schema::run::input::Locations], preferred_country_code: String, ) -> schema::FulfillmentGroupLocationRankingAddOperation { schema::FulfillmentGroupLocationRankingAddOperation { fulfillment_group_handle: input.handle().to_string(), rankings: prioritize_locations(input.inventory_location_handles().to_vec(), locations, preferred_country_code), } } fn prioritize_locations( handles: Vec, locations: &[schema::run::input::Locations], preferred_country_code: String, ) -> Vec { // Load the inventory locations for the fulfillment group handles .into_iter() .map(|location_handle| { let location = locations.iter().find(|&loc| *loc.handle() == location_handle); // Rank the location as 0 if the country code matches the preferred one, otherwise rank it as 1 schema::RankedLocation { location_handle, rank: match location { Some(location) => { if location.address().country_code() == Some(&preferred_country_code.to_string()) { 0 } else { 1 } } None => 1, }, } }) .collect() } ``` ##### JavaScript ```javascript // @ts-check /** * @typedef {import("../generated/api").RunInput} RunInput * @typedef {import("../generated/api").RunInput["fulfillmentGroups"][0]} FulfillmentGroup * @typedef {import("../generated/api").RunInput["locations"][0]} Location * @typedef {import("../generated/api").CartFulfillmentGroupsLocationRankingsGenerateRunResult} CartFulfillmentGroupsLocationRankingsGenerateRunResult * @typedef {import("../generated/api").Operation} Operation * @typedef {import("../generated/api").FulfillmentGroupLocationRankingAddOperation} FulfillmentGroupLocationRankingAddOperation * @typedef {import("../generated/api").RankedLocation} RankedLocation */ /** * @param {RunInput} input * @returns {CartFulfillmentGroupsLocationRankingsGenerateRunResult} */ export function run(input) { // Define a type for your configuration, and parse it from the metafield /** * @type {{ * preferredCountryCode: string * }} */ const configuration = input?.locationRule?.metafield?.jsonValue ?? {}; // Load the fulfillment groups and generate the rank operations for each one let operations = input.fulfillmentGroups .map(fulfillmentGroup => /** @type {Operation} */( { fulfillment_group_location_ranking_add: buildRankOperation(fulfillmentGroup, input.locations, configuration.preferredCountryCode) } )); // Return the operations return { operations: operations }; }; /** * @param {FulfillmentGroup} fulfillmentGroup * @param {Location[]} locations * @param {String} preferredCountryCode * @returns {FulfillmentGroupLocationRankingAddOperation} */ function buildRankOperation(fulfillmentGroup, locations, preferredCountryCode) { return { fulfillmentGroupHandle: fulfillmentGroup.handle, rankings: prioritizeLocations(fulfillmentGroup.inventoryLocationHandles, locations, preferredCountryCode), }; }; /** * @param {string[]} locationHandles * @param {Location[]} locations * @param {String} preferredCountryCode * @returns {RankedLocation[]} */ function prioritizeLocations(locationHandles, locations, preferredCountryCode) { // Load the inventory locations for the fulfillment group return locationHandles.map(locationHandle => { const location = locations.find((loc) => loc.handle == locationHandle); return { locationHandle, // Rank the location as 0 if the country code matches the preferred one, otherwise rank it as 1 rank: location?.address.countryCode === preferredCountryCode ? 0 : 1, } }); }; ``` *** ## Next steps * Build a [location rule user interface](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/build-ui) using [Admin UI extensions](https://shopify.dev/docs/api/admin-extensions). * Learn how to use [variables](https://shopify.dev/docs/apps/build/functions/input-queries/use-variables-input-queries) in your input query. *** * [What you'll learn](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#what-youll-learn) * [Requirements](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#requirements) * [Step 1: Define the configuration metafield](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#step-1-define-the-configuration-metafield) * [Step 2: Configure the function](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#step-2-configure-the-function) * [Next steps](https://shopify.dev/docs/apps/build/orders-fulfillment/order-routing-apps/location-rules/add-configuration.md#next-steps)