Builder form, web form and doctype: why 'DoesNotExistError'

I’ve generated:

  1. ‘NGS Contact Form’ doctype (add Guest permission Read/Create)
  2. ‘NGS Contact Form’ web form (linked to the upper doctype)
  3. Builder page with a default form (full_name, email, message)

Beblow is the Embed HTML of the Builder page:

<script>
(() => {
  console.log("[A] embed script loaded (no-turnstile)");

  const WEB_FORM_ROUTE = "ngs-contact-form";

  const $ = id => document.getElementById(id);
  const el = {
    full_name: $("full_name"),
    email: $("email") || $("email_id"),
    message: $("message"),
    btn: $("submitBtn"),
    msg: $("msgBox")
  };

  function setBusy(on){
    if(!el.btn) return;
    if(on){
      if(!el.btn.dataset.origHtml) el.btn.dataset.origHtml = el.btn.innerHTML;
      el.btn.disabled = true;
      el.btn.setAttribute("disabled","disabled");
      el.btn.setAttribute("aria-busy","true");
      el.btn.classList.add("is-disabled","btn-disabled","disabled");
      el.btn.innerText = "Submitting...";
    }else{
      el.btn.disabled = false;
      el.btn.removeAttribute("disabled");
      el.btn.removeAttribute("aria-busy");
      el.btn.classList.remove("is-disabled","btn-disabled","disabled");
      el.btn.innerHTML = el.btn.dataset.origHtml || "Submit";
    }
  }

  function toast(t, ok){
    if(!el.msg) return;
    el.msg.textContent = t || "";
    el.msg.style.padding = "6px 10px";
    el.msg.style.borderRadius = "6px";
    el.msg.style.marginTop = "8px";
    el.msg.style.background = t ? (ok ? "#e8fff1" : "#ffecec") : "transparent";
  }

  async function submitForm(ev){
    console.log("[C] submit handler fired, btn:", ev?.target);
    const payload = {
      full_name: el.full_name?.value || "",
      email: el.email?.value || "",
      message: el.message?.value || ""
      // no turnstile_token
    };
    console.log("[D] payload:", payload);
    console.log("[D1] web_form param:", WEB_FORM_ROUTE);

    if(!WEB_FORM_ROUTE){ toast("web_form 缺失,无法提交", false); return; }
    if(!payload.full_name || !payload.email || !payload.message){
      toast("请填写完整信息。", false); return;
    }

    const body = new URLSearchParams({ web_form: WEB_FORM_ROUTE, data: JSON.stringify(payload) });
    console.log("[E] form-data:", body.toString());

    setBusy(true); toast("Submitting...", true);
    try{
      console.log("[F] fetch start");
      const res = await fetch("/api/method/frappe.website.doctype.web_form.web_form.accept", {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
          "X-Frappe-CSRF-Token": (window.csrf_token || window.frappe?.csrf_token || "")
        },
        credentials: "include",
        body
      });
      console.log("[G] fetch done status:", res.status);
      const json = await res.json().catch(()=>({}));
      console.log("[G1] accept response:", json);

      if (res.ok && !json.exc){
        toast("OK:已创建记录。", true);
        ["full_name","email","message"].forEach(id => { const i = $(id); if(i) i.value = ""; });
      } else {
        toast("FAIL: " + (json.message || JSON.stringify(json)), false);
      }
    }catch(err){
      console.error("[Gx] network/error:", err);
      toast("网络错误", false);
    }finally{
      setBusy(false);
      console.log("[H] finalize");
    }
  }

  if (el.btn){
    el.btn.type = "button";
    el.btn.addEventListener("click", e => { e.preventDefault(); submitForm(e); });
    console.log("[A2] click bound on button");
  } else {
    console.warn("[A2] no button found; binding form submit instead");
    const form = (el.full_name || el.email || el.message)?.closest?.("form") || document.querySelector("form");
    form?.addEventListener("submit", e => { e.preventDefault(); submitForm(e); });
  }
})();
</script>

and ngs_contact_form.py:

class NGSContactForm(Document):
	def before_insert(self):
		self.full_name = (self.full_name or "").strip()
		self.email = (self.email or "").strip()
		self.message = (self.message or "").strip()
		if not self.full_name or not self.email or not self.message:
			frappe.throw(_("Full Name, Email and Message are required."))
		safe_name = re.sub(r"[^\w\s-]", "", self.full_name).replace(" ", "_")
		self.contact_id = make_autoname(f"CT-{safe_name}-.###")
		self.name = self.contact_id
		if len(self.message) > 8000:
			frappe.throw(_("Message is too long."))
		ip = _get_client_ip()
		_rate_limit(f"ngs-contact:ip:{ip}", limit=5, ttl=300)  # 5 分钟最多 5 次
		_rate_limit(f"ngs-contact:email:{self.email}", limit=5, ttl=300)

But when I fill the form and click the submit button, the console report the error:

(index):164  POST https://athenomics.com/api/method/frappe.website.doctype.web_form.web_form.accept 404 (Not Found)
submitForm @ (index):164
(anonymous) @ (index):192Understand this error
(index):173 [G] fetch done status: 404
(index):175 [G1] accept response: {exc_type: 'DoesNotExistError', _server_messages: '["{\\"message\\": \\"DocType None not found\\", \\"titl…1d96f298c6d1fa7626c234d7ea52a85160f537acd7c4\\"}"]'}

Why there is a ‘DoesNotExistError’? It doesn’t make any sense.

Any idea or suggestions are welcome.

Thanks,

2 Likes