How to Update Sitecore XM Cloud Fields from a Next.js Application Using the Authoring API

Learn how to integrate Sitecore XM Cloud's Authoring API with your Next.js application to programmatically update content fields and workflows in real-time

August 27, 2025

By Sohrab Saboori

Why Use the Sitecore XM Cloud Authoring API for Field Updates in Next.js

When building headless applications with Sitecore XM Cloud, you often need to update content fields programmatically — for workflows, approvals, or dynamic data sync.

This guide shows you how to implement field updates from a Next.js application using GraphQL mutations against the Authoring API.

Typical use cases:

  • Real-time synchronization with external systems
  • Dynamic updates based on user actions
  • Building custom admin interfaces

What You Need Before Using the Sitecore XM Cloud Authoring API

Before starting, ensure you have:

  • Sitecore XM Cloud environment with API access
  • OAuth credentials (Client ID, Client Secret) from Deploy → Credentials → Automation
  • Next.js application with API routes
  • Access token management already implemented
  • Basic understanding of GraphQL

Understanding the Right API Endpoint

Endpoint Purpose Notes
/sitecore/api/graph/edge Read-only queries for rendering Use in frontend apps
/sitecore/api/authoring/graphql/v1 Mutations & item updates Use for updating fields
/api/v1/pages Page-level operations only Not for regular items

To update fields, always use the Authoring endpoint:

https://your-instance.sitecorecloud.io/sitecore/api/authoring/graphql/v1

Setting Up XM Cloud API Credentials

Generate API Credentials

  1. Log into Sitecore XM Cloud Deploy portal
  2. Navigate to CredentialsEnvironment tab
  3. Click “Create credentials” and select “Automation”

  4. Configure: • Label: “API Integration” • Location: “Edge administration”

You’ll receive:

CLIENT_ID=your_client_id_here
CLIENT_SECRET=your_client_secret_here

Environment Configuration

Add to your .env.local:

SITECORE_CLIENT_ID=your_client_id_here
SITECORE_CLIENT_SECRET=your_client_secret_here
SITECORE_AUTHORING_ENDPOINT=https://your-instance.sitecorecloud.io/sitecore/api/authoring/graphql/v1
SITECORE_AUTH_ENDPOINT=https://auth.sitecorecloud.io/oauth/token
SITECORE_AUDIENCE=https://api.sitecorecloud.io

Important: Use the authoring endpoint for mutations, not the edge endpoint.

Implementing Field Updates with Sitecore XM Cloud Authoring API

1. Sitecore Update Service

Create lib/sitecore-update.ts: This service is a reusable helper class that wraps the Authoring API mutation logic. It keeps your GraphQL update queries in one place so you can call them from anywhere in your app.


interface UpdateResult {
 success: boolean;
 message?: string;
 error?: string;
}
class SitecoreUpdateService {
  private authoringEndpoint: string;
  constructor() {
    this.authoringEndpoint = process.env.SITECORE_AUTHORING_ENDPOINT || "";
  }
  /**
   * Update a single field value
   */
  async updateField(
    itemId: string,
    fieldName: string,
    fieldValue: string,
    accessToken: string,
    language: string = "en"
  ): Promise<UpdateResult> {
    try {
      const mutation = `
        mutation UpdateField($id: ID!, $lang: String!, $fieldName: String!, $value: String!) {
          updateItem(input: {
            itemId: $id,
            language: $lang,
            fields: [{ name: $fieldName, value: $value }]
          }) {
            item {
              name
              path
            }
          }
        }
      `;
      const variables = {
        id: itemId,
        lang: language,
        fieldName: fieldName,
        value: fieldValue,
      };
      const response = await fetch(this.authoringEndpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${accessToken}`,
        },
        body: JSON.stringify({
          query: mutation,
          variables,
          operationName: "UpdateField",
        }),
      });
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      const result = await response.json();
      if (result.errors) {
        throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
      }
      return {
        success: true,
        message: `Field "${fieldName}" updated successfully`,
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }
  /**
   * Update multiple fields in one request
   */
  async updateMultipleFields(
    itemId: string,
    fields: Array<{ name: string; value: string }>,
    accessToken: string,
    language: string = "en"
  ): Promise<UpdateResult> {
    try {
      const mutation = `
        mutation UpdateMultipleFields($id: ID!, $lang: String!, $fields: [ItemFieldInput!]!) {
          updateItem(input: {
            itemId: $id,
            language: $lang,
            fields: $fields
          }) {
            item {
              name
              path
            }
          }
        }
      `;
      const variables = {
        id: itemId,
        lang: language,
        fields: fields,
      };
      const response = await fetch(this.authoringEndpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${accessToken}`,
        },
        body: JSON.stringify({
          query: mutation,
          variables,
          operationName: "UpdateMultipleFields",
        }),
      });
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      const result = await response.json();
      if (result.errors) {
        throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
      }
      return {
        success: true,
        message: `Successfully updated ${fields.length} fields`,
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }
}
export const sitecoreUpdateService = new SitecoreUpdateService();

