Marketpalce App: How I Built an Excel to JSON Custom Field for Sitecore XM Cloud/ SitecoreAI
March 8, 2026 • Nona Dzhurkova • 6 min read

Marketpalce App: How I Built an Excel to JSON Custom Field for Sitecore XM Cloud/ SitecoreAI

Recently, I had an interesting requirement for a Sitecore XM Cloud project. The client wanted a way to create a table component with different data inside across different sites. The data can vary from a simple list to being filled with technical information. The requirements also stated that there should be a possibility to dynamically decide which column becomes a filter, and for larger row counts, to have a search option.

The data will be provided in Excel format. But as you probably know, uploading a file to the Media Library, reading it from there, and parsing it into a usable format can be a performance killer.

So, to summarize:

The content editors needed a way to upload Excel files and store the data as a table in Sitecore. Instead of building backend endpoints to parse the files, I decided to build a Sitecore Marketplace application that acts as a custom field.

The goal was simple: editors click on a field, an app opens, they upload an Excel file, and the app converts it to a JSON array. This JSON is then saved directly into the Sitecore field as text.

Here is how I built it and how you can do something similar.

You can also watch a demonstration of the app in action here:

1. The Idea and Architecture

The app runs as a Custom Field extension inside XM Cloud / Sitecore AI. For more information on how to do this, refer to the Sitecore documentation on extension points. When you configure it, the app loads in an iframe and talks to Sitecore using the Marketplace SDK.

The process looks like this:

  1. Load: The app initializes the SDK and gets the current value of the field.
  2. Upload: The editor selects an Excel or CSV file. This happens only in the browser—no file is sent to a server.
  3. Convert: The app reads the file. The first row becomes the headers, and the other rows become JSON objects.
  4. Preview: A dynamic table shows the data so the editor can verify it.
  5. Save: The app turns the JSON into a string and saves it back to Sitecore.

By doing everything in the browser using the SheetJS (xlsx) library, it is fast and secure.

2. Reading Excel in the Browser

To make sure files never leave the user's browser, I used SheetJS. First, we need to read the file and get the sheet names. For CSV files, it is just plain text, but for Excel files, we read them as an ArrayBuffer.

Here is a simplified version of how I get the sheet names:

export async function getExcelSheetNames(file: File): Promise<string[]> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    const isCSV = file.name.toLowerCase().endsWith(".csv");

    reader.onload = (e) => {
      try {
        let workbook;
        if (isCSV) {
          const text = e.target?.result as string;
          workbook = XLSX.read(text, { type: "string" });
        } else {
          const data = new Uint8Array(e.target?.result as ArrayBuffer);
          workbook = XLSX.read(data, { type: "array" });
        }
        resolve(workbook.SheetNames);
      } catch (error) {
        reject(error);
      }
    };

    reader.onerror = () => reject(new Error("Failed to read file"));

    if (isCSV) {
      reader.readAsText(file);
    } else {
      reader.readAsArrayBuffer(file);
    }
  });
}

After the user selects a sheet, the app converts it to JSON. It creates an array of objects where the keys match the column names from the first row.

3. The Dynamic Preview Table

Before saving, it is always a good idea to let the editor see what they are about to save. I built a PreviewTable component in React.

Because different Excel files have different columns, the table needs to be dynamic. It takes the keys from the first JSON object and uses them as table headers.

export function PreviewTable({ jsonData, currentPage, rowsPerPage }: PreviewTableProps) {
  if (!jsonData?.length) return null;

  const startIndex = (currentPage - 1) * rowsPerPage;
  const endIndex = startIndex + rowsPerPage;
  const displayedRows = jsonData.slice(startIndex, endIndex);

  return (
    <TableContainer>
      <Table>
        <Thead>
          <Tr>
            <Th>#</Th>
            {Object.keys(jsonData[0]).map((key) => (
              <Th key={key}>{key}</Th>
            ))}
          </Tr>
        </Thead>
        <Tbody>
          {displayedRows.map((row, rowIndex) => (
            <Tr key={rowIndex}>
              <Td>{startIndex + rowIndex + 1}</Td>
              {Object.keys(jsonData[0]).map((key) => (
                <Td key={key}>{row[key] != null ? String(row[key]) : ""}</Td>
              ))}
            </Tr>
          ))}
        </Tbody>
      </Table>
    </TableContainer>
  );
}

It includes pagination so the browser doesn't freeze if the editor uploads a massive spreadsheet.

4. Connecting to Sitecore Marketplace SDK

The most important part is how the app talks to Sitecore. It uses the useMarketplaceClient hook to initialize the connection.

When the page loads, it tries to read the existing field value so the editor can see what is currently saved:

useEffect(() => {
  if (!error && isInitialized && client) {
    if (typeof (client as any).getValue === "function") {
      (client as any)
        .getValue()
        .then((currentValue: string) => {
          if (currentValue) {
            try {
              const parsed = JSON.parse(currentValue);
              setJsonData(Array.isArray(parsed) ? parsed : [parsed]);
            } catch {
              // Not valid JSON, just ignore it
            }
          }
        });
    }
  }
}, [client, error, isInitialized]);

When the editor clicks the Save button, we stringify the JSON array, set the value in the host, and close the app panel:

const jsonString = JSON.stringify(jsonData, null, 2);
client.setValue(jsonString);

// Wait a bit, show success message, and close the app
setTimeout(() => {
  if (client) client.closeApp();
}, 800);

5. Rendering the JSON on the Website

Once the data is saved in Sitecore, it is just a normal string field (like Multi-Line Text). On the front-end (for example, in a Next.js rendering), you just read the field, parse the JSON, and render your HTML table.

const raw = item.fields.MyTableField?.value;
const rows = raw ? JSON.parse(raw) : [];

return (
  <table>
    <thead>
      <tr>
        {rows[0] && Object.keys(rows[0]).map((key) => <th key={key}>{key}</th>)}
      </tr>
    </thead>
    <tbody>
      {rows.map((row, i) => (
        <tr key={i}>
          {rows[0] && Object.keys(rows[0]).map((key) => (
            <td key={key}>{row[key]}</td>
          ))}
        </tr>
      ))}
    </tbody>
  </table>
);

Final Thoughts

Building a custom field app for XM Cloud is quite straightforward once you understand the Marketplace SDK. By doing the Excel parsing completely on the client side, we avoid backend complexity, file upload limits, and security concerns.

This was the first marketplace app I saw go live. It is a simple but clever idea with a lot of potential. The idea first came about around Sitecore Symposium, and just a few days later, it became a real, functioning app. It just took me a bit of time to finally sit down and write about it! Most importantly, it saves a huge amount of time for content authors and makes their day-to-day work much easier!

Featured Blogs