1. Quick starts

Quick start (manual)

Version: 0.4

This guide describes how to start developing for the Sitecore Marketplace locally by connecting a JavaScript or TypeScript app to Sitecore using the Marketplace SDK. This guide is for building a client-side app with built-in authorization.

In this guide, you:

Prerequisites

Check that you have the following before getting started:

  • A Marketplace app installed in your Sitecore Cloud Portal organization.

  • Node.js 16 or later. Check your installed version by using the node --version command.

  • npm 10 or later. Check your installed version by using the npm --version command.

    Note

    We strongly recommend that you also install Blok in your app. Blok is the Sitecore product design system, and installing it is the easiest way you can give your app the Sitecore look and feel.

    Matching the Sitecore look and feel is required for public Marketplace apps and strongly recommended for custom Marketplace apps. This is to ensure a consistent user experience for the Sitecore product user.

Create an app

If you don't already have an app, create a JavaScript or TypeScript one.

To create a new JavaScript or TypeScript app, run the following command in your terminal:

npm create vite@latest

Install the packages

To install the Marketplace SDK packages in your app, run the following commands:

npm install @sitecore-marketplace-sdk/client # Required for all Marketplace apps
npm install @sitecore-marketplace-sdk/xmc # Required only if your app uses SitecoreAI APIs
npm install @sitecore-marketplace-sdk/ai # Required only if your app uses AI skills APIs

Initialize the SDK

