Supporting app driven and native golf club configurators in one Shopify theme
- 01Built a guided LAB Golf interface around controls rendered by Hulk Product Options
- 02Built a native Miura configurator driven by metaobjects, variant metafields, and section settings
- 03Isolated both runtimes by product template and extended the cart flow for Miura components
Role
Shopify theme developer for both configurator implementations. Implemented within a customized Eurus theme and its existing product and cart foundations. Codebase inherited Shopify theme with Hulk Product Options and an existing pipeline for adding linked products to the cart.
Scope covered
- Online Store 2.0 product templates and section blocks
- Hulk Product Options storefront integration
- Shopify metaobjects and variant metafields
- Product forms and cart line properties
- AJAX cart, cart page, and cart drawer behavior
Context
ProShop GolfN sells clubs that customers configure before purchase. I built two implementations for that experience. LAB Golf putters use Hulk Product Options as the source of option controls. Miura irons use a native theme interface that reads Shopify catalog data and adds selected components as linked cart lines.
Both implementations live in the same customized Eurus theme, which uses Liquid, Alpine.js, and Tailwind. They serve a similar customer journey, but they do not have the same technical boundary. For LAB Golf, the theme adapts an interface rendered by an external app. For Miura, the theme owns the option data, browser state, readable specifications, and cart relationship.
I delivered the LAB configurator work in September 2025. The Miura implementation followed in March 2026 and joined the main theme that month.
My role
I built the theme layer for both configurators. For LAB Golf, I turned the Hulk controls into a guided sequence and connected completion state to the product purchase controls. For Miura, I built the Liquid data adapter, the native Alpine controller, the product form contract, and the cart behavior for linked components.
Eurus and Hulk Product Options were existing foundations. Hulk remained responsible for rendering the LAB option controls, while the Miura implementation extended Eurus's pipeline for linked products with markers that described one configured build.
The actual challenge
The storefront needed a consistent guided experience without pretending that both product lines had the same source of truth.
LAB Golf already had its option model in Hulk. Recreating those fields in Liquid would have introduced a second representation that could drift away from the app. The theme instead needed to organize the rendered controls, validate each stage, and reveal the real purchase form only after the required selections were complete.
Miura had a different requirement. A configured iron combines a head with a selected shaft, grip, ferrule, and optional SST shaft alignment service. Each priced component is represented by a Shopify catalog variant. The native interface therefore needed to produce readable build details and linked variant identifiers from the same selection state.
System design
I kept the two implementations separate at the runtime boundary:
| Product line | Option authority | Theme responsibility |
|---|---|---|
| LAB Golf | Hulk Product Options | Guided stages, navigation, and completion gating |
| Miura | Shopify catalog and section data | Option data, guided stages, specifications, and linked cart lines |
sections/main-product.liquid checks the product template suffix when the configurator setting is enabled. A lab-golf product receives the Hulk controller, while a miura-golf product receives the native controller. snippets/product-template.liquid then loads only the matching JavaScript file. It also omits the Hulk app block from the Miura product while preserving that block for LAB Golf.
This separation keeps each controller focused on the system it actually owns. Shared product page code only decides which runtime applies.
Adapting Hulk for LAB Golf
The LAB template includes the Hulk Product Options app block. Because the app renders its interface after the theme, assets/product-hulk.js waits for that markup before initializing. It locates the Hulk option container, hides the original continuous list, and inserts a navigation interface with Foundation, Function, and Form stages.
The controller does not recreate the option catalog. It operates on the controls supplied by Hulk. It groups options using the classes present in the rendered markup, moves between stages, and observes changes to select elements, custom dropdowns, and the required text input.
Completion is calculated per stage. The next button remains unavailable while the current required controls are empty. The product template initially hides the real purchase form behind a disabled state, then reveals it when the Alpine controller reports that every stage is valid.
This approach preserved one option authority, with a deliberate dependency on Hulk's rendered structure. The initialization delay and selectors such as .hulkapps_product_options, .tab2-option, and .dropdown_selected formed the adapter contract between the app and the theme.
Building Miura natively
The Miura controller does not wait for an app interface. snippets/miura-configurator.liquid builds its data from Shopify records during the page render.
Shaft and grip brands come from shop.metaobjects.shaft_brands.values and shop.metaobjects.grip_brands.values. Each entry points to a product whose variants supply IDs, titles, prices, availability flags, and images. Shaft variants also use custom.shaft_material and custom.shaft_flex so the browser can narrow the available models as the customer changes material and flex.
Ferrules and the SST service come from products selected in the section settings. The Liquid adapter serializes all of this data for assets/product-miura.js, which manages the three native stages and keeps the product forms synchronized.
The controller writes nine readable properties for the build: club number, length, lie, loft, extra wraps, shaft, ferrule, SST, and grip. It also maintains linked variant inputs for four component roles. Empty properties and unresolved component inputs remain disabled so they are not submitted prematurely.
Sharing purchase and cart behavior
Both configurators gate purchasing, but they reach that result differently. LAB derives validity from Hulk's rendered controls. Miura calculates validity from state it owns. For Miura, a MutationObserver also preserves the disabled state applied by the underlying theme, so a complete configuration cannot override another reason that the product is unavailable.
Miura then adds a cart contract that LAB does not need from the custom theme code. The parent form carries _miura_bundle=true and the selected linked variants. The cart component hashes the parent form values, posts the parent line, then posts the component lines with the same relationship key and _miura_addon=true.
The cart drawer suppresses separate rows for those Miura extras and removes them with the parent. The main cart keeps the component lines linked by coordinating quantity and removal controls. The theme contains no checkout extension, so checkout presentation is outside this implementation.
The two Miura requests are not atomic. If Shopify accepts the parent and rejects a component request, the interface reports the error, but the parent remains in the cart. That tradeoff kept the integration within Shopify's existing AJAX cart flow while making the partial failure visible.
Result
I delivered two isolated configurator architectures inside one product page system. LAB Golf retained Hulk Product Options as its option authority while presenting its controls as three guided stages. Miura used three native stages and associated four catalog component roles with a parent club build.
Customers received a consistent guided journey even though the two product lines relied on different data and cart contracts. The theme gained a clear extension point for each runtime, and shared product page behavior remained free from state specific to a configurator.
Takeaway
Similar customer experiences do not always need identical implementations. When an app already owns the option model, a focused theme adapter can add structure without copying the data. When configuration changes the catalog items placed in the cart, native ownership gives the theme a clearer contract from selection through cart handling. Keeping those runtimes separate made the boundary visible and allowed each product line to use the source of truth it actually required.