Skip to content

Instantly share code, notes, and snippets.

@Nooshu
Created March 13, 2026 19:17
Show Gist options
  • Select an option

  • Save Nooshu/dd608f9df6e21ad5e2ec3a37e1e12264 to your computer and use it in GitHub Desktop.

Select an option

Save Nooshu/dd608f9df6e21ad5e2ec3a37e1e12264 to your computer and use it in GitHub Desktop.
This is the client-side script for adding Cloudflare Turnstile to my blog.
(function () {
const SITE_KEY = "0x4AAAAAACpYvR2v9J0i1tr_";
const form = document.getElementById("fs-frm");
if (!form) return;
const tokenInputName = "cf-turnstile-response";
let tokenInput = form.querySelector(`input[name="${tokenInputName}"]`);
if (!tokenInput) {
tokenInput = document.createElement("input");
tokenInput.type = "hidden";
tokenInput.name = tokenInputName;
form.appendChild(tokenInput);
}
const errorSummary = document.getElementById("form-error-summary");
const formStatus = document.getElementById("form-status");
function showFormError(message) {
if (errorSummary) {
errorSummary.textContent = message;
errorSummary.hidden = false;
errorSummary.focus();
}
}
function clearFormError() {
if (errorSummary) {
errorSummary.textContent = "";
errorSummary.hidden = true;
}
}
function showFieldError(fieldId, message) {
const field = document.getElementById(fieldId);
const errorEl = document.getElementById(fieldId + "-error");
if (field) {
field.setAttribute("aria-invalid", "true");
field.setAttribute("aria-describedby", fieldId + "-error");
}
if (errorEl) {
errorEl.textContent = message;
errorEl.hidden = false;
}
}
function clearFieldError(fieldId) {
const field = document.getElementById(fieldId);
const errorEl = document.getElementById(fieldId + "-error");
if (field) {
field.removeAttribute("aria-invalid");
field.removeAttribute("aria-describedby");
}
if (errorEl) {
errorEl.textContent = "";
errorEl.hidden = true;
}
}
function clearAllErrors() {
clearFormError();
["name", "email", "message"].forEach(clearFieldError);
}
function setStatus(message) {
if (formStatus) {
formStatus.textContent = message;
}
}
function validateForm() {
clearAllErrors();
const errors = [];
const nameField = form.querySelector("#name");
const emailField = form.querySelector("#email");
const messageField = form.querySelector("#message");
if (!nameField.value.trim()) {
showFieldError("name", "Please enter your full name.");
errors.push(nameField);
}
if (!emailField.value.trim()) {
showFieldError("email", "Please enter your email address.");
errors.push(emailField);
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailField.value.trim())) {
showFieldError("email", "Please enter a valid email address.");
errors.push(emailField);
}
if (!messageField.value.trim()) {
showFieldError("message", "Please enter a message.");
errors.push(messageField);
}
if (errors.length > 0) {
const summary =
errors.length === 1
? "There is 1 error in the form."
: "There are " + errors.length + " errors in the form.";
showFormError(summary);
errors[0].focus();
return false;
}
return true;
}
let widgetId = null;
let pendingSubmit = false;
let submitButton = null;
function onTokenReceived(token) {
tokenInput.value = token;
if (pendingSubmit) {
pendingSubmit = false;
form.submit();
}
}
function onTokenError(errorCode) {
tokenInput.value = "";
pendingSubmit = false;
setStatus("");
console.error("Turnstile error:", errorCode);
showFormError(
"Verification failed. Please try again. If you use an ad blocker, you may need to allow Cloudflare on this site."
);
if (submitButton) {
submitButton.disabled = false;
submitButton.textContent = "Send Message";
}
if (widgetId !== null && typeof turnstile !== "undefined") {
turnstile.reset(widgetId);
}
}
function renderTurnstile() {
const container = document.getElementById("turnstile-container");
if (!container || widgetId !== null) return;
if (typeof turnstile !== "undefined") {
widgetId = turnstile.render(container, {
sitekey: SITE_KEY,
size: "invisible",
execution: "execute",
callback: onTokenReceived,
"error-callback": onTokenError,
"expired-callback": function () {
tokenInput.value = "";
},
});
}
}
function waitForTurnstile() {
return new Promise((resolve) => {
if (typeof turnstile !== "undefined") {
resolve();
return;
}
let attempts = 0;
const checkInterval = setInterval(() => {
attempts++;
if (typeof turnstile !== "undefined") {
clearInterval(checkInterval);
resolve();
} else if (attempts > 100) {
clearInterval(checkInterval);
resolve();
}
}, 100);
});
}
waitForTurnstile().then(() => {
renderTurnstile();
});
form.addEventListener("submit", async function (e) {
e.preventDefault();
if (!validateForm()) return;
submitButton = form.querySelector('button[type="submit"]');
if (submitButton) {
submitButton.disabled = true;
submitButton.textContent = "Sending…";
}
setStatus("Verifying, please wait.");
await waitForTurnstile();
if (widgetId === null) {
renderTurnstile();
await new Promise((resolve) => setTimeout(resolve, 100));
}
if (tokenInput.value) {
form.submit();
return;
}
if (widgetId !== null && typeof turnstile !== "undefined") {
pendingSubmit = true;
turnstile.execute(widgetId);
} else {
console.error("Turnstile widget not available");
onTokenError("widget-not-available");
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment