# From stored data to published data on Shopify and BigCommerce

> We measured whether what you save in the Shopify or BigCommerce admin reaches the published HTML, layer by layer: method, results and what didn't fail.

URL: https://clione.ai/studies/stored-vs-published-data/
Lang: en

You save something in your store's admin (a [metafield](/glossary/shopify-metafields/), a [custom field](/glossary/bigcommerce-custom-fields/), an FAQ, a new description) and the platform confirms the change. Then you don't see it in Google, or in an AI answer, or in the Google tool that checks for [rich results](/glossary/rich-results/). The confirmation was about the admin, not the page. **A saved value is not yet a published value**: the theme, the apps, JavaScript, the cache and time all sit between the two, and any of them can keep it out of what the requester of the page receives.

And the page has more than one reader. The same URL can exist in four versions: the one a shopper's browser builds, the one Google renders before adding it to its index, the one a [crawler](/glossary/web-crawler/) gets when it reads the HTML without running JavaScript, and the copy Google kept from the last time it came by. We measure which saved fields reach each of those versions on Shopify and BigCommerce stores, where the ones that don't get lost, and why. We also publish what did arrive, because a failure that doesn't show up is a result too.

## Why a saved value may not get published

Before measuring, the mechanism. Each of these seven routes is documented by the platform or by the standard that governs it; none of them is a statistic yet.

### The theme doesn't render the field

On Shopify, a metafield doesn't appear in the store by itself. It shows where the theme references it: connected as a dynamic source to a section or block in the theme editor, or written into the template code.[^shopify-metafields-display] In [Liquid](/glossary/shopify-liquid/), the language of Shopify themes, that reference looks like `{{ product.metafields.namespace.key }}`, and `.value` returns its content.[^shopify-liquid-metafield] **A metafield nobody has connected is saved and not published.**

One setting tends to take the blame: the "Storefronts" access on the metafield's definition. Shopify explains that this setting controls permissions for the Storefront API, the one headless and custom storefronts use, and that it doesn't affect Liquid templates: in Liquid, [metafields](/glossary/shopify-metafields/) are always accessible, with that setting on or off.[^shopify-metafields] If a metafield doesn't show on the page, the first thing to check is whether the theme asks for it.

BigCommerce has two fields that look alike and don't publish the same way. [Custom fields](/glossary/bigcommerce-custom-fields/) appear on the storefront, on the page of the item they belong to, as name and value pairs, and where they sit depends on the theme.[^bc-custom-fields] Metafields, even with the permission that makes them visible to the storefront, can be displayed there through the GraphQL Storefront API.[^bc-metafields] That is the route BigCommerce documents, not the theme's HTML, so we treat that metafield as a control: if it doesn't appear in the HTML, that's expected.

### An app installed with its app embed switched off

Many Shopify apps add their code to the store through an [app embed](/glossary/shopify-app-embed/): a block that Shopify renders and injects before the closing `</head>` or `</body>` tag. **After the app is installed, those blocks are turned off by default.** You turn them on in the theme editor, under theme settings, or through a deep link the app itself provides.[^shopify-app-embeds] An FAQ app or a markup app can be installed and configured without having written a single line to the page.

### Two sources describe the same product

A Shopify theme can generate its own [JSON-LD](/glossary/json-ld/) with Liquid's `structured_data` filter, which outputs the item as a [Product](/glossary/product-schema/) from [Schema.org](/glossary/schema-org/), or as a [ProductGroup](/glossary/productgroup/) if it has variants, inside a `<script type="application/ld+json">` tag.[^shopify-structured-data-filter] If a reviews app or an SEO app adds another block, the page describes the same product twice.

That alone is not an error. Google accepts several [structured data](/glossary/structured-data/) items on one page, nested inside each other or in separate blocks, and explains that separate blocks can be tied together with the [@id identifier](/glossary/id-json-ld/).[^google-sd-policies] In JSON-LD, `@id` uniquely identifies each node the document describes.[^jsonld-11] **What turns duplication into a defect is conflict**: two different values for the same property of the same product, for example one price in the theme's block and another in the app's. That's why we separate the consistent duplicate from the conflicting one, and the first doesn't count as a failure.

### It only exists after JavaScript runs

