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

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="custom-chat-id"
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.

Configuring Collab Chat

You can tell collab chat to only add collaborative functionality around HTML inside a specific area, for example, let's say you add the footer code above and then add this div in the post/page content to enable collab chat:

Collab Chat With Specified Content
Copy Copy
1
2<div
3 id="fc_box"
4 type="collab"
5 urlid="custom-chat-id"
6><p>This content will have collab chat!</p></div>
7

Then the paragraph element inside the <div> will have collab chat enabled, and nothing else on the page. If you don't put any content in the <div> then it will enable collab chat on the entire post body.

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.