Integrate LinkedOption with third-party apps
LinkedOption can sync product groups to Shopify metafields and metaobjects so mobile apps, custom storefronts, and other integrations can display linked product options. The exported data includes option names, values, display categories, and the product for each option combination.
Your integration developer reads this data through Shopify's Storefront API and builds the option selector in your other app. Enabling sync makes the data available; the other app still needs to support this format.
Enable the integration
Product group sync is available on the Advanced, Premium, and Pro plans, with monthly or annual billing. Lower plans show an upgrade link in Settings. Downgrading below Advanced pauses product group sync and keeps previously exported data without further updates. After upgrading, enable product group sync again to refresh the data.
- Open LinkedOption → Settings → Advanced.
- Find Integrate with third-party apps, the last setting on the page.
- Turn on Sync product group data to a public metafield, then click the page's Save button. LinkedOption starts syncing your published product groups in the background after the setting is saved. Discard restores the saved setting without changing sync.
- Wait for Product groups synced, then give your developer this guide and a product ID from a published group.

Select Sync product group data to a public metafield for third-party integrations, then click Save. The Agentic option shown checked in this example is a separate setting and is not required for third-party product group sync.
Turning sync off and clicking Save stops further updates. By default, data already synced to Shopify is kept.
When you uncheck an enabled sync setting, an additional unchecked option appears: Also remove the synced product group data. Select it before clicking Save to start removing the exported product metafields and group metaobjects in the background. This option appears only when you are turning off a previously saved enabled setting; it is hidden on first use when sync is already off. Discard or turning sync back on clears the removal choice. Agentic option data and product groups in LinkedOption are kept.
Product group changes sync automatically. The Settings card shows the store's overall public sync status, separated from the settings by a divider. There are no separate sync or deletion buttons. Removal starts only after you select the removal option and save.
What your developer needs
- A Storefront API token for the same Shopify store, with
unauthenticated_read_product_listingsandunauthenticated_read_metaobjectspermissions. - Products published to the sales channel used by that token.
- The ability to query Shopify metafield references and render a custom selector in the third-party app.
Both exported definitions have Storefront access set to PUBLIC_READ. This makes them readable with the appropriate Storefront access; it does not grant anonymous write access. Merchants and apps with the necessary Admin API permissions can edit the exported records. Storefront API access is read-only. See Shopify's access scope reference.
Use a public Storefront token in a browser or mobile client. Keep Admin API credentials and private Storefront tokens on your server. The examples below use Storefront API version 2026-07.
Where the data lives
LinkedOption creates the definitions when you enable sync. Your developer does not need to create them manually.
Resource | Identifier | Type / purpose |
|---|---|---|
Product metafield | Namespace |
|
Metaobject | Type | One record per exported group |
Metaobject field |
|
|
Metaobject field |
|
|
The product reference is the entry point. There is no separate linked_options.group_id product metafield. The group_id field belongs to the referenced metaobject.
Step 1: Read the current product's group
Send this GraphQL query to your store's /api/2026-07/graphql.json Storefront endpoint:
query LinkedOptionPublicGroup(
$productId: ID!
$country: CountryCode
$language: LanguageCode
) @inContext(country: $country, language: $language) {
node(id: $productId) {
... on Product {
id
metafield(namespace: "linked_options", key: "group") {
reference {
... on Metaobject {
id
field(key: "value") {
value
}
}
}
}
}
}
}Example variables (replace the product ID):
{
"productId": "gid://shopify/Product/1001",
"country": "US",
"language": "EN"
}For a public Storefront token, use request headers:
Content-Type: application/json
X-Shopify-Storefront-Access-Token: YOUR_PUBLIC_STOREFRONT_TOKEN
Send a JSON request body containing query and variables. Check HTTP failures and GraphQL errors before using the result. The metaobject field's value is a JSON string, so parse it with JSON.parse. See Shopify's MetaobjectField reference.
// responseBody is the parsed Storefront API response.
if (responseBody.errors?.length) {
throw new Error("Could not read the product group.");
}
const raw = responseBody.data?.node?.metafield?.reference?.field?.value;
let group = null;
try {
const parsed = raw ? JSON.parse(raw) : null;
if (
parsed?.schema_version === 1 &&
parsed.active === true &&
Array.isArray(parsed.options) &&
Array.isArray(parsed.products)
) {
group = parsed;
}
} catch {
// Use the product's normal interface if the exported JSON is invalid.
}
If the product, metafield, or reference is missing, or the schema is unsupported or inactive, use your app's normal product interface. Do not block the product page while a group is syncing.
The common data structure
All three LinkedOption group types use this structure. IDs in this example are illustrative; treat actual option and value IDs as opaque strings.
{
"schema_version": 1,
"group_id": "lo-group-123",
"revision": 1,
"active": true,
"options": [
{
"id": "o-color",
"name": "Color",
"product_page_type": "COLOR_SWATCH",
"product_card_type": "DEFAULT",
"values": [
{
"id": "v-red",
"value": "Red",
"swatch": {
"color_type": 1,
"color": "#ff0000",
"color1": "",
"color_image": "",
"image": "",
"image_source": "CUSTOM"
},
"badge": ""
},
{
"id": "v-blue",
"value": "Blue",
"swatch": {
"color_type": 1,
"color": "#0000ff",
"color1": "",
"color_image": "",
"image": "",
"image_source": "CUSTOM"
},
"badge": ""
}
],
"value_groups": [
{
"id": "g-warm",
"name": "Warm colors",
"value_ids": [
"v-red"
]
}
]
},
{
"id": "o-size",
"name": "Size",
"product_page_type": "BUTTON",
"product_card_type": "DROPDOWN",
"values": [
{
"id": "v-small",
"value": "Small",
"swatch": {
"color_type": 1,
"color": "",
"color1": "",
"color_image": "",
"image": "",
"image_source": "CUSTOM"
},
"badge": ""
},
{
"id": "v-large",
"value": "Large",
"swatch": {
"color_type": 1,
"color": "",
"color1": "",
"color_image": "",
"image": "",
"image_source": "CUSTOM"
},
"badge": ""
}
],
"value_groups": []
}
],
"products": [
{
"id": "gid://shopify/Product/1001",
"option_values": {
"o-color": "v-red",
"o-size": "v-small"
}
},
{
"id": "gid://shopify/Product/1002",
"option_values": {
"o-color": "v-blue",
"o-size": "v-large"
}
}
]
}Field | Meaning |
|---|---|
| Contract version. This guide describes version |
| Group identity within this store. Include the store identity when caching across stores. |
| Increases when source data or exported translations change. An unchanged resync can keep the same revision. |
| Render only when |
| Ordered options; preserve this order in the selector. |
| Identity used to match combinations. |
| Display label; it can be empty for a subgroup-based group. |
| Ordered values with an |
| Optional display categories within an option. An empty array means no categories. |
| IDs of values in that category. Categories can overlap. |
| Shopify Product GID. Keep it as a string. |
| Option ID → value ID assignments for that product. This maps an option combination to its product. |
Option display types
Each option includes product_page_type from its selected template and product_card_type from its product card configuration.
product_page_type:BUTTON,COLOR_SWATCH,IMAGE_SWATCH, orDROPDOWN.product_card_type: the same four types, orDEFAULTto followproduct_page_type.
Use the appropriate field for the surface you render. Multiple-option groups can use different types for each option. Subgroup groups use the first configured row's types for their single shared option, matching LinkedOption's existing rendering behavior. These types are not translated. For older cached records without these fields, default to BUTTON on product pages and DEFAULT on product cards. These additive fields keep schema version 1; existing records receive them after the next synchronization.
How the three group types map
- Single option: one entry in
options, with one assigned value per product. - Multiple options: multiple entries in
options, with a complete set of assignments per selectable product. Existing display categories appear invalue_groups. - Subgroups: one option with categories in
value_groups. A product can appear in several categories, whileproductscontains it once. Categories are presentation groups, not additional selection dimensions.
A legacy representative product with no assigned combination can have option_values: {}. It remains a group member but must not be treated as a selectable combination. Partially assigned or conflicting combinations cause that group's sync to fail rather than exporting an ambiguous mapping.
Match by IDs, never by translated labels. Translations and reordering preserve IDs. Removing and recreating a value or product relation can produce a new ID; refresh selection state when the group changes.
Step 2: Fetch product details in the same context
The group exports product IDs only. Query those products separately for titles, images, prices, availability, URLs, and variants. Use the same token, country, language, and relevant buyer context as your product page.
query LinkedOptionGroupProducts(
$ids: [ID!]!
$country: CountryCode
$language: LanguageCode
) @inContext(country: $country, language: $language) {
nodes(ids: $ids) {
... on Product {
id
title
handle
availableForSale
}
}
}Set ids to the exported Product GIDs and extend the selection set for your app's needs. Batch large lists within Shopify's query limits. Products unavailable to the token's channel can return null. Apply your storefront's market and purchasing rules before deciding which combinations are selectable. Membership in a group does not guarantee that a product is purchasable in the current market.
Step 3: Render options and select the matching product
- Render
optionsand theirvaluesin order. Usevalue_groupsfor category headings if present. - Find the current product in
productsand initialize the selection from itsoption_values. - After a selection changes, find the product whose assignments match every selected option. For partial selections, use matching candidate products to determine which remaining values are available.
- Disable combinations that have no matching, accessible product. Do not assume every possible combination exists.
- Switch to the matched product in your app. To add it to the cart, select a variant belonging to that product using your app's normal variant and cart flow.
For the example above, Red + Small selects product 1001, and Blue + Large selects 1002. Red + Large has no product and should be unavailable.
This helper finds a complete combination after you have filtered inaccessible products:
function findProductForSelection(group, selection, accessibleProducts) {
if (!group.options.length) return null;
const complete = group.options.every((option) =>
option.values.some((value) => value.id === selection[option.id])
);
if (!complete) return null;
return (
group.products.find(
(product) =>
accessibleProducts.has(product.id) &&
group.options.every(
(option) =>
product.option_values?.[option.id] === selection[option.id]
)
) ?? null
);
}
accessibleProducts is a Set of Product GIDs your app can display in the current context. The exported mapping describes linked products; each target product can still have its own Shopify variant options. Option values include swatch colors, image configuration, and badge text as described below. Theme-specific styling and product variant IDs are not included.
Swatches and badges
Each options[].values[] entry also includes swatch and badge so your integration can render color and image options:
{
"id": "v-red",
"value": "Red",
"badge": "New",
"swatch": {
"color_type": 2,
"color": "#ff0000",
"color1": "#0000ff",
"color_image": "",
"image": "https://cdn.example.com/red-swatch.jpg",
"image_source": "CUSTOM"
}
}color_type:1for a single color,2for two colors, or3for a pattern image. Usecolorandcolor1for colors, andcolor_imagefor the pattern image URL.image: the option image URL, separate from the pattern image.- For single-option and subgroup groups,
image_sourceisCUSTOM,FIRST, orSECOND. LinkedOption resolves the selected image before exporting it; a missing second image falls back to the first. - For multiple-option groups,
image_sourceisCUSTOM,FIRST, orPRODUCT.FIRSTexports the first matching product image chosen by LinkedOption;CUSTOMkeeps the custom image. WithPRODUCT, use the selected combination to find the target product and fetch its image through your contextual Shopify product query. badgeis plain display text and can be translated. Swatch colors, image URLs, and IDs remain unchanged across translations.- Empty strings mean no configured value. If swatch fields or images are missing, fall back to text options.
These are optional additions to schema version 1. Existing records receive the new fields after a group update, a homepage Refresh, or daily reconciliation. Refresh cached JSON after synchronization. Each localized JSON copy still has the 128 KiB UTF-8 limit.
Languages, markets, and caching
LinkedOption exports option, value, and category translations for Shopify's published languages. Query the group with @inContext(language: ..., country: ...) so Shopify can resolve the appropriate translation and market context. Market-specific labels use the language's global translation as a fallback, then the original source text when no translation is available.
IDs, ordering, and the list of group members are shared across languages and markets. Prices, currencies, inventory, B2B rules, and channel visibility come from your contextual Shopify product queries, not from the group JSON. @inContext alone is not a substitute for your app's purchasing and visibility rules.
Cache group data by store, group, revision, language, and market context. Product detail caches must also include the relevant channel and buyer context. Use a bounded cache lifetime: records can be removed and source changes sync asynchronously.
Sync behavior and troubleshooting
The status in Settings summarizes current public sync work across the store. It shows syncing while any task is waiting or running, and shows failure with a count if unresolved failures remain. A successful update to one group does not hide another group's failure. Individual group statuses are shown in the product group list. The page checks status every five seconds.
Enabling sync starts a full export. Product group and translation changes trigger background updates, and LinkedOption runs a daily reconciliation. Updates are asynchronous; retry after sync completes if a group or reference is temporarily missing.
Only published groups and active product members are exported. Removing or unpublishing a group removes its exported record and references. Disabling sync keeps the last exported data until you delete it or enable sync again.
If someone edits the exported base data in Shopify, a routine check preserves those edits when the LinkedOption source has not changed and shows a notice in Settings. A later source change can overwrite managed fields. Treat LinkedOption as the source of truth; place your own additional information in separate fields. External edits to translated copies are not individually checked for conflicts.
Symptom | What to check |
|---|---|
No | Sync is enabled and complete; the group is published and contains the product. |
Product or reference is | Token scopes, same-store token, product channel publication, and current sync status. |
Options appear, but switching selects the wrong product | Match option/value IDs and the full combination, not labels or array positions. |
A group fails to sync | Check the error shown in Settings, invalid combinations, conflicting Shopify definitions, or oversized data. Each JSON copy has a 128 KiB UTF-8 limit. |
Labels use the original language | Check that the Shopify language is published, the LinkedOption translation exists, sync completed, and the query supplies the intended language/country. |
Data appears unchanged after editing | Allow background sync to finish and refresh your context-specific cache. The homepage Refresh also triggers public sync when enabled; it preserves external edits if the LinkedOption source is unchanged. |
Third-party apps reading the complete group JSON must explicitly support this schema. For Shopify Catalog Mapping, use the separate Agentic option sync and follow the Agentic catalog mapping guide. Neither export automatically creates Shopify native Combined Listings.
Updated on: 14/10/2026
Thank you!
