State and data
The state layer and the hooks your code reads data through.
All app state (config, products, cart, user, navigation) lives in a single Zustand-based store. You never touch the store directly. @gauntlet/state exposes it through hooks, and blocks read and write everything through them. This keeps blocks decoupled from where data actually comes from: the same hook call is backed by config, Shopify APIs, or Appbrew services as appropriate, with caching handled for you.
The hook families
The names below are the ones you will use most, grouped by what they cover.
Blocks — a block's own config, resolved through the layers described in Config and theme:
useBlock(screenId, componentId, instanceId)— the block's raw config entryuseBlockSettings(block)— mergedsourceandoptions(globals + instance)useBlockStyle(block)— merged style (theme + variant + instance)
Products:
useProduct,useProductByHandle,useProductsByHandles
Cart and checkout:
useCart,useAddToCart,useCheckout
User:
useUser— the signed-in customer and authentication state
Settings:
useSettings— the app's global settings objectuseGlobalSettings— global defaults for block typesuseIntegrationSettings— your integration's config values (see The integrations model)
Navigation:
useRoute— the current routeuseLink— navigate to a screen, product, collection, or URL from config-style links
Shopify queries and metafields:
useShopifyQuery— run a raw Storefront GraphQL queryuseMetafieldQuery,useMultipleMetafields— read metafields from products and other resources
Analytics:
useAnalytics— fire events to every registered tracker
Type the result, null-check before use
Hooks that fetch remote data return null (or undefined) until the data arrives, and query hooks are generic: you declare the shape you expect. Both rules together look like this:
import { useShopifyQuery } from '@gauntlet/state'
interface FeaturedProductResult {
product: {
title: string
handle: string
} | null
}
const query = `
query {
product(handle: "featured-hoodie") {
title
handle
}
}
`
export function FeaturedProduct() {
const data = useShopifyQuery<FeaturedProductResult>(query)
if (!data?.product) {
return null
}
return <Text>{data.product.title}</Text>
}The pattern generalizes to every data hook: define an interface for the result, assert or parameterize the hook with it, and guard before rendering. Rendering null while data loads is correct. A block that assumes data is present crashes the screen for users on slow networks.
Arrays too
The same discipline applies to lists: check the array exists and has items before mapping over it. Config-driven data means any field can be absent on any given app.
Writing state
Mutations go through the same hooks: useAddToCart returns a function that adds line items, useCheckout starts checkout, and so on. Fire-and-forget from event handlers; the store updates and every subscribed block re-renders reactively.
Beyond hooks: actions and events
Hooks are how your UI reads and writes. Actions are the layer underneath: cart:add, checkout:begin, coupon:apply and friends are named operations that anyone can intercept through the app extension host from @gauntlet/brewery (platform 0.27.0 and later). useAddToCart runs cart:add for you.
That matters in two directions. If you are extending someone's app (a partner integration), it is how you change behavior without touching their code. If you are calling an action yourself, note that it returns a Result instead of throwing, so a blocked operation hands you a reason to show the user.
On 0.26 and earlier: the action layer isn't available. Hooks call the built-in operations directly, and a package can't intercept them; behavior changes need a change in app code.
There is also onConfigReady(cb) for running setup once app config has landed. See Extending app behavior.
Full reference
The complete hook catalog with signatures lives at the @gauntlet/state reference.

