Talk Shop
Home
Learn More
About Us
Follow Us
Blog
Tools
Newsletter
Join Discord
Join

Community

  • Developers
  • Growth
  • Entrepreneurs
  • Support
  • Experts
  • Tools

Location

123 Mars, Crater City, Red Planet

(WiFi may be spotty)

Hours

Who has time for breaks? We're here 24/7!

Contact

hello@letstalkshop.com

Talk Shop
Talk Shop

Built for real builders. Not affiliated with Shopify Inc.

Home
Privacy
Terms
  1. Home
  2. >Blog
  3. >Shopify Development
  4. >Shopify Functions Metaobjects: Build Data-Driven Discount Logic (2026)
Shopify Development11 min read

Shopify Functions Metaobjects: Build Data-Driven Discount Logic (2026)

Shopify Functions can now read app-owned metaobjects. Learn the input query syntax, a tiered-discount example, and best practices for dynamic, data-driven logic.

Talk Shop

Talk Shop

Jun 19, 2026

Shopify Functions Metaobjects: Build Data-Driven Discount Logic (2026)

In this article

  • What changed in 2026: Functions can read metaobjects
  • A quick refresher: Functions and Metaobjects
  • Why combine Shopify Functions metaobjects instead of metafields
  • How metaobject access works inside a Function
  • Building a tiered discount that reads from a metaobject
  • Beyond discounts: validation and market-aware logic
  • Common Pitfalls
  • Best Practices for Shopify Functions Metaobjects
  • FAQ
  • Conclusion

For years, every business rule you wanted inside a Shopify Function had to be baked into the WebAssembly binary or smuggled in through a single JSON metafield. As of the 2026-04 API version, that constraint is gone: Shopify Functions metaobjects are now readable directly inside your input query. You can pull structured, merchant-editable data — pricing tiers, market rules, eligibility tables — into your discount and validation logic without redeploying a thing. This guide shows developers how the change works, the exact input query syntax, a concrete tiered-discount example, and the pitfalls worth knowing before you ship.

If you have never touched Functions before, start with our Shopify Functions API for beginners and the hands-on build your first custom discount walkthroughs. This piece assumes you already know the basics and want to combine two primitives into something more powerful.

What changed in 2026: Functions can read metaobjects

On April 1, 2026, Shopify shipped metaobject access inside Functions as part of the GraphQL API version 2026-04. Before this, a Function's input query could read the function owner's metafields and the cart, but it could not traverse into a metaobject entry. That meant any structured configuration — say, five discount tiers each with a threshold and a rate — had to be serialized into one metafield value and parsed by hand inside the binary.

Now you can query an app-owned metaobject entry as part of the input query, and Shopify resolves the fields you ask for and hands them to your run target as typed input. The shift is small in code but large in architecture: your Function becomes a thin rules engine, and the rules live in data the merchant can edit.

Why this matters for developers

Three things change in practice:

  • No redeploys for rule changes. Update a metaobject entry in the admin and the next checkout uses the new values. The binary never moves.
  • Structured beats stringly-typed. Instead of JSON.parse-ing a blob and hoping the shape holds, you get distinct, validated fields.
  • Merchants own the data. Operations staff can manage entries themselves, which is exactly what metaobjects were designed for.

If metaobjects are new to you, skim the Shopify metafields guide first — metaobjects are the next step up from a single metafield into a full structured record.

A quick refresher: Functions and Metaobjects

Two primitives are in play here, so a one-paragraph recap of each keeps everyone aligned before the code.

Shopify Functions are small WebAssembly modules that run inside Shopify's backend at well-defined extension points called targets — discounts, cart transforms, delivery customization, validation, and more. A Function has three parts: a configuration file (shopify.extension.toml), an input query (.graphql) that declares the data the Function needs, and the logic (.rs or .js) that receives that input and returns operations. Functions can be written in any language that compiles to Wasm, though Shopify recommends Rust for performance.

Metaobjects are structured, reusable records — like a lightweight content type. Each metaobject definition declares typed fields (single-line text, number, reference, and so on), and each entry is one instance with a unique handle. Unlike a metafield, a metaobject is not attached to a specific product or order; it stands on its own and can be referenced from anywhere.

