Skip to content

ฟอร์ม ​

หน้านี้สอนอะไร

ให้ผู้แก้เนื้อหาสร้างฟอร์มในหน้า admin เอง (ติดต่อเรา สมัครกิจกรรม แบบสอบถาม) เก็บข้อมูลที่ผู้เข้าชมส่งมา ได้อีเมลแจ้งทุกครั้งที่มีคนส่ง และกันบอท

ควรอ่านก่อน: Plugins และอีเมล

ฟอร์มติดต่อในหน้า admin พร้อมแถบข้อมูลที่ส่งมา

@easy-cms/plugin-form-builder เพิ่มเมนู ฟอร์ม ในหน้า admin ผู้แก้เนื้อหาต่อช่องกรอกจาก block เลือกว่าหลังส่งจะเกิดอะไร และใครจะได้อีเมล ส่วนหน้าเว็บแสดงฟอร์มด้วย element เดียวคือ <easy-form> หรือวาดเองจาก API

bash
npm install @easy-cms/plugin-form-builder
bash
pnpm add @easy-cms/plugin-form-builder
bash
yarn add @easy-cms/plugin-form-builder
bash
bun add @easy-cms/plugin-form-builder
ts
import { consoleEmail, defineConfig } from '@easy-cms/core'
import { formBuilderPlugin } from '@easy-cms/plugin-form-builder'

export default defineConfig({
  // …
  email: consoleEmail(), // หรือ smtp() จาก @easy-cms/email-smtp ดูหน้าอีเมล
  plugins: [formBuilderPlugin({ defaultTo: 'hello@example.com' })],
})

plugin เพิ่ม collection forms และ form-submissions ให้สร้าง migration ด้วย (easy-cms migrate:create forms)

สร้างฟอร์ม ​

ฟอร์มหนึ่งมี:

  • ชื่อฟอร์ม และ slug: หน้าเว็บหาฟอร์มจาก slug (เช่น contact)
  • ช่องกรอก เป็น block: ข้อความ, ข้อความยาว, อีเมล, ตัวเลข, เบอร์โทร, ตัวเลือก (dropdown หรือ radio เลือกได้ข้อเดียวหรือหลายข้อ), ช่องติ๊ก (เช่น "ยอมรับเงื่อนไข"), วันที่ และข้อความอธิบาย (ข้อความคั่นระหว่างช่อง) แต่ละช่องมีชื่อในข้อมูล ป้าย ต้องกรอกหรือไม่ ข้อความตัวอย่าง ค่าเริ่มต้น และความกว้าง (เต็มหรือครึ่งแถว)
  • ปุ่มส่ง
  • หลังส่ง: แสดงข้อความ (rich text) หรือไปหน้าอื่น
  • อีเมลแจ้งเตือน: ใครจะได้อีเมลเมื่อมีคนส่ง (ดูด้านล่าง)

ฟอร์มมีฉบับร่าง รับข้อมูลเฉพาะฟอร์มที่เผยแพร่แล้ว จึงเตรียมฟอร์มไว้ก่อนได้โดยยังไม่ขึ้นเว็บ ถ้าใช้ หลายภาษา ป้ายและข้อความแปลได้เหมือนเนื้อหาอื่น และผู้เข้าชมจะเห็นฟอร์มในภาษาของตัวเอง

แถบข้างของฟอร์มแสดงจำนวนข้อมูลที่ส่งมา ลิงก์ไปดู export เป็น CSV (มี byte-order mark ให้ Excel อ่านภาษาไทยได้) และโค้ดสำหรับใส่ในหน้าเว็บ

ในหน้าเว็บ ​

ฟอร์มบนหน้าเว็บ แสดงด้วย <easy-form>

<easy-form> เป็น Web Component ที่โหลดฟอร์ม แสดง ส่งให้ server ตรวจ และแสดงข้อความหลังส่ง render ใน light DOM CSS ของเว็บจึงปรับหน้าตาได้ (class easy-form__field, easy-form__input, easy-form__error…) ส่วน element.css เป็นค่าเริ่มต้นที่เลือกใช้ได้