Google processes a page in three phases, crawling, rendering and [indexing](/glossary/indexing/), and in the second one a headless Chromium runs the page's JavaScript.[^google-js-seo] Thanks to that [JavaScript rendering](/glossary/javascript-rendering/), a value a script adds after the page loads can make it into Google's index. But the same documentation points out that **not every bot can run JavaScript**.[^google-js-seo] And for product markup it adds another warning: dynamically generated markup can make Shopping crawls less frequent and less reliable, which is a problem for data that changes fast, like [price and availability](/glossary/price-and-availability/).[^google-sd-js] A value injected with JavaScript can exist for one reader and not for another.

### A cached copy

Between the store's server and whoever requests the page there may be a [cache](/glossary/cdn-caching-for-bots/). While a stored response is fresh, the cache can serve it without checking with the origin server, and the `Age` header says how many seconds have passed since that response was generated or validated at the origin.[^rfc9111] That's why a change you just saved can take a while to show. We give no cache times for any platform here: the method observes them in the headers and in how long each value takes to converge, and publishes them as measured, not as a rule.

### Right today, different tomorrow

A field that was published correctly can change later without anyone editing it by hand: a sync from the [PIM](/glossary/pim/), an app update or a theme change. That's [data drift](/glossary/data-drift/) and, when one system writes over what another wrote, [field overwrite](/glossary/field-overwrite/). A measurement taken on one day tells you how the page stood that day. **Checking the published page is a job you repeat**, not a box you tick once.

### Markup the page doesn't show

Google is blunt about it: don't mark up content that isn't visible to readers of the page; if the JSON-LD describes something, the body of the page has to describe the same thing.[^google-sd-policies] An app can declare an answer, a rating or a price in the markup that the shopper can't find on the page. We record it as a class of its own, "In markup, not visible", because the value has been published, but only for machines.

## How we measured it

Whoever runs this study is part of a team that also builds a tool that checks published pages. That's why the tool plays no part in any measurement: they all come from HTTP requests, a headless browser and Google's public tools, and **the method is written so that anyone can repeat it**.

### The sample

Two platforms, Shopify and BigCommerce, and two kinds of store:

- **Test stores**, one per platform, set up for the study. On Shopify, on a paid plan: Shopify announced in 2020 that new development stores are password protected, and that a partner can't remove the password unless they transfer the store to a merchant or choose a plan.[^shopify-dev-stores] And the [Rich Results Test](/glossary/rich-results-test/) needs every resource on the page to be reachable by an anonymous user on the internet.[^google-rrt] A development store could only be measured on the first two layers, and we would say so. The themes: on Shopify, Shopify's reference theme in its current version and a theme a client uses; on BigCommerce, Cornerstone, a [Stencil](/glossary/bigcommerce-stencil/) theme, and one other theme. The name and version of each are noted on the day of the run.
- **Authorized stores**, up to three per platform, belonging to clients who give written permission, store by store, for us to read their pages. They are named or anonymized according to that permission. Read only: nothing is changed on them.

On each store we read three [product pages](/glossary/product-page/): a simple one, one with variants and, if the store has one, one with an active reviews or FAQ app. Optionally, a collection or [category page](/glossary/category-page/).

### What we saved

On Shopify: the [product description](/glossary/product-description/), the [meta title](/glossary/meta-title/) and [meta description](/glossary/meta-description/), one metafield connected as a dynamic source, another referenced in Liquid, the theme's JSON-LD with its product properties (name, description, price, availability, [brand](/glossary/brand-schema/) and [GTIN](/glossary/gtin/), all properties of Product and its [Offer](/glossary/offer-schema/) in Schema.org),[^schema-product] an app's JSON-LD through its app embed, first off and then on, and the FAQs with their [FAQPage](/glossary/faqpage/) markup, if an app supplies them.

On BigCommerce: the description, the page title and the meta description, one custom field, the content of a [BigCommerce widget](/glossary/bigcommerce-widget/) placed in a theme region, which BigCommerce says renders as part of the storefront's HTML,[^bc-widgets] a JSON-LD block injected by a script, the theme's structured data if it has any, and the GTIN, the UPC and the [MPN](/glossary/mpn/).

Each platform carries a control: a saved field that isn't connected to anything and therefore shouldn't appear. On Shopify, an unconnected metafield; on BigCommerce, a metafield with storefront read permission (`read_and_sf_access`).[^bc-metafields] **A missing control is "absent by design" and never counts as a failure.** The results table lists each field with the route by which it was supposed to reach the page.

