GHL Experts

How to Password Protect a GoHighLevel Funnel Page with a PIN Code

Add PIN-code password protection to any GoHighLevel funnel page with this JavaScript solution. Includes full code, setup instructions, and customization options.

December 14, 2020
6 min read

Why You Would Password Protect a GoHighLevel Funnel

GoHighLevel does not include native password protection for funnel pages. Every published funnel page is publicly accessible to anyone with the URL. For most marketing funnels, that is exactly what you want -- you are trying to drive traffic to the page, not restrict it.

But there are legitimate scenarios where you need to limit access:

Exclusive content delivery. You sold a course, membership, or resource and want to gate the delivery page behind a password so only paying customers can access it.

Client preview pages. You built a funnel for a client and want them to review it before it goes live, without making it publicly accessible during the review period.

Internal tools and dashboards. You created a reporting page, resource library, or internal tool as a funnel page and want to restrict access to your team.

Event or webinar replay pages. You want to share a webinar replay only with registered attendees, not the general public.

Staging and development. You are testing a funnel and want to prevent accidental public access while it is still in progress.

This JavaScript solution adds a clean, 4-digit PIN code gate to any GHL funnel page. When someone visits the page, they see a password prompt instead of the page content. The actual funnel only appears after entering the correct PIN.

The Complete Script

Here is the full password protection code:

1<script>
2!(function () {
3 const pin_code = "1234";
4
5 const head = document.querySelector("head");
6 const style = document.createElement("style");
7 head.append(style);
8 style.innerHTML = `
9 @import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css");
10 #__nuxt {display: none}
11 .password_container {
12 display: flex;
13 width: 100%;
14 height: 100vh;
15 justify-content: center;
16 align-items: center;
17 padding: 1rem;
18 }
19 .password_container .form-group {
20 display: flex;
21 flex-direction: column;
22 justify-content: center;
23 align-items: center;
24 min-height: 400px;
25 }
26 .password_container form {
27 max-width: 500px;
28 display: flex;
29 margin: 0 auto;
30 width: 100%;
31 justify-content: center;
32 align-items: center;
33 padding: 1.5rem;
34 box-shadow: 0px 0px 8px #ccc;
35 border-radius: 10px;
36 }
37 .password_container .password_input {
38 display: flex;
39 width: 100%;
40 justify-content: center;
41 align-items: center;
42 gap: 5px;
43 }
44 .password_container label {
45 margin-bottom: 10px;
46 font-size: 20px;
47 font-family: "Roboto";
48 }
49 .password_container .form-group i {
50 font-size: 100px;
51 color: #ccc;
52 }
53 .password_container .password_input input {
54 display: flex;
55 justify-content: center;
56 align-items: center;
57 max-width: 50px;
58 max-height: 50px;
59 min-height: 50px;
60 border: 1px solid #ddd;
61 box-shadow: 0px 50px 80px #ddd;
62 border-radius: 5px;
63 text-align: center;
64 }
65 .password_container .password_input input:focus {
66 outline: 2px solid #ccc;
67 }
68 .password_container .error{
69 color: red;
70 margin-top: 10px;
71 font-weight: 600;
72 }`;
73
74 const body = document.querySelector("body");
75 const mainElement = document.querySelector("#__nuxt");
76 mainElement.remove();
77
78 const password_container = document.createElement("div");
79 password_container.classList.add("password_container");
80 password_container.innerHTML = `
81<form>
82 <div class="form-group">
83 <i class="fas fa-lock"></i>
84 <label for="password">Enter your password</label>
85 <div class="password_input">
86 <input type="password" name="p1" maxlength="1"/>
87 <input type="password" name="p2" maxlength="1"/>
88 <input type="password" name="p3" maxlength="1"/>
89 <input type="password" name="p4" maxlength="1"/>
90 </div>
91 <span class="error"><span>
92 </div>
93</form>
94`;
95 body.append(password_container);
96 const errorElement = password_container.querySelector(".error");
97
98 const inputs = [...password_container.querySelectorAll("input")];
99 const url = new URL(location.href);
100 let submited = "";
101
102 const updateInputByParams = (input) => {
103 if (input.name === "p1") {
104 input.value = url.searchParams.get("p1");
105 }
106 if (input.name === "p2") {
107 input.value = url.searchParams.get("p2");
108 }
109 if (input.name === "p3") {
110 input.value = url.searchParams.get("p3");
111 }
112 if (input.name === "p4") {
113 input.value = url.searchParams.get("p4");
114 }
115 submited += input.value;
116 };
117
118 inputs.forEach((input, index) => {
119 updateInputByParams(input);
120 input.addEventListener("input", (e) => {
121 const target = e.target;
122 const value = target.value;
123
124 if (target.name === "p1") {
125 inputs[1].value = "";
126 inputs[2].value = "";
127 inputs[3].value = "";
128 submited = "";
129 errorElement.innerHTML = "";
130 }
131
132 submited += value;
133 const nextInput = inputs[index + 1];
134 if (nextInput) inputs[index + 1].focus();
135 if (submited.length !== 4) return;
136
137 if (submited === pin_code) {
138 body.prepend(mainElement);
139 mainElement.style.display = "block";
140 password_container.remove();
141 } else {
142 errorElement.innerHTML = "Password not match!";
143 }
144 });
145 });
146
147 if (submited === pin_code) {
148 body.prepend(mainElement);
149 mainElement.style.display = "block";
150 password_container.remove();
151 } else {
152 if (submited) {
153 errorElement.innerHTML = "Password not match!";
154 }
155 }
156})();
157</script>