ts
import '@easy-cms/plugin-form-builder/element'
import '@easy-cms/plugin-form-builder/element.css'

export default defineNuxtPlugin(() => {})

// nuxt.config.ts: vue: { compilerOptions: { isCustomElement: (tag) => tag === 'easy-form' } }
// ในหน้า: <ClientOnly><easy-form form="contact" /></ClientOnly>
tsx
'use client'
import { useEffect } from 'react'
import '@easy-cms/plugin-form-builder/element.css'

export function EasyForm({ form }: { form: string }) {
  useEffect(() => {
    void import('@easy-cms/plugin-form-builder/element')
  }, [])
  return <easy-form form={form} />
}
html
<!-- CMS เสิร์ฟ element ให้ด้วย สำหรับเว็บ static และ framework อื่น -->
<script type="module" src="https://cms.example.com/api/cms/form/element.js"></script>
<easy-form form="contact" api="https://cms.example.com/api/cms"></easy-form>
attributeค่าเริ่มต้น
form—slug ของฟอร์ม
api/api/cmsREST API เมื่อ CMS อยู่คนละ server
localelang ของหน้าภาษาของเนื้อหา ถ้าเป็นภาษาที่เว็บมี

element ส่ง event easy-form:submitted (event.detail.confirmation) และ easy-form:error (event.detail.errors) เช่น สำหรับ analytics

หน้าเว็บที่อยู่คนละ origin (แบบ standalone) ต้องใส่ origin นั้นใน cors การส่งฟอร์มไม่มี cookie จึงไม่ต้องใส่ใน auth.trustedOrigins

วาดฟอร์มเอง ​

@easy-cms/plugin-form-builder/client มี API ชุดเดียวกันโดยไม่มี element สำหรับ component ของคุณเอง:

ts
import { getForm, submitForm } from '@easy-cms/plugin-form-builder/client'

const form = await getForm('contact', { api: '/api/cms', locale: 'th' })
// form.fields: [{ kind, name, label, required, placeholder, options… }]

const result = await submitForm(
  'contact',
  { data: { name, email, message }, token: form.token, [form.honeypot]: '' },
  { api: '/api/cms' },
)
if (result.ok) show(result.confirmation) // { type: 'message', html } หรือ { type: 'redirect', url }
else showErrors(result.errors) // [{ field?, message }]

ส่ง token กลับไปตามที่ได้รับ และส่งช่อง honeypot ว่างไว้ (ดูกันสแปม) เบื้องหลังคือ GET <api>/form/:slug และ POST <api>/form/:slug/submit

อีเมลแจ้งเตือน ​

แต่ละฟอร์มมีรายการอีเมลที่ส่งทุกครั้งที่มีคนส่งฟอร์ม ในหัวเรื่องและข้อความ {{name}} คือค่าของช่องนั้น และ {{*}} คือตารางของทุกช่อง

  • ถึง คือรายชื่ออีเมลคั่นด้วยจุลภาค ถ้าว่างจะใช้ defaultTo ของ plugin
  • ตอบกลับถึง ใส่ {{email}} ได้ กดตอบกลับแล้วจะถึงคนที่ส่งฟอร์ม
  • ถึง {{email}} ส่งอีเมลยืนยันให้คนที่ส่งฟอร์ม อีเมลแบบนี้ใส่ได้เฉพาะช่องสั้นๆ (ไม่มีข้อความยาว ไม่มี {{*}}) และตัดที่ 100 ตัวอักษร ไม่อย่างนั้นใครก็ใช้ฟอร์มนี้ส่งข้อความอะไรก็ได้ไปหาใครก็ได้

อีเมลส่งผ่าน email adapter ใน config มีคิวและส่งซ้ำเมื่อส่งไม่สำเร็จ และใช้ภาษาที่ผู้เข้าชมใช้ตอนกรอก

กันสแปม ​