### What counts as saved

Each field's moment zero is recorded three times: a screenshot of the admin, an API read saved as JSON with its last-updated date (the Admin GraphQL API on Shopify, the Catalog REST API on BigCommerce), and the time in UTC. On the test stores, each value carries a unique string that identifies it in any layer without confusing it with other text on the page.

### The four layers we read

1. **HTML without JavaScript.** A GET request, running nothing, repeated with three identities: the [user agent](/glossary/user-agent/) of a desktop browser, that of [Googlebot](/glossary/googlebot/) Smartphone and that of [OAI-SearchBot](/glossary/oai-searchbot/). Changing the user agent records what the server sends to whoever shows up under that name, not what that bot actually does. We keep the status code, the final URL, the headers (`cache-control`, `age`, `cache-status`) and the body.
2. **Rendered DOM.** A headless Chromium, driven by Playwright: it loads the page, waits for the network to go idle and then a fixed wait more. The DOM is serialized and the visible text and every `application/ld+json` block are extracted.
3. **Rich Results Test**, with the smartphone agent, the one the tool uses by default: detected items, errors, warnings and a screenshot.[^google-rrt] Google warns that it doesn't guarantee the page will appear as it does in the preview.[^google-rrt] The FAQ rich result hasn't appeared in Google Search since May 7, 2026,[^google-updates] so FAQPage, and any type the test doesn't recognize, is validated with the [schema validator](/glossary/schema-markup-validator/) against the HTML from layers 1 and 2.
4. **[Search Console](/glossary/google-search-console/) URL Inspection**, only where we have access to the property, because the tool only inspects URLs in the open property.[^gsc-url-inspection] We read the live test and the indexed version, which is the most recent one Google has indexed, not the one that's live, with the date it was last crawled. Its crawled page view gives the returned HTML and the headers; the screenshot of the rendered page only exists in the live test.[^gsc-url-inspection]

### When we read

Layers 1 and 2 at one minute, 15 minutes, one hour and 24 hours after saving. Layers 3 and 4 live, at one hour. The indexed version, whenever Google crawls the URL again; if it doesn't within the week, we say so. An optional read at seven days.

### How we compare

Before comparing, each value is normalized the same way on both ends: whitespace collapsed, HTML entities decoded, Unicode in NFC form and the price as a number plus currency. If a layer truncates the text, we note where.

### How we classify each reading

- **Match**: the saved value is in the layer, unchanged.
- **Absent**: it isn't there.
- **Altered**: it's there, but truncated, escaped or with an earlier value.
- **JavaScript only**: missing from the HTML without JavaScript and present in the rendered DOM.
- **Cached**: the earlier value shows first and then converges; we note how long it took.
- **Consistent duplicate**: two or more nodes describe the same thing with the same values.
- **Conflicting duplicate**: two nodes give different values for the same property.
- **In markup, not visible**: it's in the JSON-LD and not in the text the shopper sees.
- **Absent by design**: the controls.

### How we attribute the cause

On the test stores, the cause is demonstrated: we change one thing only and read again. On the authorized stores, where nothing is touched, it is inferred, and the table marks it as inferred.

## Results

| Platform    | Field                                                                                                              | How it was supposed to publish                                 | HTML without JavaScript | Rendered DOM | Rich Results Test | URL Inspection (live / indexed) | Result  | Cause   |
| ----------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | ----------------------- | ------------ | ----------------- | ------------------------------- | ------- | ------- |
| Shopify     | Product description                                                                                                | Theme, product template                                        | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| Shopify     | Meta title and meta description                                                                                    | Theme, `<head>`                                                | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| Shopify     | Connected metafield                                                                                                | Dynamic source in the theme editor                             | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| Shopify     | Metafield in the template                                                                                          | Liquid                                                         | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| Shopify     | Unconnected metafield (control)                                                                                    | None: absent by design                                         | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| Shopify     | Theme JSON-LD (Product or ProductGroup) | Theme, `structured_data` filter                                | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| Shopify     | An app's JSON-LD (off, then on)                                                                                    | App embed                                                      | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| Shopify     | FAQs and FAQPage                                                                                                   | App, block or app embed                                        | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| BigCommerce | Product description                                                                                                | Theme                                                          | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| BigCommerce | Page title and meta description                                                                                    | Theme, `<head>`                                                | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| BigCommerce | Custom field                                                                                                       | Theme, wherever it places it                                   | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| BigCommerce | Metafield with storefront access (control)                                                                         | GraphQL Storefront API: absent by design from the theme's HTML | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| BigCommerce | Theme structured data                                                                                              | Theme, if it includes it                                       | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| BigCommerce | Widget content                                                                                                     | Widget in a theme region                                       | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| BigCommerce | Injected JSON-LD                                                                                                   | Script                                                         | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |
| BigCommerce | GTIN, UPC and MPN                                                                                                  | Theme, if it includes them                                     | Pending                 | Pending      | Pending           | Pending                         | Pending | Pending |

