FastComments.com

Add Comments to GoHighLevel Sites

With FastComments we can easily add live commenting to any site built with GoHighLevel.

Note that this tutorial requires a FastComments account. It's recommended that you sign up first and then come back here. You can create an account here.

Signing in first will ensure the generated code snippets are already tied to your account.

GoHighLevel Membership Sites and Other Sites

This tutorial is split between two categories: Membership Sites and regular GoHighLevel Sites.

We start with the instructions for Membership Sites.

Step 1: Edit Course Internal Link

First, we're going to edit the settings for our course.

To do this, open the course, and click Edit Details.

Edit Course Details
Edit Course Details

Step 2: Open Advanced Settings Internal Link

Next, we need to open the Advanced settings:

Open Advanced Settings
Open Advanced Settings

We'll be adding our code to the Tracking Code section. Go to that section and click Footer Code.

Step 3: Copy Code Internal Link

Now we're going to copy our FastComments code. Try using the "Copy" button on the right, as the code is quite large to work properly with GoHighLevel:

GoHighLevel FastComments Code Snippet
Copy Copy
1
2<script>
3 (function () {
4 const tenantId = 'demo';
5 const VALID_PATTERNS = ['/post']; // only show on these pages. This is ignored if you set TARGET_ELEMENT_ID.
6 const TYPE = 'commenting'; // set to one of: commenting, live, collab. You can also configure this per-page.
7 const TARGET_ELEMENT_ID = ''; // you can set this to add the widget to a specific area of the page by adding a separate HTML element with a div
8 const SCRIPT_ID = 'fastcomments-embed'; // don't change this
9
10 function getType() {
11 if (TARGET_ELEMENT_ID) {
12 const container = document.getElementById(TARGET_ELEMENT_ID);
13 if (container) {
14 return container.getAttribute('type');
15 } else {
16 console.log('FastComments: Type not available yet.');
17 return null;
18 }
19 }
20 return TYPE;
21 }
22
23 function getConstructor() {
24 const type = getType();
25 if (!type) {
26 return null;
27 }
28 if (type === 'commenting') {
29 return window.FastCommentsUI;
30 }
31 if (type === 'live') {
32 return window.FastCommentsLiveChat;
33 }
34 if (type === 'collab') {
35 return window.FastCommentsCollabChat;
36 }
37 }
38
39 function getScript() {
40 const type = getType();
41 if (type === 'commenting') {
42 return 'https://cdn.fastcomments.com/js/embed-v2.min.js';
43 }
44 if (type === 'live') {
45 return 'https://cdn.fastcomments.com/js/embed-live-chat.min.js';
46 }
47 if (type === 'collab') {
48 return 'https://cdn.fastcomments.com/js/embed-collab-chat.min.js';
49 }
50 }
51
52 function getContainer() {
53 let container;
54 if (TARGET_ELEMENT_ID) {
55 container = document.getElementById(TARGET_ELEMENT_ID);
56 if (container && container.getAttribute('type') === 'collab') {
57 container = getContentContainer(); // instead we'll target the page content!
58 }
59 } else {
60 container = getContentContainer();
61 }
62 return container;
63 }
64
65 // Function to ensure script is loaded
66 function ensureScriptLoaded() {
67 return new Promise(async (resolve) => {
68 // Check if script tag already exists
69 let scriptTag = document.getElementById(SCRIPT_ID);
70
71 if (!scriptTag) {
72 await new Promise((resolve) => {
73 const interval = setInterval(() => {
74 if (!getType()) {
75 return; // wait
76 }
77 // wait for this so we can accurately determine what script/constructor to fetch
78 const container = getContentContainer();
79 if (container) {
80 resolve();
81 clearInterval(interval);
82 }
83 }, 100);
84 });
85 console.log('FastComments: Script tag not found, adding dynamically...');
86 scriptTag = document.createElement('script');
87 scriptTag.id = SCRIPT_ID;
88 scriptTag.src = getScript();
89 scriptTag.async = true;
90
91 scriptTag.onload = () => {
92 console.log('FastComments: Script loaded successfully', scriptTag.src);
93 resolve();
94 };
95
96 scriptTag.onerror = () => {
97 console.error('FastComments: Failed to load script', scriptTag.src);
98 resolve(); // Resolve anyway to prevent hanging
99 };
100
101 document.head.appendChild(scriptTag);
102 } else if (getConstructor()) {
103 // Script tag exists and is already loaded
104 console.log('FastComments: Script already loaded');
105 resolve();
106 } else {
107 // Script tag exists but not ready yet
108 console.log('FastComments: Waiting for script to initialize...');
109 scriptTag.addEventListener('load', () => {
110 resolve();
111 });
112
113 // Fallback in case the script is already loading
114 const checkInterval = setInterval(() => {
115 if (getConstructor(getContainer())) {
116 clearInterval(checkInterval);
117 resolve();
118 }
119 }, 100);
120
121 // Timeout after 10 seconds
122 setTimeout(() => {
123 clearInterval(checkInterval);
124 console.warn('FastComments: Script load timeout');
125 resolve();
126 }, 10000);
127 }
128 });
129 }
130
131 // History API modifications for SPA support
132 const oldPushState = history.pushState;
133 history.pushState = function pushState() {
134 const ret = oldPushState.apply(this, arguments);
135 window.dispatchEvent(new Event('pushstate'));
136 window.dispatchEvent(new Event('locationchange'));
137 return ret;
138 };
139
140 const oldReplaceState = history.replaceState;
141 history.replaceState = function replaceState() {
142 const ret = oldReplaceState.apply(this, arguments);
143 window.dispatchEvent(new Event('replacestate'));
144 window.dispatchEvent(new Event('locationchange'));
145 return ret;
146 };
147
148 window.addEventListener('popstate', () => {
149 window.dispatchEvent(new Event('locationchange'));
150 });
151
152 function getContentContainer() {
153 let container = null;
154 // Try to find container with multiple selectors
155 container = document.querySelector('#post-body');
156 if (!container) {
157 container = document.querySelector('#content-container #content-container #post-description');
158 }
159 if (!container) {
160 container = document.querySelector('#post-description');
161 }
162 if (!container) {
163 container = document.querySelector('#content-container');
164 }
165 if (!container) {
166 container = document.querySelector('.post-description'); // mobile
167 }
168 return container;
169 }
170
171 let lastInstance;
172 let currentUrlId;
173
174 // Main render function
175 async function render() {
176 let rendered = false;
177
178 // Ensure script is loaded before proceeding
179 await ensureScriptLoaded();
180
181 // Look for the target element with specific ID
182 let container = getContainer();
183
184 function tryNext() {
185 if (rendered) {
186 return;
187 }
188
189 // Check if we should render on this page
190 if (!TARGET_ELEMENT_ID && !VALID_PATTERNS.some(function (pattern) {
191 return window.location.pathname.includes(pattern);
192 })) {
193 console.log('FastComments: Not set to load on this page. Waiting.');
194 setTimeout(tryNext, 1000);
195 return;
196 }
197
198 container = getContainer(); // important to re-fetch
199
200 if (container) {
201 // Double-check if available
202 if (!getConstructor()) {
203 console.log('FastComments: not ready, waiting...');
204 setTimeout(tryNext, 300);
205 return;
206 }
207 console.log('FastComments: Target element found, initializing...');
208
209 // Get urlId attribute
210 const urlIdAttr = container.getAttribute('urlid');
211
212 // Check if we need to re-render (urlId changed or first render)
213 if (currentUrlId !== urlIdAttr || !lastInstance) {
214 currentUrlId = urlIdAttr;
215
216 // Destroy previous instance if exists
217 if (lastInstance) {
218 lastInstance.destroy();
219 // Clear the container content
220 container.innerHTML = '';
221 }
222
223 // Prepare config
224 const config = {
225 tenantId,
226 showLiveRightAway: true
227 };
228
229 // Only add urlId to config if it's not "auto"
230 if (urlIdAttr && urlIdAttr !== 'auto') {
231 config.urlId = urlIdAttr;
232 console.log('FastComments: Using urlId:', urlIdAttr);
233 } else {
234 console.log('FastComments: Using auto URL determination');
235 }
236
237 // Initialize FastComments
238 lastInstance = getConstructor()(container, config);
239 rendered = true;
240 } else {
241 console.log('FastComments: Already rendered with same urlId');
242 rendered = true;
243 }
244
245 // Monitor if container gets removed or urlId changes
246 const interval = setInterval(function () {
247 const currentContainer = getContainer();
248 if (!currentContainer) {
249 console.log('FastComments: Container removed, will retry...');
250 rendered = false;
251 currentUrlId = null;
252 tryNext();
253 clearInterval(interval);
254 } else {
255 if (getType() === 'collab' && (!getContentContainer() || !getContentContainer().classList.contains('fastcomments-collab-chat-container'))) {
256 console.log('FastComments: collab chat removed!');
257 container = null; // collab chat was removed
258 rendered = false;
259 tryNext();
260 clearInterval(interval);
261 }
262 const newUrlId = currentContainer.getAttribute('urlid');
263 if (newUrlId !== currentUrlId) {
264 console.log('FastComments: urlId changed, re-rendering...');
265 rendered = false;
266 tryNext();
267 clearInterval(interval);
268 }
269 }
270 }, 1000);
271 } else {
272 console.log('FastComments: Target element not found, waiting...');
273 setTimeout(tryNext, 300);
274 }
275 }
276
277 tryNext();
278 }
279
280 // Initial render
281 render();
282
283 // Re-render on location change
284 window.addEventListener('locationchange', function () {
285 console.log('FastComments: Location changed, updating...');
286 render();
287 });
288 })();
289</script>
290