The "app-owned" requirement

This is the single most important rule to internalize: only app-owned metaobject types are accessible to Functions. App-owned types use the reserved $app: prefix in their type (for example, $app:discount_tiers). A standard merchant-created metaobject type — one without that prefix — will not resolve inside a Function input query. We will come back to why this trips people up in the pitfalls section.

Why combine Shopify Functions metaobjects instead of metafields

Monitor comparing simple and nested dark data interfaces.

You can already feed configuration into a Function through a JSON metafield on the function owner, so why reach for metaobjects? The answer is about structure, scale, and who maintains the data.

ConcernJSON metafieldApp-owned metaobject
Data shapeOne opaque string you parse yourselfTyped fields resolved by Shopify
Editing UXRaw JSON in the adminForm fields per definition
Reuse across functionsCopy the blob each placeOne entry, referenced anywhere
ValidationManual, in your binaryEnforced by field types
RelationshipsFlatten or duplicateNative references between entries

A single discount with one threshold fits fine in a metafield. But the moment you have multiple related records — a tier table, per-market rules, a catalog of bundle definitions — metaobjects win because each record is a real entry with its own fields and handle, and the merchant edits it through a proper form instead of hand-writing JSON.

Good fits for the pattern

  • Market-aware discounts — one metaobject entry per market with its own rate and minimum.
  • Tiered or volume pricing — a table of { threshold, rate } rows.
  • Rule-based validation — allow/deny lists, quantity caps, or B2B eligibility flags.
  • Bundle definitions — component products and bundle pricing held as structured entries.

For deeper context on where custom app development is heading, see our overview of the 2026 Shopify custom app changes.

How metaobject access works inside a Function

The mechanics live entirely in the input query. Functions reach metaobjects through the Shop field exposed in the 2026-04 schema, and you target a specific entry by handle or ID.

Budgeting for complexity

Function input queries have a strict complexity budget of 30 points, and metaobject access is metered:

  • Each metaobject root costs 1 point.
  • Each field(key:) call costs 3 points.

So a query that reads one metaobject and pulls three fields costs 1 + (3 × 3) = 10 points — comfortably inside budget, but you can see how a wide entry or several metaobjects adds up fast. Ask only for the fields the logic uses.

The shape of the data

When you query a metaobject's fields, each field comes back as a key/value pair where value is a string. Number and boolean fields arrive as their string representation, so your logic parses them. Reference fields return the referenced resource's ID, which you can resolve in the same query if you need its attributes.

Building a tiered discount that reads from a metaobject

Tablet displaying cyan bar charts of tiered discounts on a dark background.

Let's make this concrete with a volume discount whose tiers live in an app-owned metaobject. The merchant edits the tiers in the admin; the Function reads them at checkout. We will use the Discount API target cart.lines.discounts.generate.run from the current Discount Function structure.

Step 1: Define the app-owned metaobject

Create a metaobject definition with the type $app:discount_tiers. Give it a JSON or structured field per row, or a single JSON field holding the tier array — for clarity here, assume one entry with three fields: tier_threshold, tier_rate, and message. In a real catalog you would model each tier as its own entry and reference them, but a single configuration entry keeps the example readable.

Step 2: Write the input query

The input query fetches the cart subtotal and reads the tier configuration from the metaobject by handle. Note the $app: type prefix and the field(key:) calls that drive complexity cost.

graphqlgraphql
query Input {
  cart {
    cost {
      subtotalAmount {
        amount
      }
    }
    lines {
      id
      cost {
        subtotalAmount {
          amount
        }
      }
    }
  }
  discount {
    discountClasses
  }
  shop {
    metaobject(handle: { type: "$app:discount_tiers", handle: "default" }) {
      threshold: field(key: "tier_threshold") {
        value
      }
      rate: field(key: "tier_rate") {
        value
      }
      label: field(key: "message") {
        value
      }
    }
  }
}

This costs roughly 10 complexity points for the metaobject portion (1 root + 3 fields × 3), well under the 30-point budget.

Step 3: Write the run logic

The run target receives the resolved input and returns discount operations. Here is the logic outline in JavaScript — read the metaobject values, parse them, and emit an orderDiscountsAdd operation when the cart clears the threshold.

