Insights

Implementing Custom Script Into Your XM Cloud Solution

Learn how to provide your Sitecore authors the ability to add and remove third party scripts onto any pages they desire

Empowering Sitecore Authors to Add Custom Script

In this guide, we'll focus on giving your Sitecore authors the ability to add custom scripts to their pages. In this example, we will leverage SurveyMonkey as the exemplar script, but the technique outlined here is versatile and adaptable, allowing seamless integration with any third-party scripts.

The Challenge: Implementing a way for the client to be able to add a custom script tag on a per page basis.

Warning - You should have a robust CSP enabled and restrict access to admin user roles to prevent your content authors from inserting potentially malicious code.

1. Creating A New Sitecore Component

1a. Next.js: Creating script.tsx

The first thing we want to do is create a new .tsx file in your solution. Feel free to name it anything you like, but because I’m using Survey Monkey as the example, I will name mine SurveyMonkeyScript.ts

A list of CMS script files, including 'SurveyMonkeyScript.tsx.'

After creating the file, and add the basic Sitecore and React imports. We can add a simple “Script Box loading...” return so we can actively see our new component on the page. This will allow us to make sure everything is configured correctly in Sitecore, before we start adding the script specific code.

import React, { useEffect, useState } from 'react';
import { withSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';

export const SurveyMonkeyScript = (props): JSX.Element => {
  const { sitecoreContext } = props;

  return (
    <div>
      Script Box loading...
    </div>
  );
};
export default withSitecoreContext()(SurveyMonkeyScript);

1b. Sitecore: Creating Our New Rendering

Once you have created your new .tsx file, we need to create a location for our authors to input their scripts! We can do this by finding the template for your selected pages and adding a new multi-line text field to your item’s template file. We will use this to access/edit our script's value.

Note: It's important that you use a multi-line text field here and not a Rich Text field, as sitecore will automatically strip all script tags from Rich Text fields.

A screenshot of a content management system interface with empty text entry fields labeled 'Content' and 'Body.'

After adding the multi-line text field to your item’s template file, you can go to your item, and add your script code inside the new input.

Close-up of a 'Survey Monkey Script' text box in a CMS interface.

Next, we need to link our Next.js component with Sitecore. We can do this by creating a new JSON rendering. I am going to make one specifically for my Survey Monkey script. We can then link it to the .tsx file we created above. Make sure the “Component Name” in Sitecore matches the name of your Next.js script.tsx file, for me that’s SurveyMonkeyScript.

A screenshot showing the CMS structure, highlighting 'Page Content - Survey Monkey' and data component bindings.

After creating our JSON rendering, we need to add the item into our “Available Renderings”.

A sidebar menu in a CMS showing 'Dictionary,' 'Presentation,' and 'Page Components' options.

A dialog box in a CMS for selecting items, with 'Page Content - Survey Monkey' available.

The final step is to insert our new component into our selected page using the Sitecore Experience Editor. Once you do, you should be able to see the Script Box loading... div we created. If you don't, then you know you’ve made a mistake, and will need to spend some time debugging before you can continue. As long as you can see the Script Box loading... text, you can hit “save” and we can move on to the next step.

A CMS rendering selection screen highlighting 'Page Content - Survey Monkey.'

2. Next.js: Accessing Our Third Party Code Inside script.tsx

As much as I love our “Script Box loading...” text, this doesn’t look great for our authors. Luckily, we can easily create an “Experience Editor only” mode. Inside the .tsx file, we can utilize the sitecoreContext value pageEditing to detect if we are inside the experience editor, and display a user-friendly message, when we are. My example is using Tailwind 3.4

const isEdit = sitecoreContext?.pageEditing;
if (isEdit) {
  return <h3 className="my-8 bg-gray-100 p-16">Script Box</h3>;
}

return (
  <div>
    Script Box loading...
  </div>
);

A CMS script box labeled 'Page Content - Survey Monkey' with 'Script Box' placeholder text.

Next, we need to add some code to pluck our script value from our multi line field inside sitecoreContext. You can add a simple console.log for your sitecoreContext and find the correct route. For my instance, it was simple:

//console.log('sitecoreContext (Use this to find your Script item): ', sitecoreContext);

const scriptCode = sitecoreContext?.route?.fields?.['Monkey Survey Script']?.value;

You might get a hydration error when you pass the script code into the DOM. To fix this, we can create a subcomponent called MonkeySurveyScript and use Next Dynamic to import the script code without using SSR.

import dynamic from 'next/dynamic';
const MonkeySurveyScript = dynamic(() => import('./MonkeySurveyScript'), { ssr: false });

.
.

return <MonkeySurveyScript scriptCode={scriptCode} />;

3. Next.js: Injecting our Sitecore Script into the DOM

3a. Creating a Reusable Custom Hook for Your Incoming Scripts.

If you don’t want to have the reusable functionality of this in your project, you can put the useEffect directly into your .tsx file. However, I want to build out the cleanest code possible - so we will be creating a custom hook that will take the incoming <script> data, and dynamically insert it into the DOM.

Step 1: Create a new .tsx file under a custom hooks folder. Ex: hooks/useScript.tsx

Inside this file, we can add a simple document.body.appendChild to append our incoming script tag to the DOM. It's also good practice to add a cleanup inside the useEffect. In a lot of cases with Next.js, removing the script when the user navigates to another page ensures the script re-executes when navigating back.

import { useEffect } from 'react';

const useScript = incomingScript => {
  useEffect(() => {
    document.body.appendChild(incomingScript);

    //Dont forget to clean up your useEffect
    return () => {
      document.body.removeChild(incomingScript);
    }
  }, [incomingScript]);
};

export default useScript;

Step 2: We can now import and reference our new hook, passing through our incoming script data. The Custom Hook will dynamically import the script onto the page, after the client loads.


const isEdit = sitecoreContext?.pageEditing;
if (isEdit) {
  return <h3 className="my-8 bg-gray-100 p-16">Script Box</h3>;
}

const scriptCode = sitecoreContext?.route?.fields?.['Monkey Survey Script']?.value;
useScript(scriptCode);

return <div></div>;

3b. Alternative Quick Fix/Work Around. (Optional)

Sometimes you need the script tag on the page prior to Next.js sending the data to the client. If your script tag doesn't work correctly while using the above method, then this is a quick workaround that is a little crude, but will still work.

Instead of creating a customHook, we will use dangerouslySetInnerHTML to insert the script value inside a div. I recommend setting a unique, and easy to find, ID. This will make it much easier to debug once the code has been deployed.

return (
    <div
      id="script-box"
      dangerouslySetInnerHTML={{
        __html: `${scriptCode}`,
      }}
    ></div>
  );

In Closing:

You can use the above code for any third-party script that your authors might want to insert onto their pages. In the above example, we limited it to only one individual script, but with some simple refactoring, you could allow the insertion of multiple scripts.



Meet Tyler Holmes

Full Stack Developer

🇬🇧🧑‍💻🏸

Tyler is a friendly Full Stack Developer who loves developing top-notch solutions for unique problems. He possesses a deep understanding of Technical SEO, C#, and React/Next.js, along with an insatiable craving for carbonated water and Milkis!

Connect with Tyler