Different Comment Box Types

You can configure the TYPE = 'commenting' line to switch the product used (for example you can change it to live for streaming chat or collab for collab chat).

Putting The Comment Box Where You Want

Let's say you want to put comment boxes on specific parts of the page and not the default locations. Change this line:

const TARGET_ELEMENT_ID = ''; // set to use target div mode

To:

const TARGET_ELEMENT_ID = 'fc_box'; // set to use target div mode

Then in the GHL editor, click the "code" button and add where you want the comments to go:

GoHighLevel FastComments Div
Copy Copy
1
2<div
3 id="fc_box"
4 type="commenting"
5 urlid="curiouser-notcheating-whatischeating"
6></div>
7

Different Comment Box Type Per-Page

Let's say you want users to highlight and discuss pieces of text, or use the streaming chat UI instead.

First follow the steps above in "Putting The Comment Box Where You Want".

Note in that small snippet there's type="commenting".

If you want to enable collab chat for example change type to type="collab".

Only Show On Specific Pages

If you don't set don't set TARGET_ELEMENT_ID, you can instead configure the VALID_PATTERNS variable, to set which URL routes the comments should show. By default, it will show on pages that contain /post in the URL.

Step 4: Paste Code Internal Link

Now that we've copied our snippet, paste it in the Footer Code section as shown:

Paste Code
Paste Code

Member Site Success Internal Link

That's it! You should now have live commenting added to your GoHighLevel course.

Success
Success

If you've run into a permission denied error, or would like to customize FastComments, read on.

Member Site Customization Internal Link

FastComments is designed to be customized to match your site.

If you'd like to add custom styling, or tweak configuration, Checkout our Customization Documentation to learn how.

Step 1: Add Custom Code Element Internal Link

First, we're going to open the editor for the page of our site we want to add comments to.

Open Editor
Open Editor

Now find the place on the page where you want to add comments. Move your mouse toward the end of that area. A + icon will appear:

Add Section
Add Section

If we click that it asks us how many columns should the new section be. We'll select 1 COLUMN:

Add a Column
Add a Column

Now if you move your mouse over the new 1-column-row you'll have the option to add an element. Click that:

Add Element
Add Element

Scroll down and pick CUSTOM JS/HTML:

Select CUSTOM JS/HTML
Select CUSTOM JS/HTML

Now select our new element and click Open Code Editor on the left:

Open Code Editor
Open Code Editor

Step 2: Copy and Paste Code Internal Link

It's time to copy our code. Copy the following code:

GoHighLevel Site Comments Code
Copy Copy
1
2<script src="https://cdn.fastcomments.com/js/embed-v2.min.js"></script>
3<div id="fastcomments-widget"></div>
4<script>
5 FastCommentsUI(document.getElementById('fastcomments-widget'), {
6 tenantId: "demo",
7 urlId: window.location.pathname
8 });
9</script>
10

Paste that in the editor window we opened:

Paste Code
Paste Code

We can now click Yes, Save on the bottom right of that window.

At the top of the page now click Save and then Preview.

Site Success Internal Link

That's it! You should now have live commenting added to your GoHighLevel site.

Success
Success

If you've run into a permission denied error, or would like to customize FastComments, read on.

Site Customization Internal Link

FastComments is designed to be customized to match your site.

If you'd like to add custom styling, or tweak configuration, Checkout our Customization Documentation to learn how.

In Conclusion

If for any reason the provided steps or code is not working, please let us know.