javascriptjavascript
// src/cart_lines_discounts_generate_run.js
export function cartLinesDiscountsGenerateRun(input) {
  const config = input.shop?.metaobject;
  const subtotal = parseFloat(input.cart.cost.subtotalAmount.amount);

  // No config entry → no discount, fail safe.
  if (!config) {
    return { operations: [] };
  }

  const threshold = parseFloat(config.threshold?.value ?? "0");
  const rate = parseFloat(config.rate?.value ?? "0");
  const message = config.label?.value ?? "VOLUME DISCOUNT";

  if (subtotal < threshold || rate <= 0) {
    return { operations: [] };
  }

  return {
    operations: [
      {
        orderDiscountsAdd: {
          selectionStrategy: "FIRST",
          candidates: [
            {
              message,
              targets: [{ orderSubtotal: { excludedCartLineIds: [] } }],
              value: { percentage: { value: rate } },
            },
          ],
        },
      },
    ],
  };
}

The same logic in Rust follows the generated input types from your schema. The structure mirrors the JavaScript: read the metaobject fields off input.shop.metaobject, parse the strings, and return an operation.

rustrust
// src/main.rs (outline)
#[shopify_function_target(query_path = "src/input.graphql", schema_path = "schema.graphql")]
fn cart_lines_discounts_generate_run(input: input::ResponseData) -> Result<output::CartLinesDiscountsGenerateRunResult> {
    let subtotal: f64 = input.cart.cost.subtotal_amount.amount.parse()?;

    // Pull the app-owned metaobject config; bail out cleanly if absent.
    let config = match input.shop.metaobject {
        Some(m) => m,
        None => return Ok(no_discount()),
    };

    let threshold: f64 = field_value(&config.threshold).parse().unwrap_or(0.0);
    let rate: f64 = field_value(&config.rate).parse().unwrap_or(0.0);

    if subtotal < threshold || rate <= 0.0 {
        return Ok(no_discount());
    }

    // ...build OrderDiscountsAdd with a Percentage value of `rate`
    Ok(build_order_discount(rate, field_value(&config.label)))
}

Step 4: Deploy and let merchants edit

Run shopify app deploy, create the metaobject entry, and the merchant can now adjust tier_threshold and tier_rate in the admin. Each checkout reads the live values. To change behavior, edit the entry — no rebuild, no redeploy. For the full Discount API surface, see Shopify's Discount Function API reference.

Beyond discounts: validation and market-aware logic

Payment terminal and glowing grid displays in a dark environment.

The pattern generalizes well past discounts because metaobject access works across all function targets — discounts, cart transforms, delivery customization, fulfillment constraints, and validation.

Rule-based cart validation

A validation Function can read an app-owned metaobject holding rules — maximum quantity per SKU, restricted shipping regions, or B2B-only products — and return errors when the cart breaks them. The rules live in entries the merchant manages, so adding a new restricted product is a data edit, not a deploy.

Market-aware behavior

Model one metaobject entry per market, each carrying its own thresholds and rates, and reference the entry that matches the cart's context. Because references are first-class, you can keep a clean one-entry-per-market table and resolve the right one at runtime. This is the kind of dynamic, data-driven logic that was painful to express with a single flattened metafield.

Common Pitfalls

Even a clean pattern has sharp edges. These are the ones that cost the most debugging time.

Forgetting the $app: prefix

The number-one mistake: pointing your input query at a merchant-created metaobject type and getting nothing back. Only app-owned types (the `$app:` reserved prefix) resolve inside Functions. If your query returns null and you swear the entry exists, check the type string first. A standard discount_tiers type will silently fail where $app:discount_tiers works.

Blowing the complexity budget

With each field costing 3 points against a 30-point ceiling, a wide metaobject or several metaobjects in one query will overflow fast. Request only the fields the logic actually reads. If you need many fields, reconsider the data model rather than cramming everything into one query.

Treating values as typed

Every metaobject field value arrives as a string. Numbers, booleans, and dates are stringified. Parse defensively — parseFloat, unwrap_or, sensible defaults — and never assume a field is populated. A missing or malformed value should fail safe to "no discount," not panic.

