and runs during HTML parse, but hubspotutk is set by js.hs-analytics.net, measured loading at ~834ms. On a first visit the cookie does not exist yet, so a single read returns nothing and the visitor gets no external ID at all — and a first visit is exactly what an ad click is. So poll briefly until HubSpot sets it. The SDK loads lazily (~4.5s) and replays a queue, so an init queued a second or two in still lands before any events are transmitted. */ (function attachExternalId() { var attempts = 0; var MAX_ATTEMPTS = 40; /* 40 x 200ms = 8s, then give up quietly */ var timer = null; function tryAttach() { var raw = getExternalId(); if (!raw) { if (++attempts >= MAX_ATTEMPTS && timer) clearInterval(timer); return; } if (timer) clearInterval(timer); sha256Hex(raw).then(function (hash) { oaiq("init", { user: { external_id_sha256: hash } }); }).catch(function () {}); } tryAttach(); /* returning visitor: immediate */ if (!getExternalId()) timer = setInterval(tryAttach, 200); })(); /* --------------------------------------------------------------------------- 2. Load the SDK off the critical path Whichever comes first: the main thread going idle after load, or the first sign of real user intent. No preconnect on purpose — a TLS handshake during LCP for a script we deliberately deferred is a net loss. Measured on clickpost.ai: load event 3614ms, SDK fetched 4508ms. ------------------------------------------------------------------------ */ var loaded = false; var INTENT = ["pointerdown", "keydown", "scroll"]; function loadSdk() { if (loaded) return; loaded = true; INTENT.forEach(function (t) { removeEventListener(t, loadSdk, { passive: true }); }); var s = document.createElement("script"); s.src = SDK; s.async = true; s.fetchPriority = "low"; document.head.appendChild(s); } INTENT.forEach(function (t) { addEventListener(t, loadSdk, { passive: true, once: true }); }); addEventListener("load", function () { window.requestIdleCallback ? requestIdleCallback(loadSdk, { timeout: 2500 }) : setTimeout(loadSdk, 1500); }, { once: true }); /* --------------------------------------------------------------------------- 3. Conversion id sha256(lowercase(email) + "|" + formId + "|" + ISO-hour), first 20 hex, "lead_" prefix. - email lowercased/trimmed so casing does not fork the id - formId keeps two different forms in the same hour from colliding - the hour bucket is only the server's fallback; the id computed here is sent to the relay verbatim, so the two paths cannot disagree even when a submit straddles an hour boundary Ads Manager's own sample omits event_id. We send it so the pixel event and the relay event collapse into one conversion instead of counting twice. ------------------------------------------------------------------------ */ function buildEventId(email, formId) { var seed = [ String(email || "").trim().toLowerCase(), String(formId || ""), new Date().toISOString().slice(0, 13) ].join("|"); return crypto.subtle.digest("SHA-256", new TextEncoder().encode(seed)).then(function (buf) { var hex = Array.prototype.map.call(new Uint8Array(buf), function (b) { return b.toString(16).padStart(2, "0"); }).join(""); return "lead_" + hex.slice(0, 20); }); } /* Guards a re-fire from the history.pushState that book-a-demo-core.js runs after submit, and from back-navigation onto the same state. */ function firedOnce(id) { try { if (sessionStorage.getItem("oaiq_" + id)) return true; sessionStorage.setItem("oaiq_" + id, "1"); return false; } catch (e) { return false; /* private mode / blocked storage: fire rather than lose it */ } } /* Path 2. keepalive lets the request finish even if the page navigates away. Failures are swallowed deliberately: a reporting call must never surface an error to someone who just submitted a form. The relay hashes the email server-side into user.emails_sha256 and adds the caller's IP and user agent, so this event carries the same match signals the pixel gets for free. */ function sendToRelay(email, formId, eventType, eventId) { try { var payload = { token: RELAY_TOKEN, email: email, form_id: formId || "", event_type: eventType, event_id: eventId, /* verbatim, so both paths always match */ source_url: location.href }; var oppref = getOppref(); if (oppref) payload.oppref = oppref; var externalId = getExternalId(); if (externalId) payload.external_id = externalId; fetch(RELAY_URL, { method: "POST", keepalive: true, headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).catch(function () {}); } catch (e) {} } /* Capture the email while the form is still on the page. onFormSubmitted does not reliably carry submissionValues, and by then the form may be removed. */ var lastEmail = ""; document.addEventListener("submit", function (e) { var f = e.target; if (!f || !f.querySelector) return; var i = f.querySelector('input[name="email"], input[type="email"]'); if (i && i.value) lastEmail = i.value; }, true); /* Every HubSpot form on the site reports through this one message event, so no module or theme JS needs editing to add coverage. */ window.addEventListener("message", function (e) { var d = e.data; if (!d || d.type !== "hsFormCallback" || d.eventName !== "onFormSubmitted") return; var email = (d.data && d.data.submissionValues && d.data.submissionValues.email) || lastEmail; if (!email) return; /* without an email there is no stable id to dedupe on */ var eventType = EVENT_BY_FORM[d.id] || EVENT_DEFAULT; buildEventId(email, d.id).then(function (id) { if (firedOnce(id)) return; oaiq("measure", eventType, { type: DATA_TYPE }, { event_id: id }); /* path 1 */ sendToRelay(email, d.id, eventType, id); /* path 2 */ }); }); })();

91ÊÓÆµ

See where your logistics stands on automation Start assessment

Home > Blog >

Ecommerce Share of Retail Sales: Growth Trends, Data & Forecasts (2026)

Ecommerce Share of Retail Sales: Growth Trends, Data & Forecasts (2026)

Aparajita Ray
By Aparajita Ray

Ready to ship smarter? Talk to our logistics experts.

Trusted by 600+ brands

In this blog

    TL/DR summary

    Global online share crossed 20% in 2024 and is projected to reach 22.9% by 2028; the US tracks at 16.2% (2024) on a path to 20.0% (2028). Q2 2025 showed e-commerce gaining share (16.3% seasonally adjusted) as ecommerce sales outgrew total. Use quarterly estimates to tune labor, media, and inventory.

    Key pointers:

     

    • Watch the Census Bureau department release each quarter; build plans on the seasonally adjusted series.

    • Expect a steady increase in share across the forecast window; align promo timing with first-quarter refunds and second-quarter graduations.

    • Balance online and store: let repeats flow through e-commerce; use stores for discovery and service.

    • Track price, architecture, and delivery promise; small price changes can move the share in sensitive categories like electronics.

    Keep your overview tight, your statistics clean, and your execution boringly excellent; the mix will do the rest.

    Ecommerce Share trend, global vs US (2022–2028)

     

    Scope 2022 2023 2024 2025 2026 2027 2028
    Global online share of retail 18.60% 19.40% 20.10% 20.80% 21.50% 22.20% 22.90%
    US online share of retail 14.40% 15.30% 16.20% 17.10% 18.00% 19.00% 20.00%
    Q2 2025 (Census Bureau; seasonally adjusted) — — — 16.3% share (online $304.2B / $1,865.4B) — — —
    Q2 2025 (not adjusted) — — — 15.5% share (online $292.9B) — — —

    Introduction

    For leaders, the signal inside the e-commerce noise is the relative mix: how much of total retail sales is digitally transacted, and how quickly that mix is shifting by quarter. Reading the statistic (the ecommerce share of retail sales) helps calibrate inventory, ad budgets, and store labor. In the US, retail e-commerce continues to gain ground even as stores remain essential, and the cadence of change matters more than any single data point. This growth rate in online shopping is a glimpse of consumer behavior in the modern market.

    Below, we convert estimates and report highlights into an operator’s playbook, with simple explanations of what the rising percentage means for demand capture.

    Key highlights

    • Global online share crossed 20.1% in 2024 and is projected to reach 22.9% by 2028; the US tracks lower but is climbing.
    • US share path (online as a share of retail sales): 14.4% (2022) - 16.2% (2024) - 20.0% (2028).
    • Q2 2025 (Census, seasonally adjusted): e-commerce sales $304.2B vs total retail sales $1,865.4B → 16.3% share; ecommerce sales grew 5.3% YoY vs 3.9% for total.
    • Not adjusted: Q2 2025 online $292.9B; share 15.5%; underscores seasonal variation.
    • Amazon remains a significant force in the channel mix; the broader market still relies on stores for discovery and service.

    A simple overview of the share metric

    The share is the Think of it as the slice of sales transacted online. Tracking it by year and by quarter shows adoption and helps retailers compare performance to industry baselines.

    Global context: Rising digital mix

    Globally, the digital mix rises steadily as devices, access, and payments improve.

     

    • Global share by year: 18.6% (2022), 19.4% (2023), 20.1% (2024), 20.8% (2025), 21.5% (2026), 22.2% (2027), 22.9% (2028).

    • Interpretation: Roughly 0.7 percentage‑point increases per period, implying a predictable growth slope operators can plan around.

    • Implication: International portfolios should compare countries; China often runs ahead of the US mix, while other countries lag based on infrastructure and internet adoption.

    United States: Level and momentum

    The US runs below the global mix but is compounding upward.

     

    • US share by year: 14.4% (2022), 15.3% (2023), 16.2% (2024), 17.1% (2025), 18.0% (2026), 19.0% (2027), 20.0% (2028).

    • For every dollar spent in retail, roughly one‑sixth was online in 2024; by 2028, one in five purchases is expected to be online.

    • Keep store staffing calibrated for service and pickup while letting ecommerce sales handle repeat orders.

    Quarter‑by‑quarter: Reading census signals

    To manage the business, watch the each quarter. The Q2 reads blend demand and seasonal variation.

     

    • Second quarter 2025 (Census department release Aug 19, 2025): Estimated e-commerce $304.2B and total sales $1,865.4B (seasonally adjusted, not for price changes). Share 16.3%; ecommerce outpaced retail growth (ecommerce increased 5.3% YoY vs 3.9%).

    • Not adjusted: Q2 2025 share 15.5% as spring promotions lift online and store traffic differently.

    • First quarter 2025 to second quarter 2025 step‑up: e-commerce up 1.4% QoQ vs total up 0.4%. It is a signal of a mixed shift.

    • Same quarter prior year (Q2 2024): baseline for comparison in planning and bonus targets; use it to judge channel contribution.

    • Tactics: use seasonally adjusted series for weekly run‑rate expectations; use not‑adjusted for promo timing and tax‑refund effects in the first quarter and summer lull in the last quarter.

    What drives the share: Practical levers

    Mix moves are not abstract; they respond to levers that managers control.

     

    • Access: App performance, payment services, and delivery promise raise conversion.

    • Price & price changes: When fuel or freight spikes, basket thresholds and order fees can compress or expand the share.

    • Category sensitivity: Electronics and bulky categories migrate faster online; experiential and urgent items skew to the store.

    • Mobile: M-commerce matters for convenience; optimize session speed and buyer flows for online shoppers.

    Visual: Share trend, global vs US (2022–2028)

    Let us look at a dual‑line chart comparing the global and US mix, which is embedded below.

     

    retail-ecommerce-shareClosing remarks: Where the mix is headed next

    The practical takeaway is simple: measure the mix and manage it. As US retail e-commerce sales rise from mid‑teens percent toward one‑in‑five by 2028, winning operators will use EDI discipline, store‑linked fulfillment, and precise report reads to harvest margin. Treat e-commerce as a growth engine and stores as a trust engine; together they compound.

    Keep an eye on the second quarter and holiday cadence, keep your statistic pack current, and let the mix (not anecdotes) steer investments.

    Reference Sources

     

    The Logistics Automation Platform for E-commerce

    Momentum Leader - G2 Users Most Likely To Recommend - G2 Best Results Mid-Market - G2
    . window.disableHubSpotCookieBanner = true; function killNative() { var n = document.getElementById("hs-eu-cookie-confirmation"); if (n && n.parentNode) { n.parentNode.removeChild(n); return true; } return false; } killNative(); if (window.MutationObserver) { var mo = new MutationObserver(function () { killNative(); }); mo.observe(document.documentElement, { childList: true, subtree: true }); setTimeout(function () { mo.disconnect(); }, 15000); } var COOKIE = "cp_cookie_consent"; var EU = ["at","be","bg","hr","cy","cz","dk","ee","fi","fr","de","gr","hu","ie","it","lv","lt","lu","mt","nl","pl","pt","ro","sk","si","es","se","gb","uk","is","li","no","ch"]; function readCookie(nm) { var m = document.cookie.match(new RegExp("(?:^|; )" + nm + "=([^;]*)")); return m ? decodeURIComponent(m[1]) : null; } function writeCookie(nm, val, days) { var d = new Date(); d.setTime(d.getTime() + (days * 864e5)); document.cookie = nm + "=" + encodeURIComponent(val) + ";expires=" + d.toUTCString() + ";path=/;SameSite=Lax" + (location.protocol === "https:" ? ";Secure" : ""); } function stored() { var raw = readCookie(COOKIE); if (!raw) return null; try { var o = JSON.parse(raw); if (typeof o.analytics === "boolean") return o; } catch (e) {} return null; } function apply(state, revoke) { window._hsp = window._hsp || []; window._hsp.push(["setHubSpotConsent", { analytics: !!state.analytics, advertisement: !!state.advertisement, functionality: !!state.functionality }]); if (revoke && !state.analytics && !state.advertisement && !state.functionality) { window._hsp.push(["revokeCookieConsent"]); } window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "cp_cookie_consent", cp_consent_analytics: !!state.analytics, cp_consent_advertisement: !!state.advertisement, cp_consent_functionality: !!state.functionality }); document.documentElement.setAttribute("data-cp-consent", "set"); } function inScope(banner) { if (banner.getAttribute("data-scope") !== "eu_only") return true; var geo = (document.body.getAttribute("data-geo_location") || "").toLowerCase(); if (!geo) return true; return EU.indexOf(geo) !== -1; } function init(banner) { var days = parseInt(banner.getAttribute("data-days"), 10) || 180; var prefs = banner.querySelector("[data-cpcb-prefs]"); var compact = banner.querySelector('[data-cpcb-actions="compact"]'); var expanded = banner.querySelector('[data-cpcb-actions="expanded"]'); var toggles = banner.querySelectorAll("[data-cpcb-toggle]"); function show() { banner.hidden = false; requestAnimationFrame(function () { banner.classList.add("is-visible"); }); } function hide() { banner.classList.remove("is-visible"); setTimeout(function () { banner.hidden = true; }, 300); } function collapse() { if (!prefs) return; prefs.hidden = true; if (expanded) expanded.hidden = true; if (compact) compact.hidden = false; } function expand() { if (!prefs) return; prefs.hidden = false; if (compact) compact.hidden = true; if (expanded) expanded.hidden = false; } function paint(state) { Array.prototype.forEach.call(toggles, function (t) { var key = t.getAttribute("data-cpcb-toggle"); if (key === "necessary") { t.setAttribute("aria-checked", "true"); return; } t.setAttribute("aria-checked", state[key] ? "true" : "false"); }); } function fromToggles() { var state = { analytics: false, advertisement: false, functionality: false }; Array.prototype.forEach.call(toggles, function (t) { var key = t.getAttribute("data-cpcb-toggle"); if (key in state) state[key] = t.getAttribute("aria-checked") === "true"; }); return state; } function commit(state, revoke) { writeCookie(COOKIE, JSON.stringify({ analytics: !!state.analytics, advertisement: !!state.advertisement, functionality: !!state.functionality, v: 1, ts: Date.now() }), days); apply(state, revoke); hide(); } Array.prototype.forEach.call(toggles, function (t) { if (t.disabled) return; t.addEventListener("click", function () { t.setAttribute("aria-checked", t.getAttribute("aria-checked") === "true" ? "false" : "true"); }); }); banner.addEventListener("click", function (e) { if (!e.target || !e.target.closest) return; var btn = e.target.closest("[data-cpcb-action]"); if (!btn) return; var action = btn.getAttribute("data-cpcb-action"); if (action === "customise") { expand(); return; } if (action === "accept") { commit({ analytics: true, advertisement: true, functionality: true }, false); return; } if (action === "reject") { commit({ analytics: false, advertisement: false, functionality: false }, true); return; } if (action === "save") { commit(fromToggles(), true); return; } }); var existing = stored(); if (existing) { apply(existing, false); paint(existing); collapse(); } else if (!inScope(banner)) { // Out of the consent-required region: implied consent, banner never shown. commitSilently(); } else { collapse(); show(); } function commitSilently() { var all = { analytics: true, advertisement: true, functionality: true }; writeCookie(COOKIE, JSON.stringify({ analytics: true, advertisement: true, functionality: true, v: 1, ts: Date.now(), implied: true }), days); apply(all, false); } // Public re-open hook for a footer "Cookie settings" link. window.cpCookieBanner = window.cpCookieBanner || {}; window.cpCookieBanner.open = function () { var s = stored() || { analytics: false, advertisement: false, functionality: false }; paint(s); expand(); show(); }; } function boot() { var banners = document.querySelectorAll("[data-cpcb]"); if (!banners.length) return; init(banners[0]); document.addEventListener("click", function (e) { if (!e.target || !e.target.closest) return; var trigger = e.target.closest("[data-cp-cookie-settings]"); if (!trigger) return; e.preventDefault(); if (window.cpCookieBanner && window.cpCookieBanner.open) window.cpCookieBanner.open(); }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", boot); } else { boot(); } })();