2. Next.js API Route

Create pages/api/sitecore/update-field.ts: This API route acts as a secure server-side proxy between your Next.js frontend and Sitecore. It ensures credentials stay hidden and handles requests from your React components.

import { NextApiRequest, NextApiResponse } from "next";
import { sitecoreUpdateService } from "../../../lib/sitecore-update";
import { getAccessToken } from "../../../lib/auth"; // Your auth implementation
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== "POST") {
    return res.status(405).json({ error: "Method not allowed" });
  }
  try {
    const { itemId, fieldName, fieldValue, language = "en" } = req.body;
    // Validate required parameters
    if (!itemId || !fieldName || fieldValue === undefined) {
      return res.status(400).json({
        error: "Missing required parameters",
        required: ["itemId", "fieldName", "fieldValue"],
      });
    }
    // Get access token (implement this based on your auth setup)
    const accessToken = await getAccessToken();
    // Update the field
    const result = await sitecoreUpdateService.updateField(
      itemId,
      fieldName,
      fieldValue,
      accessToken,
      language
    );
    if (result.success) {
      return res.status(200).json({
        success: true,
        message: result.message,
        data: { itemId, fieldName, fieldValue, language },
      });
    } else {
      return res.status(500).json({
        success: false,
        error: result.error,
      });
    }
  } catch (error) {
    console.error("Update field error:", error);
    return res.status(500).json({
      success: false,
      error: "Internal server error",
    });
  }
}

3. React Hook for Updates

Create hooks/use-sitecore-field-update.ts: This custom React hook provides a simple interface for UI components to trigger Sitecore field updates. It handles loading state and abstracts the API call so your components stay clean.

import { useState } from "react";
interface UpdateParams {
  itemId: string;
  fieldName: string;
  fieldValue: string;
  language?: string;
}
interface UpdateResult {
  success: boolean;
  message?: string;
  error?: string;
}
export const useSitecoreFieldUpdate = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [lastResult, setLastResult] = useState<UpdateResult | null>(null);
  const updateField = async (params: UpdateParams): Promise<UpdateResult> => {
    setIsLoading(true);
    setLastResult(null);
    try {
      const response = await fetch("/api/sitecore/update-field", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(params),
      });
      const result: UpdateResult = await response.json();
      setLastResult(result);
      return result;
    } catch (error) {
      const errorResult: UpdateResult = {
        success: false,
        error: error instanceof Error ? error.message : "Network error",
      };
      setLastResult(errorResult);
      return errorResult;
    } finally {
      setIsLoading(false);
    }
  };
  const updateMultipleFields = async (
    itemId: string,
    fields: Array<{ name: string; value: string }>,
    language?: string
  ): Promise<UpdateResult> => {
    setIsLoading(true);
    setLastResult(null);
    try {
      const response = await fetch("/api/sitecore/update-multiple-fields", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ itemId, fields, language }),
      });
      const result: UpdateResult = await response.json();
      setLastResult(result);
      return result;
    } catch (error) {
      const errorResult: UpdateResult = {
        success: false,
        error: error instanceof Error ? error.message : "Network error",
      };
      setLastResult(errorResult);
      return errorResult;
    } finally {
      setIsLoading(false);
    }
  };
  return {
    updateField,
    updateMultipleFields,
    isLoading,
    lastResult,
  };
};

