Skip to main content

Rust for Functions

You can write your functions in Rust. This guide describes the shopify_function Rust crate that Shopify provides to help developers build with Shopify Functions.


The shopify_function Rust crate performs type generation, reduces boilerplate code, and makes it easier to test various function inputs. It includes the following components:

ComponentDescription
typegenA macro to enable struct generation from the Function API, based on the provided GraphQL schema and input query.
shopify_functionAn attribute macro that marks the given function as the entrypoint for Shopify Functions, by:
  • Exporting a WebAssembly function with the specified name, which must match the target export in the function extension configuration.
  • Providing an efficient API to manage the function's input and output.
run_function_with_inputA utility for unit testing that enables you to add new tests based on a given JSON input string.

Anchor to Viewing the generated typesViewing the generated types

To preview the types generated by the shopify_function Rust crate, use the cargo doc command.

Terminal

cargo doc --open

You can also use the cargo-expand crate to view the generated source:

Terminal

cargo install cargo-expand
cargo expand --doc

To make development easier, install the rust-analyzer VSCode extension for:

  • Code completion
  • Go to definition
  • Real-time error checking
  • Type information on hover
Note

The generated .output.graphql files are used for output type generation purposes. You can add these files to your .gitignore file.


Anchor to Example implementationsExample implementations

Explore example implementations using the shopify_function Rust crate.


Shopify Functions compiled Wasm file must be under 256 kB. Here are a few tips to keep binary size small when using Rust:

# Change this line:
export WASM_PATH=target/wasm32-wasip1/release/your-function-name.wasm

RUSTFLAGS="-C strip=none" cargo build --target=wasm32-wasip1 --release \
&& wasm-snip --snip-rust-panicking-code $WASM_PATH \
| wasm-opt -O3 --enable-bulk-memory --strip-debug -o function.no-panic.wasm -
  • Use to_ascii_uppercase and to_ascii_lowercase when possible to avoid pulling in Unicode tables, unless needed.

  • Only query for data you need.

    Code generation happens for all types and fields included in the input queries (for example, run.graphql). Review and remove any unused parts of the queries.

  • Keep JSON metafields that require deserialization as small as possible.

    Code generated for deserialization increases the binary size. The smaller the metafield is, the less code needs to be generated.

  • Bring your own types and deserializers.

    Instead of using the generated structs from the shopify_function_macro crate, write the appropriate struct definitions and derive the deserializers using mini_serde.

    The structs from shopify_function_macro can be used as a starting point, see them with cargo expand.

    Alternative serializers are generally less efficient than serde, make sure to benchmark the instruction count when going down this path.

  • Updating the shopify_function crate in your function to version 1.0.0 and above as outlined below.


Anchor to Updating existing function to using shopify_function 1.0.0 and higherUpdating existing function to using shopify_function 1.0.0 and higher

