Shopify UI Extensions

repository·2026-10-rc·Indexed 43 days ago

https://github.com/shopify/ui-extensions

Public definition of Shopify's UI extension API for building strongly-typed, sandboxed UI modules. Includes the @shopify/ui-extensions-tester for testing extensions with Vitest, featuring mock Shopify globals, fetch and navigation mocks, and surface-specific helpers for Admin and Checkout targets (API version 2025-10 or later).

Tokens
16.5K
Snippets
44
Records
82
Agent score
56%

What's inside Shopify UI Extensions

  1. What are @shopify/ui-extensions and which surfaces do they support?

    2026-10-rc

    The @shopify/ui-extensions package provides the public type definitions and utilities required to build Shopify UI extensions.

    Extensions are built using a unified underlying technology (Remote DOM) and share a common set of core components (such as s-block, s-button, and s-text-field) and capabilities (such as session tokens and direct API access).

    While the core technology is shared, APIs and components are partitioned by surface to ensure developers only see what is relevant to their specific context. Supported surfaces include:

    • Admin
    • Customer account
    • POS (Point of Sale)
    • Checkout
  2. How NPM deployment is triggered

    2026-10-rc

    NPM deployment is automated via GitHub Actions. When changeset files are detected on a stable version or RC branch, a GitHub action creates a PR that:

    1. Merges all unpublished changesets into a single changelog.
    2. Increments the version numbers of the updated packages.

    Once this PR is reviewed and merged by a member of the UI Extension Stewards GitHub team, the new package versions are automatically published to NPM. Packages are tagged with their API version, allowing users to install specific versions via:

    yarn add @shopify/ui-extensions@2023-01
  3. Test extension code that relies on signals

    2026-10-rc

    The mock Shopify API does not implement working signals. To test logic that depends on signal changes (like shopify.lines.value), you must test the interaction and the state change separately:

    1. Test the interaction: Trigger the event (e.g., a button click) and verify that the correct mutation function was called with the expected arguments.
    2. Test the rendering: Manually update the mock signal value (e.g., extension.shopify.lines.value = [...]) and call extension.render() to verify the UI reflects the new state.
    import {createCartLine} from '@shopify/ui-extensions-tester/checkout';
    
    // 1. Test the interaction
    test('increments cart line quantity on click', async () => {
      const line = createCartLine();
      extension.shopify.lines.value = [line];
      const applyCartLinesChange = vi.spyOn(extension.shopify, 'applyCartLinesChange');
    
      await extension.render();
    
      const button = document.body.querySelector('s-button')!;
      fireEvent.click(button);
    
      await waitFor(() => {
        expect(applyCartLinesChange).toHaveBeenCalledWith({
          type: 'updateCartLine',
          id: line.id,
          quantity: line.quantity + 1,
        });
      });
    });
    
    // 2. Test the rendering of the new state
    test('it renders the cart line quantity', async () => {
      extension.shopify.lines.value = [createCartLine({quantity: 2})];
    
      await extension.render();
    
      const text = document.body.querySelector('s-text')!;
      expect(text.textContent).toContain('2');
    });
  4. Set up the extension test harness

    2026-10-rc

    You must create a harness to manage the lifecycle of the shopify global and the DOM. There are two ways to do this:

    Use setUpExtension with the using keyword for automatic setup and teardown via Explicit Resource Management.

    2. Classic Setup

    Use getExtension and manually manage the lifecycle with Vitest's beforeEach and afterEach hooks. setUp() creates the mock shopify global, and tearDown() clears the DOM and the global.

    // Option 1: Automatic teardown (Node 22+)
    import {setUpExtension} from '@shopify/ui-extensions-tester';
    
    test('rendering the extension', async () => {
      using extension = setUpExtension('purchase.checkout.block.render');
      await extension.render();
    });
    
    // Option 2: Manual lifecycle
    import {getExtension} from '@shopify/ui-extensions-tester';
    import {beforeEach, afterEach} from 'vitest';
    
    const extension = getExtension('purchase.checkout.block.render');
    
    beforeEach(() => extension.setUp());
    afterEach(() => extension.tearDown());
  5. How extension points and registration work

    2026-10-rc

    An extension system is constructed by mapping specific "extension points" (identified by unique string names) to third-party code.

    The Registration Lifecycle

    1. Configuration: You define your target extension point in a shopify.ui.extension.toml file.
    2. Registration: When your extension script is loaded, Shopify automatically calls shopify.extend() to register your code for that specific extension point.
    3. Execution: The host application calls a run() function, passing in the public API (such as data and a UI root) which is then forwarded to your extension's callback.

    Implementation Pattern

    In your extension code, you typically use a helper function (like extensions()) as a default export. This implicitly handles the registration and execution logic.

  6. How UI Extensions work technically

    2026-10-rc

    UI Extensions are built on remote-ui, an open-source technology that uses a message-passing system to communicate between the extension and the host application.

    Key technical characteristics:

    • Component Model: Extensions render components that are represented as minimal JavaScript objects rather than actual DOM elements. These objects are sent to the host application, which renders them into native UI.
    • Decoupled Rendering: Because extensions do not render to the DOM, Shopify can update or change the underlying native components without requiring extension developers to update their code. This also enables seamless merchant theming (e.g., in Shopify Checkout).
    • Framework Bindings: remote-ui provides integrations for frameworks like React and Vue, allowing developers to use familiar syntax while the underlying communication remains via remote-ui.
  7. What are UI extensions?

    2026-10-rc

    A UI extension is a JavaScript-based module that hooks into client-side behaviors on Shopify's first-party UI surfaces. Extensions are configured via a shopify.extension.toml file and consist of three core properties:

    • name: The name presented to merchants.
    • target: A string identifier describing the specific UI surface and responsibility where the extension should inject itself (e.g., purchase.checkout.cart-line-item.render-after).
    • module: The local JavaScript module that contains the logic to render the UI.

    UI extensions are built on remote-dom, which enables rendering native UI elements within a safe sandbox. Each target provides a specific set of available UI Components (and their properties) and imperative APIs (for reading/writing data relevant to that specific surface).

  8. Related components for text and headings

    2026-10-rc

    When building layouts with headings, you may need these related components to manage hierarchy and text styling:

    • HeadingGroup: Use this to control the heading level (e.g., H1, H2, H3) for all nested headings within the group.
    • Text: Use this to provide semantic value and specific styling for individual pieces of text content.
    • TextBlock: Use this to render blocks of text that should occupy the full available width, such as paragraphs.
  9. How UI extension versioning works

    2026-10-rc

    Shopify UI extensions use a versioned API system. New stable versions are released quarterly at the beginning of each quarter (months 01, 04, 07, or 10) using the YYYY-MM format.

    Release Lifecycle

    • Stable Versions (YYYY-MM): These are the official releases. Only bugfixes (patch changes) should be merged into stable branches.
    • Release Candidates (YYYY-MM-rc): Created alongside a new stable version to preview upcoming features. Active development happens here. Packages published from these branches use a {{SEMVER}}-rc.{{VERSION}} format (e.g., 2025.10.0-rc.5).
    • Unstable (Deprecated): The unstable branch is frozen at 2025-07 and should not be used for new work. Always branch off the latest RC branch instead.