Real-World Examples of Updating Fields in Sitecore XM Cloud

1. Update Approval Status

// Update approval status field
const updateApprovalStatus = async (itemId: string, status: string) => {
  const result = await updateField({
    itemId: itemId,
    fieldName: "approvalStatus",
    fieldValue: status, // "Pending", "In Progress", "Approved"
  });
  if (result.success) {
    console.log("Status updated successfully");
  } else {
    console.error("Update failed:", result.error);
  }
};

2. Update Multiple Fields at Once

// Update multiple fields in a single request
const updateContentFields = async (itemId: string) => {
  const fields = [
    { name: "title", value: "New Title" },
    { name: "description", value: "Updated description" },
    { name: "lastModified", value: new Date().toISOString() },
  ];
  const result = await updateMultipleFields(itemId, fields);
  if (result.success) {
    console.log(`Updated ${fields.length} fields`);
  }
};

3. UI Test Page Example

export default function SitecoreStatusTest() {
  const { updateField } = useSitecoreFieldUpdate();
  const [status, setStatus] = useState("Pending");
  return (
    <button
      onClick={() => updateField("8F128029CDC84C6DB31D9A9AE09A91E9", "approvalItemStatus", "Approved")}
    >
      Approve
    </button>
  );
}

Common Errors When Using the Authoring API (and How to Fix Them)

  • Schema is not configured for mutations → You’re calling the Edge endpoint. Use /authoring/graphql/v1.
  • Variable "$id" of type "String!" … expecting "ID!" → Change mutation variable to $id: ID!.
  • 401 Unauthorized → Token expired or wrong audience.
  • 404 Not Found → Double-check your endpoint URL.

Validation helpers:

const validateItemId = (id: string) => /^[0-9A-F-]{36}$/i.test(id);

Optimizing Performance When Updating Fields in Sitecore XM Cloud with the Authoring API

  • Batch Updates: Use updateMultipleFields instead of calling updateField repeatedly.
  • Token Caching: Cache access tokens to avoid extra OAuth requests.
  • Retry Logic: Implement exponential backoff for transient errors.

Final Thoughts On Using Sitecore XM Cloud Authoring API with Next.js

The Sitecore XM Cloud Authoring API is the backbone for enabling write operations in a headless setup. While the Edge GraphQL endpoint powers fast read-only queries for front-end rendering, the Authoring API lets developers go further — updating fields, triggering workflow changes, and synchronizing external systems.

By integrating it into your Next.js application:

  • You separate content mutations from your rendering layer.
  • You keep API credentials secure by routing through server-side API routes.
  • You can build real-time UI tools (approval dashboards, content editors, custom workflows).
  • You unlock automation — programmatically keeping Sitecore items in sync with other systems.

The key lesson: always use the Authoring API for mutations, Edge for queries, and never expose secrets to the client side. With a solid service layer and clear error handling, your Next.js apps can confidently manage Sitecore content in real time.

This opens up endless possibilities for building custom admin panels, workflow tools, or real-time integrations — all while staying headless and future-proof.

Photo of Fishtank employee Sohrab Saboori

Sohrab Saboori

Senior Full-Stack Developer

Sohrab is a Senior Front-End Developer with extensive experience in React, Next.js, JavaScript, and TypeScript. Sohrab is committed to delivering outstanding digital solutions that not only meet but exceed clients' expectations. His expertise in building scalable and efficient web applications, responsive websites, and e-commerce platforms is unparalleled. Sohrab has a keen eye for detail and a passion for creating seamless user experiences. He is a problem-solver at heart and enjoys working with clients to find innovative solutions to their digital needs. When he's not coding, you can find him lifting weights at the gym, pounding the pavement on the run, exploring the great outdoors, or trying new restaurants and cuisines. Sohrab believes in a healthy and balanced lifestyle and finds that these activities help fuel his creativity and problem-solving skills.