| Platform    | Median time to converge | Maximum time to converge | Fields measured |
| ----------- | ----------------------- | ------------------------ | --------------- |
| Shopify     | Pending                 | Pending                  | Pending         |
| BigCommerce | Pending                 | Pending                  | Pending         |

## What didn't fail, and what went unmeasured

A study that only publishes failures overstates the problem. What went right, the hypotheses that didn't hold and what the instruments couldn't see weigh as much here as the results.

Some things this method doesn't measure from the start, and it's worth saying so before the results:

- **Whether a page earns a rich result.** Google's test says whether it detects the markup, not whether the page will show it in Search,[^google-rrt] and the FAQ one no longer shows.[^google-updates]
- **What a bot actually does.** Reading with Googlebot's or OAI-SearchBot's user agent records what the server hands to that name, not how each company processes the page.
- **The indexed version of stores we can't access.** Without the Search Console property there's no URL Inspection.[^gsc-url-inspection]

## What it means

Each layer answers a different question, and none of them answers all of them. This is the frame for reading the results:

| If you want to know…                                            | Read…                                        |
| --------------------------------------------------------------- | -------------------------------------------- |
| What a bot that doesn't run JavaScript receives                 | The HTML without JavaScript (layer 1)        |
| What a shopper's browser builds                                 | The rendered DOM (layer 2)                   |
| Whether Google detects your structured data, and as which types | The Rich Results Test (layer 3)              |
| What Google sees at your URL right now                          | The URL Inspection live test (layer 4)       |
| What Google kept the last time it crawled the page              | The URL Inspection indexed version (layer 4) |

**A test that passes doesn't prove the value is published**: it validates the markup the tool found at that moment, and the indexed version can differ from the live one.[^gsc-url-inspection] The comparison [Validate the markup vs verify the publication](/comparisons/validate-vs-verify/) works through the difference between the two checks, and the glossary entry on [publication verification](/glossary/publication-verification/) covers the concept.

To run the check on your own store, step by step, there are two guides: [From metafield to published page (Shopify)](/guides/metafield-to-published-page/) and [From widget to published page (BigCommerce)](/guides/bigcommerce-widget-to-published-page/). To read your product pages the way a bot does, see [What an AI crawler sees in your store](/guides/what-an-ai-crawler-sees/).

## Limits

- **Few stores, and hand-picked themes and apps.** The results hold for the stores we measured, not for "Shopify stores" or "BigCommerce stores". There is no frequency claim here.
- **One week and one set of versions.** A theme or an app that updates the following month may behave differently.
- **Changing the user agent is not a bot's behavior.** It tells you what the server hands to that name.
- **The Rich Results Test and URL Inspection only speak for Google.** Other engines may read the page differently.
- **We don't control when Google crawls again.** An old indexed version is a data point, not a store failure.
- **The test stores have no real traffic**, and that can change how their caches behave.
- **The authorized stores select themselves**: they are clients who agree to let us read their pages, and BigCommerce weighs more heavily among them.

## The data

Each reading is one row of the dataset, with these columns: `run_id`, `datetime_utc`, `operator`, `platform`, `store_kind`, `store_id`, `theme_name`, `theme_version`, `relevant_apps`, `url`, `page_type`, `field`, `saved_where`, `saved_value_or_hash`, `saved_at_utc`, `render_route`, `layer`, `user_agent`, `http_status`, `cache_headers`, `found`, `found_value`, `node_count`, `conflict`, `class`, `time_to_converge`, `cause`, `cause_basis`, `evidence_file` and `notes`. For authorized stores that are anonymized, `store_id` is a code and the saved value is published as a hash.

The rest of the concepts for measuring what you publish are laid out in order in the [measurement and verification reading path](/learn/measurement-and-verification/).