ฟอร์มสาธารณะมักโดนบอท สิ่งเหล่านี้เปิดอยู่เสมอ:

  • honeypot: ช่องที่ซ่อนไว้ คนมองไม่เห็นแต่บอทจะกรอก
  • เวลาขั้นต่ำ: ฟอร์มที่ส่งเร็วกว่า 2 วินาทีหลังโหลด (minSubmitTime) ถือเป็นบอท
  • rate limit: 5 ครั้งต่อ 10 นาทีต่อผู้เข้าชมต่อฟอร์ม (rateLimit) แยกผู้เข้าชมด้วย IP ที่เก็บเป็น hash ซึ่งเปลี่ยนทุกช่วงเวลา

บอทที่ติดกับดักจะได้ "ขอบคุณ" เหมือนคนทั่วไปแต่ข้อมูลไม่ถูกบันทึก บอทจึงไม่รู้ว่าต้องหลบอะไร

ถ้าต้องการมากกว่านั้น เปิด Cloudflare Turnstile ซึ่งตรวจว่าเป็นคนโดยไม่เก็บข้อมูลส่วนตัว และส่วนใหญ่ไม่ต้องคลิกอะไร:

ts
formBuilderPlugin({
  turnstile: {
    siteKey: process.env.TURNSTILE_SITE_KEY,
    secretKey: process.env.TURNSTILE_SECRET_KEY,
  },
})

จากนั้น <easy-form> จะโหลดสคริปต์ของ Cloudflare ถ้าเว็บมี Content Security Policy ให้เพิ่ม https://challenges.cloudflare.com ใน script-src และ frame-src

ความเป็นส่วนตัว ​

ข้อมูลที่ส่งมาเก็บค่าที่กรอก หน้า และภาษา ไม่เก็บ IP ลบข้อมูลเก่าอัตโนมัติได้ด้วย retentionDays เช่น retentionDays: 365 และควรบอกผู้เข้าชมในฟอร์มว่าเก็บอะไรไปเพื่ออะไร (ช่องข้อความอธิบายเหมาะกับเรื่องนี้) ตามที่ PDPA และ GDPR กำหนด

เฉพาะผู้ที่ login อ่านข้อมูลที่ส่งมาได้ และเฉพาะ admin ที่ลบได้ ไม่มีใครสร้างผ่าน REST API ได้ ข้อมูลมาจาก endpoint ของฟอร์ม เท่านั้น หลังผ่านการตรวจข้อมูลและตัวกันสแปมแล้ว

ตัวเลือก ​

ตัวเลือกค่าเริ่มต้น
defaultTo—ผู้รับอีเมลเมื่อช่อง "ถึง" ว่าง
defaultFromfrom ของ adapterผู้ส่งเมื่ออีเมลไม่ได้ระบุ
fieldsทั้งหมดชนิดช่องที่ผู้แก้ใช้ได้ เช่น ['text', 'email', 'textarea']
rateLimit{ max: 5, window: 600 }จำนวนครั้งต่อผู้เข้าชมต่อฟอร์มใน window วินาที หรือ false
minSubmitTime2000มิลลิวินาทีก่อนนับว่าเป็นคนส่ง
turnstile—{ siteKey, secretKey } ของ Cloudflare Turnstile
retentionDays—ลบข้อมูลที่เก่ากว่านี้
slugsforms, form-submissions{ forms, submissions }: slug ของ collection

ต่อยอดด้วยสิ่งที่มีอยู่แล้ว ​

  • Webhooks: webhook ที่ตั้ง collections: ['form-submissions'] และ events: ['create'] ส่งข้อมูลแต่ละรายการไป Slack, LINE, n8n หรือ CRM ของคุณ
  • ผู้ช่วย AI: ถ้าใช้ plugin MCP กับ API key ที่อ่าน form-submissions ได้ ผู้ช่วยสรุปข้อความที่ได้รับในสัปดาห์นี้ได้

ขั้นต่อไป ​

เผยแพร่ภายใต้สัญญาอนุญาต MIT