To initialize the Marketplace SDK:

  1. In your code editor, in the src folder of your app, create a hook, for example, /utils/hooks/useMarketplaceClient.ts.

  2. Paste the following code into your new hook:

    React and Next.js

    // utils/hooks/useMarketplaceClient.ts
    
    import { ClientSDK } from "@sitecore-marketplace-sdk/client";
    import { useEffect, useState, useCallback, useMemo, useRef } from "react";
    
    export interface MarketplaceClientState {
      client: ClientSDK | null;
      error: Error | null;
      isLoading: boolean;
      isInitialized: boolean;
    }
    
    export interface UseMarketplaceClientOptions {
      /**
       * Number of retry attempts when initialization fails
       * @default 3
       */
      retryAttempts?: number;
    
      /**
       * Delay between retry attempts in milliseconds
       * @default 1000
       */
      retryDelay?: number;
    
      /**
       * Whether to automatically initialize the client
       * @default true
       */
      autoInit?: boolean;
    }
    
    const DEFAULT_OPTIONS: Required<UseMarketplaceClientOptions> = {
      retryAttempts: 3,
      retryDelay: 1000,
      autoInit: true,
    };
    
    let client: ClientSDK | undefined = undefined;
    
    async function getMarketplaceClient() {
      if (client) {
        return client;
      }
    
      const config = {
        target: window.parent,
      };
    
      client = await ClientSDK.init(config);
      return client;
    }
    
    export function useMarketplaceClient(options: UseMarketplaceClientOptions = {}) {
      // Memoize the options to prevent unnecessary re-renders
      const opts = useMemo(() => ({ ...DEFAULT_OPTIONS, ...options }), [
        options,
      ]);
    
      const [state, setState] = useState<MarketplaceClientState>({
        client: null,
        error: null,
        isLoading: false,
        isInitialized: false,
      });
    
      // Use ref to track if we're currently initializing to prevent race conditions
      const isInitializingRef = useRef(false);
    
      const initializeClient = useCallback(async (attempt = 1): Promise<void> => {
        // Use functional state update to check current state without dependencies
        let shouldProceed = false;
        setState(prev => {
          if (prev.isLoading || prev.isInitialized || isInitializingRef.current) {
            return prev;
          }
          shouldProceed = true;
          isInitializingRef.current = true;
          return { ...prev, isLoading: true, error: null };
        });
    
        if (!shouldProceed) return;
    
        try {
          const client = await getMarketplaceClient();
          setState({
            client,
            error: null,
            isLoading: false,
            isInitialized: true,
          });
        } catch (error) {
          if (attempt < opts.retryAttempts) {
            await new Promise(resolve => setTimeout(resolve, opts.retryDelay));
            return initializeClient(attempt + 1);
          }
    
          setState({
            client: null,
            error: error instanceof Error ? error : new Error('Failed to initialize MarketplaceClient'),
            isLoading: false,
            isInitialized: false,
          });
        } finally {
          isInitializingRef.current = false;
        }
      }, [opts.retryAttempts, opts.retryDelay]); // Removed state dependencies
    
      useEffect(() => {
        if (opts.autoInit) {
          initializeClient();
        }
    
        return () => {
          isInitializingRef.current = false;
          setState({
            client: null,
            error: null,
            isLoading: false,
            isInitialized: false,
          });
        };
      }, [opts.autoInit, initializeClient]);
    
      // Memoize the return value to prevent object recreation on every render
      return useMemo(() => ({
        ...state,
        initialize: initializeClient,
      }), [state, initializeClient]);
    }

    This script manages the initialization and state of the SDK and sets up communication with window.parent so the app can run in an iframe.

  3. In your app's main page, such as App.tsx or pages/index.tsx, initialize the Marketplace client in an Effect Hook and create your first query:

    React

    // src/App.tsx
    
    import { useState, useEffect } from "react";
    import type { ApplicationContext } from "@sitecore-marketplace-sdk/client";
    import { useMarketplaceClient } from "./utils/hooks/useMarketplaceClient";
    
    export default function App() {
      const { client, error, isInitialized } = useMarketplaceClient();
      const [appContext, setAppContext] = useState<ApplicationContext>();
    
      useEffect(() => {
        if (!error && isInitialized && client) {
          console.log("Marketplace client initialized successfully.");
    
          // Make a query to retrieve the application context
          client.query("application.context")
            .then((res) => {
              console.log("Success retrieving application.context:", res.data);
              setAppContext(res.data);
            })
            .catch((error) => {
              console.error("Error retrieving application.context:", error);
            });
        } else if (error) {
          console.error("Error initializing Marketplace client:", error);
        }
      }, [client, error, isInitialized]);
    
      if (!appContext) {
        return null;
      }
    
      return (
        <>
          <h1>Welcome to {appContext?.name}</h1>
        </>
      );
    }

    Next.js (Pages Router)

    // src/pages/index.tsx
    
    import { useState, useEffect } from "react";
    import type { ApplicationContext } from "@sitecore-marketplace-sdk/client";
    import { useMarketplaceClient } from "@/utils/hooks/useMarketplaceClient";
    
    export default function App() {
      const { client, error, isInitialized } = useMarketplaceClient();
      const [appContext, setAppContext] = useState<ApplicationContext>();
    
      useEffect(() => {
        if (!error && isInitialized && client) {
          console.log("Marketplace client initialized successfully.");
    
          // Make a query to retrieve the application context
          client.query("application.context")
            .then((res) => {
              console.log("Success retrieving application.context:", res.data);
              setAppContext(res.data);
            })
            .catch((error) => {
              console.error("Error retrieving application.context:", error);
            });
        } else if (error) {
          console.error("Error initializing Marketplace client:", error);
        }
      }, [client, error, isInitialized]);
    
      if (!appContext) {
        return null;
      }
    
      return (
        <>
          <h1>Welcome to {appContext?.name}</h1>
        </>
      );
    }

    Next.js (App Router)

    // src/app/page.tsx
    
    "use client";
    
    import { useState, useEffect } from "react";
    import type { ApplicationContext } from "@sitecore-marketplace-sdk/client";
    import { useMarketplaceClient } from "@/utils/hooks/useMarketplaceClient";
    
    export default function Home() {
      const { client, error, isInitialized } = useMarketplaceClient();
      const [appContext, setAppContext] = useState<ApplicationContext>();
    
      useEffect(() => {
        if (!error && isInitialized && client) {
          console.log("Marketplace client initialized successfully.");
    
          // Make a query to retrieve the application context
          client.query("application.context")
            .then((res) => {
              console.log("Success retrieving application.context:", res.data);
              setAppContext(res.data);
            })
            .catch((error) => {
              console.error("Error retrieving application.context:", error);
            });
        } else if (error) {
          console.error("Error initializing Marketplace client:", error);
        }
      }, [client, error, isInitialized]);
    
      if (!appContext) {
        return null;
      }
    
      return (
        <>
          <h1>Welcome to {appContext?.name}</h1>
        </>
      );
    }

    This script:

    • Uses an Effect Hook to check if the Marketplace client is initialized.
    • Makes a query to retrieve details about your Marketplace app: client.query("application.context")
    • Extracts the app details from the API response and stores it in state: setAppContext(res.data);
    • Displays the app name in the user interface: <h1>Welcome to {appContext?.name}</h1>
  4. Start your app by entering the following command in your terminal:

    npm run dev
    Note

    Start your app on the same localhost address that you specified during app configuration.

Open your app in Sitecore

After initializing the SDK, you open your Marketplace app in a Sitecore extension point you selected for it during app configuration.

To open your app in Sitecore:

  1. In your web browser, in the Cloud Portal, find and open your Marketplace app in one of its extension points.

    Your app now appears in Sitecore and the two are securely communicating.

  2. On the same page, open your console to find the logs included in the SDK initialization code.

    Note

    During development, trace console logs in your web browser's console in the Sitecore extension point, not on your app's localhost address.

    Similarly, always preview your app in the Sitecore extension point. Any functionality that requires communication with Sitecore, such as getting application details and calling SitecoreAI APIs, only works in the extension points.

Next steps

You've now displayed your app in Sitecore, set up communication between the two, and made your first query. Next, you can:

If you have suggestions for improving this article, let us know!