How the Script Works

The script performs several operations when the page loads:

1. Hides the main page content. It targets the #__nuxt element (GHL's main app container) and removes it from the DOM immediately. This prevents any flash of the protected content.

1. Injects a password form. A clean, centered PIN entry form replaces the page content. It includes a lock icon, a label, four individual input fields for the PIN digits, and an error message area.

1. Handles auto-focus behavior. As the user types each digit, focus automatically moves to the next input field. This creates a smooth, app-like PIN entry experience.

1. Validates the PIN. Once all four digits are entered, the script compares the input against the stored pin_code value. If it matches, the original page content is restored and the password form is removed. If it does not match, an error message appears.

1. Supports URL-based authentication. The script checks for p1, p2, p3, and p4 URL parameters. This means you can share a pre-authenticated link like https://yourfunnel.com/page?p1=1&p2=2&p3=3&p4=4 that bypasses the PIN entry for authorized users.

1. Resets on re-entry. If the user types the first digit again (the p1 input), all subsequent fields are cleared and the error message resets, allowing a fresh attempt.

Setup Instructions

Step 1: Set Your PIN Code

Open the script and change the pin_code value on the second line to your desired 4-digit code:

1const pin_code = "1234"; // Change this to your desired PIN

You can use any 4-character string. Numbers are most intuitive for users, but the script technically accepts any characters.

Step 2: Add the Script to Your Funnel Page

1. Open the funnel page in GoHighLevel's editor.

2. Click the Settings gear icon for the page.

3. Go to the Tracking Code section.

4. Paste the entire script into the Footer Code section.

5. Save the page and publish.

It is important to use the footer code section specifically. The script needs the page's DOM to be loaded before it can find and hide the #__nuxt element.

Step 3: Test the Protection

1. Open the published funnel page URL in your browser.

2. You should see the PIN entry form instead of the page content.

3. Enter the wrong PIN -- you should see the "Password not match!" error.

4. Enter the correct PIN -- the page content should appear.

5. Test the URL parameter method by adding ?p1=1&p2=2&p3=3&p4=4 (using your actual PIN digits) to the URL.

Step 4: Share Access

You have two ways to give people access to the protected page:

Share the PIN directly. Send the page URL along with the PIN code via email, SMS, or any other channel. The recipient enters the PIN manually.

Share a pre-authenticated URL. Append the PIN as URL parameters: https://yourfunnel.com/page?p1=1&p2=2&p3=3&p4=4. Anyone who clicks this link will see the page content immediately without entering the PIN. This is convenient for links in emails or messages.

Customization Options

Changing the Visual Style

The script includes all CSS inline, so you can modify the appearance directly. Here are common customizations:

Change the lock icon color:

1.password_container .form-group i {
2 font-size: 100px;
3 color: #3B82F6; /* Change from #ccc to your brand color */
4}

Change the form shadow and border radius:

1.password_container form {
2 box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1);
3 border-radius: 16px;
4}

Change the input focus color:

1.password_container .password_input input:focus {
2 outline: 2px solid #3B82F6; /* Your brand color */
3}

Change the label text: Find this line in the HTML and modify it:

1<label for="password">Enter your password</label>

You can change it to "Enter your access code," "Enter PIN to continue," or whatever fits your context.

Adding a Background Color or Image

By default, the password container uses a white background. Add a background to the container styles:

1.password_container {
2 background-color: #1a1a2e; /* Dark background */
3 color: #ffffff; /* White text */
4}

Important Considerations

This Is Client-Side Protection

The PIN validation happens entirely in the browser using JavaScript. This means:

It is not suitable for highly sensitive content. Someone with technical knowledge could disable JavaScript, inspect the page source, or bypass the protection. For truly sensitive data, use server-side authentication.

It is appropriate for casual gating. Client previews, event replays, internal resources, and exclusive content where the goal is convenience rather than military-grade security.

The PIN is visible in the page source. Anyone who views the page source code can find the PIN value. This is a trade-off of client-side protection.

For most GHL funnel use cases -- gating a webinar replay, sharing a client preview, or restricting access to a resource page -- this level of protection is more than sufficient. The target audience is not trying to hack the page; they just need a gate that prevents casual public access.

Search Engine Implications

Because the script hides the #__nuxt element immediately, search engine crawlers will not see the page content behind the protection. This means password-protected pages will not be indexed or ranked. If you need the page to be indexed but still gated for visitors, this approach is not the right fit.

Mobile Compatibility

The PIN entry form is responsive and works on mobile devices. The input fields are sized at 50x50 pixels, which provides a comfortable touch target. The auto-focus between fields also works on mobile keyboards.

Summary

Password protecting a GoHighLevel funnel page does not require complex integrations or third-party tools. This JavaScript PIN code solution gives you a clean, user-friendly access gate that works on any GHL funnel page. Set your PIN, paste the script into the footer tracking code, and share the PIN or a pre-authenticated URL with your intended audience. While it is client-side protection and not suitable for highly sensitive data, it handles the vast majority of funnel gating needs -- client previews, exclusive content, event replays, and internal tools -- with minimal setup and no ongoing maintenance.

Want to try HighLevel?

If you're exploring marketing automation platforms, HighLevel offers a 30-day free trial.

Start your free trial