Not failing safe

A Function that errors at checkout is worse than one that does nothing. Always handle the missing-metaobject case and bad input by returning empty operations. The example above bails out cleanly whenever config is absent or the rate is non-positive.

Best Practices for Shopify Functions Metaobjects

Highlighted cyan code in a dark editor interface.

A few habits keep these builds maintainable as they grow.

  • Model data deliberately. Use one entry per logical record (per tier, per market) and references between them, rather than one giant JSON field. You get the editing UX and validation for free.
  • Namespace with `$app:` from day one. Define types app-owned even during prototyping so you do not refactor later.
  • Keep queries lean. Pull only the fields the run logic consumes; the 30-point budget rewards restraint.
  • Validate at the edges. Parse strings into typed values once, with defaults, near the top of the run function.
  • Document the contract. Write down which metaobject fields the Function depends on so a merchant edit does not break logic silently. Pair this with strong logging in development.

For more on structured data fundamentals, our metafields walkthrough and the broader Shopify development category are good next stops. Shopify's own metaobjects documentation covers definitions and field types in depth.

FAQ

Do Shopify Functions metaobjects work with every function target? Yes. As of the 2026-04 API version, metaobject access is available across all function targets, including discounts, cart transforms, delivery customization, fulfillment constraints, and validation.

Can a Function read any metaobject in the store? No. Only app-owned metaobject types — those using the reserved $app: prefix — are accessible inside a Function. Standard merchant-created types are not readable from Functions.

How much complexity does reading a metaobject cost? Each metaobject root costs 1 point and each field(key:) call costs 3 points, against a total input query budget of 30 points. One metaobject with three fields costs 10 points.

What API version do I need? GraphQL API version 2026-04 or later, released April 1, 2026. Set your Function's schema to that version and regenerate types.

Should I still use metafields for Function config? For a single value or a simple flag, a metafield is fine. Reach for metaobjects when you have multiple related records, want a real editing UX for merchants, or need references between records.

Do I redeploy the Function when rules change? No — that is the whole point. Once the Function reads from a metaobject, merchants edit the entry in the admin and the next checkout uses the new values. The binary stays put.

Conclusion

Combining Shopify Functions metaobjects turns a static WebAssembly binary into a live rules engine: the Function stays simple while the data that drives it lives in structured, merchant-editable entries. The 2026-04 change — app-owned metaobject access inside input queries — is what makes market-aware discounts, tiered pricing, and rule-based validation practical without redeploys. Mind the $app: prefix, respect the 30-point complexity budget, parse values defensively, and always fail safe. Get those four right and you have a pattern that scales from one discount to an entire data-driven catalog.

Building something with Functions and Metaobjects? **Join the Talk Shop developer community and share your Functions + Metaobjects builds with other devs** — the edge cases are where the real learning happens. What is the first piece of logic you would move out of your binary and into a metaobject?

---

Sources:

  • Metaobject access in Shopify Functions — Shopify developer changelog
  • How to Migrate Shopify Scripts to Functions (2026 Edition) — revize.app
  • Mastering Metaobjects in Shopify: Solving Discount Function Dilemmas — entaice.com
Shopify DevelopmentApps & Integrations
Talk Shop

About Talk Shop

The Talk Shop team — insights from our community of Shopify developers, merchants, and experts.

Related Insights

Related

Postscript vs Attentive: 2026 SMS Platform Comparison for Shopify

Related

WhatsApp Marketing for Shopify: The 2026 Channel Playbook

New

Business Name Generator

Generate unique, brandable business names with AI. Check domain availability instantly.

Generate Names

Talk Shop Daily

Daily ecommerce news, teardowns, and tactics.

No spam. Unsubscribe anytime. · Learn more

Try our Business Name Generator

Join the Best Ecommerce Newsletter
for DTC Brands

12-18 curated ecommerce stories from 100+ sources, delivered every morning in under 5 minutes. Trusted by 10,000+ operators.

No spam. Unsubscribe anytime. · Learn more

Join the Community

600+ Active

Connect with ecommerce founders, share wins, get feedback on your store, and access exclusive discussions.

Join Discord Server