Migrate your function to the latest shopify_function crate for potential speedups and smaller binary sizes. Follow these steps:

  1. In main.rs, add imports for shopify_function.

    use shopify_function::prelude::*;
  2. In main.rs, add type generation, right under your imports. Remove any references to the generate_types! macro.

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

    If your Function has multiple targets each with their own input query, add a nested module for each. For example:

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

    #[query("src/run.graphql")]
    pub mod run {}
    }
  3. In main.rs, ensure that you have a main function that returns an error indicating to invoke a named export:

    fn main() {
    eprintln!("Invoke a named import");
    std::process::exit(1);
    }
  4. If you have an input query to retrieve a JSON metafield value in your run.graphql file, for example:

    Rust input query

    src/run.graphql
    query Input {
    deliveryCustomization {
    metafield(namespace: "delivery-customization", key: "function-configuration") {
    jsonValue
    }
    }
    }

    You can deserialize the jsonValue directly into an object you define in your run.rs file and annotate with #[shopify_function(rename_all = "camelCase")] and #[derive(Deserialize)] as shown below:

    Rust

    src/run.rs
    #[derive(Deserialize)]
    #[shopify_function(rename_all = "camelCase")]
    pub struct DeliveryConfiguration {
    state_province_code: String,
    message: String,
    }

    Finally, use custom_scalar_overrides to link the jsonValue with its object definition in your main.rs file as shown below:

    Rust

    src/main.rs
    #[typegen("schema.graphql")]
    mod schema {
    #[query("src/run.graphql",
    custom_scalar_overrides = {
    "Input.deliveryCustomization.metafield.jsonValue" => super::run::DeliveryConfiguration,
    }
    )]
    pub mod run {}
    }
  5. Ensure your source file that has the function logic defined, includes the following imports.

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

    typically this is in run.rs or fetch.rs

  6. Throughout all of your source files, replace any references to #[shopify_function_target] with the #[shopify_function] macro, and change its return type. Typically, this is located in a file with a name equal to the target, e.g. run.rs.

    #[shopify_function]
    fn run(input: schema::run::Input) -> Result<schema::FunctionRunResult> {
  7. Update the types and fields utilized in the function to the new, auto-generated structs. For example:

    OldNew
    input::ResponseDataschema::run::Input
    input::InputDiscountNodeMetafieldschema::run::input::discount_node::Metafield
    input::InputDiscountNodeschema::run::input::DiscountNode
    output::FunctionRunResultschema::FunctionRunResult
    output::DiscountApplicationStrategy::FIRSTschema::DiscountApplicationStrategy::First

Anchor to Updating to Rust 1.84 and higherUpdating to Rust 1.84 and higher

Previously, we encouraged the use of cargo-wasi as a way to build and optimize your Rust functions. However, as of Rust version 1.84, the WebAssembly build target used by cargo-wasi was removed.

To migrate an existing Rust function to Rust version 1.84 or higher, complete the following steps:

  1. Update to the latest Shopify CLI version.

  2. Remove the deprecated wasm32-wasi build target using rustup target:

    Terminal

    rustup target remove wasm32-wasi
  3. Update your Rust version using rustup update:

    Terminal

    rustup update stable
  4. Install the new wasm32-wasip1 build target using rustup target:

    Terminal

    rustup target add wasm32-wasip1
  5. Update your build command and path in the [extensions.build] section of your shopify.extension.toml. Replace RUST-PACKAGE-NAME with the name from your Cargo.toml:

    shopify.extension.toml

    [extensions.build]
    command = "cargo build --target=wasm32-wasip1 --release"
    path = "target/wasm32-wasip1/release/[RUST-PACKAGE-NAME].wasm"

These changes are compatible with Rust 1.78 and higher.

Note

In addition to building your Rust function for WebAssembly, the cargo-wasi crate also optimized the size of your binary using the Binaryen toolchain. Shopify CLI will now optimize your module by default. You can configure this behavior via the wasm_opt configuration property.


Anchor to Migrating from JavaScriptMigrating from JavaScript

Migrating your JavaScript Shopify Function to Rust can significantly improve performance and help you stay within platform fuel limits. Rust compiles directly to WebAssembly, resulting in more efficient execution compared to JavaScript.

Anchor to JavaScript migration stepsJavaScript migration steps

  1. Generate a new function using Shopify CLI:

    Terminal

    shopify app generate extension
  2. When prompted:

    • Choose the same function type as your existing JavaScript function
    • Name it the same as your current function but append -rs (e.g., if your current function is product-discount, name it product-discount-rs)
    • Select Rust as the language
  3. Copy your existing GraphQL query, making these adjustments to support the Rust code generation:

    • Copy your run.graphql from your JavaScript function to the new Rust function's src directory
    • Rename the query from RunInput to Input
    • Add __typename to any fragments on interfaces or unions:

    src/run.graphql

    # Before (JavaScript):
    # query RunInput {
    # cart {
    # lines {
    # merchandise {
    # ... on ProductVariant {
    # id
    # }
    # }
    # }
    # }
    # }

    # After (Rust):
    query Input {
    cart {
    lines {
    merchandise {
    __typename
    ... on ProductVariant {
    id
    }
    }
    }
    }
    }
  4. Port your JavaScript logic to the generated src/run.rs file

Anchor to Reusing extension handlesReusing extension handles

By reusing the extension handle from your JavaScript function, you can seamlessly replace the existing function on the server. This means all existing instances of your function will automatically use the new Rust implementation without any changes required on the merchant's side.

Migration steps:

  1. Copy the existing handle from your JavaScript function's shopify.extension.toml:

    extensions/your-function/shopify.extension.toml

    # JavaScript function's configuration
    name = "your-function"
    handle = "your-existing-handle" # Copy this value
  2. Update your new Rust function's configuration with the copied handle:

    extensions/your-function-rs/shopify.extension.toml

    # Rust function's configuration
    name = "your-function-rs"
    handle = "your-existing-handle" # Paste the handle here
  3. Disable the JavaScript function by renaming its configuration file:

    Terminal

    mv extensions/your-function/shopify.extension.toml extensions/your-function/shopify.extension.disabled.toml
Warning

If you deploy both functions, they will both appear in the merchant admin, which may cause confusion. Always ensure you've disabled the JavaScript function before deploying the Rust version.

Anchor to Validating the migrationValidating the migration

Before deploying to production:

  1. Test the function locally:

    Terminal

    shopify app function run --input input.json
  2. Deploy to a development store and verify the function works as expected

  3. Confirm only one function appears in the merchant admin

  4. If everything works correctly, you can safely delete the JavaScript function directory



Was this page helpful?