ข้ามไปยังเนื้อหา
Dee Wan 1.0 ยังอยู่ระหว่างพัฒนาและยังไม่เปิดตัว (pre-MVP) — ดู ขอบเขตผลิตภัณฑ์ 1.0

การเขียนปลั๊กอิน

ปลั๊กอินเป็นเซอร์วิสที่ deploy แยกจาก core และไม่ได้รับ database binding คำสั่ง backend ใช้ HTTPS ส่วน UI แบบฝังที่ตรวจทานแล้วทำงานในเฟรมแยก โดย host ตรวจสิทธิ์ของ action เนื้อหาต่อไปนี้สร้างปลั๊กอินหนึ่งตัวตั้งแต่ต้นจนจบ: manifest, การติดตั้ง, grant, launch, คำสั่ง เผยแพร่แบบมีเงื่อนไข และการ uninstall

คุณต้องมีที่สำหรับรันเซอร์วิส HTTPS และที่สำหรับเก็บ state ของมัน Cloudflare Worker ที่มี ฐานข้อมูล D1 คือสิ่งที่ปลั๊กอินอ้างอิง (reference plugin) ใช้ (ซอร์สของปลั๊กอิน Scheduling คือ plugins/scheduling ใน repository ของ Dee Wan) และเอกสารนี้สมมติรูปแบบนั้น แต่ไม่มีสิ่งใดใน protocol v1 ที่บังคับเช่นนั้น

ทุก request และ response ด้านล่างถูกเล่นซ้ำกับ route จริงโดย test suite ของ Dee Wan เอง ดังนั้นสิ่งที่คุณอ่านคือสิ่งที่เซิร์ฟเวอร์ตอบจริง id และ secret ทั้งหมดเป็นข้อมูลสังเคราะห์และจงใจ ให้ใช้ไม่ได้

คุณเป็นเจ้าของ Dee Wan เป็นเจ้าของ
เซอร์วิสของคุณ ฐานข้อมูล นาฬิกา และการ retry ของมัน เนื้อหา, เวอร์ชัน, กฎ workflow, สิทธิ์, audit
browser session ของคุณเองและ UI ของคุณเอง transition นั้นถูกต้องตามกฎหรือไม่ และการนำไปใช้แบบ atomic
การตัดสินใจว่าจะร้องขอเมื่อใด การตัดสินใจว่าคำตอบคือใช่หรือไม่
การลบข้อมูลของคุณเอง การเพิกถอนสิทธิ์เข้าถึงของคุณ

Dee Wan ไม่มีวันเชื่อคำกล่าวอ้างของปลั๊กอินว่า transition ได้รับอนุญาต ว่าเวอร์ชันเป็นปัจจุบัน หรือ ว่าการยกเลิกการเผยแพร่ปลอดภัย สิ่งเหล่านั้นถูกตรวจซ้ำภายในการเปลี่ยนแปลง (mutation)

manifest คือเอกสาร HTTPS หนึ่งฉบับที่อธิบายปลั๊กอินของคุณและสิ่งที่มันต้องการ Dee Wan ดึงมัน ในสามจังหวะ และไม่เคยดึงใน request ทั่วไป:

  • ตรวจทาน ดึงและ validate มัน แสดงให้ผู้ดูแลดู และไม่เก็บอะไรเลย
  • ติดตั้ง ดึงมันอีกครั้ง validate อีกครั้ง และเก็บสำเนานั้นไว้ สำเนาที่เก็บไว้คือสิ่งที่ การตรวจทุกครั้งหลังจากนั้นใช้ ดังนั้นให้บริการ manifest ตอน install ให้เหมือนกับที่ให้บริการตอน review
  • Upgrade ดึงมันอีกครั้งจาก URL ของ manifest ที่เก็บไว้ เมื่อผู้ดูแลยอมรับ release ใหม่ (ดู การส่ง release ใหม่)
{
"manifest_version": 1,
"id": "com.example.myplugin",
"name": "My Plugin",
"version": "0.1.0",
"protocol_versions": [1],
"base_url": "https://plugin.example",
"activation_url": "https://plugin.example/dee-wan/activate",
"management_url": "https://plugin.example/installations/{installation_id}",
"requested_capabilities": [
"content:read",
"workflow:read",
"workflow:publish",
"workflow:unpublish"
],
"content_actions": [{ "id": "schedule", "label": "Schedule" }]
}

กฎที่ควรรู้ก่อนเขียน ซึ่งถูกบังคับใช้ทั้งหมด:

  • id เป็นตัวระบุแบบ reverse-domain ที่คงที่: ตัวพิมพ์เล็ก ขึ้นต้นด้วยตัวอักษร มีอย่างน้อยสาม label คั่นด้วยจุด ยาวไม่เกิน 128 ตัวอักษร มันไม่เปลี่ยนข้าม release ส่วน version คือ release ของคุณและเปลี่ยนได้อย่างอิสระ
  • name ยาว 1 ถึง 64 ตัวอักษร ไม่มี control character และไม่มี < หรือ >
  • version เป็น semantic versioning รูปแบบ MAJOR.MINOR.PATCH พร้อม suffix -prerelease ที่ใส่หรือไม่ก็ได้ (ไม่มี suffix +build) ยาวไม่เกิน 64 ตัวอักษร
  • manifest_version, protocol_versions และ version เป็นสามสิ่งที่ต่างกัน manifest_version คือ 1 ส่วน protocol_versions ระบุ protocol version ที่คุณรองรับ 1 ถึง 8 เวอร์ชัน และ อย่างน้อยหนึ่งเวอร์ชันต้องเป็นเวอร์ชันที่ core นี้รองรับ มิฉะนั้น manifest จะเป็น protocol_unsupported
  • requested_capabilities ระบุ capability อย่างน้อยหนึ่งตัว โดยไม่ซ้ำกัน
  • ทุก URL เป็น HTTPS ไม่มี credential และไม่มี fragment และทั้งหมด — รวมถึง URL ของ manifest เอง — ต้องใช้ origin เดียว (ONE) origin ที่สองคือ manifest_origin_mismatch
  • key ระดับบนสุดที่ไม่รู้จักจะถูกปฏิเสธ manifest ไม่สามารถพึ่งพาฟิลด์ที่ core นี้เพิกเฉยได้ ดังนั้นคุณ จึงไม่สามารถส่ง key ไปวันนี้แล้วหวังว่าระบบจะนำคีย์นั้นไปใช้ในภายหลัง
  • redirect ถูกปฏิเสธทั้งตอนดึง manifest และตอน activation ให้บริการทั้งสองโดยตรง
  • ปลายทาง private, loopback, link-local และ reserved ถูกปฏิเสธ สำหรับการพัฒนาบนเครื่อง local ดู การทดสอบปลั๊กอิน
  • content_actions ใส่หรือไม่ก็ได้ และมีได้สูงสุดสี่คู่ { id, label } ที่ id ไม่ซ้ำกัน id ต้องตรงกับ ^[a-z][a-z0-9_-]{0,31}$ ส่วน label ยาว 1 ถึง 24 ตัวอักษร ไม่มี control character และ ไม่มี < หรือ > ไม่มี markup ไม่มี script ไม่มี CSS ไม่มี URL ของไอคอน: core render ปุ่มของตัวเอง ด้วยสไตล์ของตัวเอง และเฉพาะบนรายการที่โมเดลอยู่ใน grant ของ installation เท่านั้น
  • manage_views ใส่หรือไม่ก็ได้: เป็นรายการหน้าจัดการโดยตรงจากชุดคำที่กำหนดตายตัว: calendar และ releases core render ลิงก์พร้อมป้ายชื่อของตัวเองสำหรับแต่ละรายการบน installation ใน การตั้งค่า → ปลั๊กอิน และเสนอแต่ละรายการข้างปฏิทินบรรณาธิการบน แดชบอร์ด และรายการ เนื้อหา ปลั๊กอินไม่ได้กำหนดป้ายชื่อ
  • management_url อาจมี {installation_id} และใช้สำหรับการนำทางเท่านั้น มันไม่มีวันได้รับ credential อายุยาวใน URL
  • ui_extensions ใส่หรือไม่ก็ได้: ส่วนเพิ่มแบบประกาศ 1 ถึง 16 รายการบนหน้าจอของ Dee Wan ดู การ์ดและตัวแก้ไขฟิลด์

schema ฉบับเต็มซึ่งสร้างจาก object ที่ core ใช้ validate คือ schemas/manifest-v1.schema.json

ui_extensions เพิ่มการ์ดและตัวแก้ไขฟิลด์ ตัวควบคุมแบบ declarative ใช้คอมโพเนนต์ของ core frame ที่ผ่านการตรวจทานเป็นตัวเลือกสำหรับ editor หรือหน้าจัดการแบบแยกจาก origin ของปลั๊กอิน เฟรมรับค่าฟิลด์ที่มีขอบเขตและ MessageChannel โดยไม่รับ admin session หรือ installation token การเปลี่ยนฟิลด์ใช้การ บันทึก ตามปกติ ส่วน operation ที่ประกาศไว้ผ่านการตรวจสิทธิ์และยืนยันจาก host ตัวแก้ไขไม่เปลี่ยนชนิดข้อมูลที่ฟิลด์จัดเก็บ

"ui_extensions": [
{
"id": "schedule-card",
"placement": "entry_sidebar",
"title": "Schedule",
"description": "Plan when this goes live.",
"launch": { "kind": "content_action", "action": "schedule" }
},
{
"id": "calendar-row",
"placement": "settings_row",
"title": "Calendar",
"launch": { "kind": "manage_view", "view": "calendar" }
},
{
"id": "priority",
"placement": "field_editor",
"control": "select",
"label": "Priority",
"help_text": "How soon this should go out.",
"options": [
{ "value": "low", "label": "Low" },
{ "value": "high", "label": "High" }
]
}
]
  • แต่ละรายการมี id ที่ตรงกับ ^[a-z][a-z0-9_-]{0,31}$ ไม่ซ้ำในแมนิเฟสต์ และคงเดิมทุก release ฟิลด์ของโมเดลอ้างถึงมัน manage, core, dee-wan, workflow, history, seo, preview และ settings สงวนไว้
  • placement คือ entry_sidebar, page_sidebar, settings_row หรือ field_editor ค่าอื่นถูกปฏิเสธ
  • การ์ดมี title (1 ถึง 40 ตัวอักษร) description ที่ใส่หรือไม่ก็ได้ (1 ถึง 160) และ launch หนึ่งตัว { "kind": "content_action", "action": "<id>" } เปิด content_actions ตัวหนึ่งของคุณสำหรับรายการ บนหน้าจอ { "kind": "manage_view", "view": "calendar" } เปิด manage_views ตัวหนึ่งของคุณ launch ที่อ้างถึงสิ่งที่แมนิเฟสต์ไม่ได้ประกาศถูกปฏิเสธ การ์ด settings_row เปิดได้เฉพาะหน้าจัดการ ทุก launch ผ่านการ launch แบบมอบสิทธิ์ในหัวข้อ 5
  • ตัวแก้ไขฟิลด์วาดฟิลด์ธรรมดา control คือ text, textarea หรือ url สำหรับฟิลด์ข้อความ และ number, boolean, date หรือ select สำหรับฟิลด์ชนิดนั้น ค่าที่เก็บยังเป็นค่าของชนิดนั้น label (1 ถึง 40) เป็นชื่อตัวแก้ไข และ help_text (1 ถึง 200) แสดงใต้ตัวแก้ไข
  • ตัวแก้ไข select มี options 1 ถึง 50 ตัว แต่ละตัวเป็น { "value", "label" } ที่ value ไม่ซ้ำ ตัวแก้ไข text และ textarea ตั้ง validation.min_length และ max_length ได้ ตัวแก้ไข URL ตั้ง max_length ได้ และ Dee Wan เพิ่มกฎ http(s) ของตัวเอง ตัวแก้ไขตัวเลขตั้ง min และ max ได้ ตัวแก้ไขวันที่ตั้ง min_date และ max_date เป็น YYYY-MM-DD ได้ ไม่รับอย่างอื่น ดังนั้นทุกกฎ เป็นกฎที่ขั้นตอนบันทึกของ Dee Wan บังคับใช้
  • ตัวแก้ไข group วาดฟิลด์ JSON หนึ่งฟิลด์เป็นแบบฟอร์มเล็ก ๆ ระบุ members 1 ถึง 8 ตัว แต่ละตัวมี key ที่ไม่ซ้ำ (^[a-z][a-z0-9_]{0,31}$) label และ control ได้แก่ text (ต้องมี max_length ไม่เกิน 500) number (min, max, integer) boolean หรือ select (options ไม่เกิน 12) required ทำให้สมาชิกชนิด text, number หรือ select เป็นข้อบังคับ ค่าเป็นอ็อบเจ็กต์ JSON หนึ่งตัวไม่เกิน 8192 ไบต์ เมื่อผู้สร้างเว็บไซต์เลือกตัวแก้ไขของคุณ Dee Wan เขียนสคีมาแบบปิดลงในฟิลด์ ค่าเริ่มต้นอนุญาตให้เว้นสมาชิกที่จำเป็นไว้ขณะบันทึกฉบับร่าง แต่โมเดลที่ตรวจแบบเข้มงวดต้องกรอกให้ครบทุกครั้งที่บันทึก ค่าที่ส่งมาแล้วผิดรูปแบบ อยู่นอกขอบเขตหรือตัวเลือก คีย์ที่ไม่ได้ประกาศ และค่าที่ไม่ใช่อ็อบเจ็กต์ยังถูกปฏิเสธ ก่อนเผยแพร่ ระบบตรวจสมาชิกที่จำเป็น ในเวอร์ชันที่บันทึกไว้ซึ่งจะนำไปเผยแพร่จริงเสมอ
  • ผู้สร้างเว็บไซต์เลือกตัวแก้ไขของคุณบนฟิลด์ของโมเดล Dee Wan คัดลอกกฎและตัวเลือกของตัวแก้ไขลงในฟิลด์ กฎและตัวเลือกเหล่านั้นจึงยังคงอยู่แม้ปลั๊กอินของคุณจะไม่พร้อมใช้งานอีกต่อไป เมื่อ installation ถูกปิดหรือถอนการติดตั้ง หรือ release ใหม่เอาตัวแก้ไขออก ฟิลด์ยังเก็บค่าไว้ และ Dee Wan วาดตัวแก้ไขของตัวเองพร้อมหมายเหตุที่ระบุชื่อ ปลั๊กอินของคุณ
  • ข้อความต้องพิมพ์ได้ ไม่มี control character และไม่มี < หรือ > คีย์ที่ไม่รู้จักถูกปฏิเสธ

operations คือการทำงานที่ Dee Wan เรียกใช้บนบริการของคุณ และแสดงด้วยฟอร์มและผลลัพธ์ของตัวเอง ผู้ใช้ เรียกใช้จาก การตั้งค่า → ปลั๊กอิน (scope: "site") หรือจากรายการ (scope: "entry") เอเจนต์เรียกใช้ ผ่าน API การจัดการหรือ MCP policies คือการตรวจที่ Dee Wan ถามก่อนการบันทึก การกู้คืน การลบ การเผยแพร่ การยกเลิกการเผยแพร่ หรือการย้ายเวิร์กโฟลว์ นโยบาย save หรือ restore ยังจัดระเบียบ เนื้อหาที่ถูกถามได้ด้วย

"operations": [
{
"id": "schedule-status",
"label": "Schedule status",
"description": "Pending scheduled changes for this entry.",
"endpoint": "https://plugin.example/dee-wan/operations/schedule-status",
"scope": "entry",
"audience": "interactive",
"requires": ["content:read"],
"result": ["rows"]
}
]
  • ต้องระบุ audience การดำเนินการ interactive แสดงในหน้าผู้ดูแล, API การจัดการ, CLI และ MCP การดำเนินการ automation ไม่แสดงในที่เหล่านั้น และเส้นทางเรียกใช้ของมันปฏิเสธ มีเพียงกระบวนการของ Dee Wan ที่ระบุชื่อที่เรียกใช้ได้ เช่น ตัววางแผนเนื้อหาที่เรียก schedule-draft ของ Scheduling
  • endpoint อยู่บน origin เดียวของ manifest requires ระบุ capability ที่คุณขอไว้ด้วย ระหว่างที่สิทธิ์ ยังขาดอย่างใดอย่างหนึ่ง การดำเนินการนั้นจะไม่ถูกเสนอ
  • input มีได้ไม่เกิน 8 ช่อง: text, number, boolean หรือ select Dee Wan วาดฟอร์มและตรวจค่า result ระบุคอมโพเนนต์ที่คำตอบของคุณใช้ได้: text, rows, links (https เท่านั้น) และ json ใส่ "destructive": true เฉพาะเมื่อการดำเนินการเปลี่ยนหรือลบข้อมูล แล้ว Dee Wan จะขอให้ผู้ใช้ยืนยัน
  • Dee Wan POST ซองเล็ก ๆ ไปยัง endpoint แลก code ของซองที่ POST /api/plugin/v1/invocations/exchange ด้วยโทเค็นของคุณ แล้วตอบ POST ด้วย { "status": "completed", "result": { "blocks": [...] } } หรือ { "status": "refused", "code", "message" } ตอบหลังแลกรหัสเท่านั้น รูปแบบและขีดจำกัดอยู่ใน เอกสารอ้างอิงโปรโตคอล: การดำเนินการและนโยบาย
  • การแลกรหัสของการดำเนินการแบบ entry จะคืน delegation_id ด้วย คือการมอบสิทธิ์ที่ผูกกับคนที่สั่งรัน การติดตั้งของคุณ รายการนั้นและโมเดลของรายการ เก็บไว้เมื่อการดำเนินการเริ่มงานที่ทำภายหลัง เช่น การตั้งเวลา และใช้เหมือนการมอบสิทธิ์จากการเปิดใช้งาน Dee Wan อ่านสิทธิ์ปัจจุบันของคนนั้นใหม่ทุกครั้ง ที่ใช้ การแลกรหัสของการดำเนินการแบบ site จะคืน delegation แบบเดียวกับ launch แบบ manage: ไม่มีรายการ และมีโมเดลที่ได้รับสิทธิ์ซึ่งคนนั้นอ่านได้ การดำเนินการ release ของ Scheduling รันแต่ละรายการภายใต้ delegation นี้ นโยบายจะได้ null
"policies": [
{
"id": "tone",
"label": "Tone check",
"kind": "save",
"endpoint": "https://plugin.example/dee-wan/policies/tone",
"requires": []
}
]
  • kind เป็นหนึ่งใน:
    • save: ทุกภาษาที่การบันทึกเปลี่ยนค่า ลิงก์ สื่อ หรือการตั้งค่าการค้นหา คุณได้คีย์ที่เปลี่ยนใน changed_fields และส่วนใหม่ใน values, links (ฟิลด์ความสัมพันธ์ → id ของรายการ), media (ฟิลด์สื่อ → id ของสื่อ) และ seo ลิงก์และสื่อที่ใช้ร่วมกันมากับภาษาหลัก การเปลี่ยนเฉพาะลิงก์ สื่อ หรือ SEO ก็ถูกถามด้วย (seo คือคีย์ของการตั้งค่า การค้นหา)
    • restore: การกู้คืนเวอร์ชันเก่า (action: "version" ข้อมูลเดียวกับการบันทึก) หรือรายการที่ออกจากถังขยะ (action: "trash") การกู้คืนเวอร์ชันถามนโยบาย save ด้วย
    • delete: รายการหรือภาษาที่ถูกย้ายลงถังขยะ (action: "trash") หรือถูกลบถาวร (action: "purge") คุณได้ draft ทั้งหมดของแต่ละภาษา
    • publish: ทุกภาษาที่จะเผยแพร่ ทั้งเวอร์ชัน unpublish: ทุกภาษาที่จะออฟไลน์ เวอร์ชันที่จะหยุดให้บริการ
    • transition: การย้ายเวิร์กโฟลว์ทุกครั้ง พร้อม transition: { from, to } release ตามเวลาและการย้ายของปลั๊กอินอื่น ก็ถูกถามด้วย caller.via บอกว่าใครเป็นผู้ย้าย
  • ตอบ { "decision": "allow" }, { "decision": "warning", "message", "fields"? } หรือ { "decision": "refuse", "code", "message", "fields"? } โดย fields คือคีย์ของฟิลด์ ตัวแก้ไขจะแสดง ข้อความของคุณที่ฟิลด์เหล่านั้น คำเตือนให้การเขียนผ่านไปได้ ไม่ต้องส่งซ้ำ
  • นโยบาย save หรือ restore อาจตอบ { "decision": "transform", "message"?, "values"?, "links"?, "media"?, "seo"? } แทน ซึ่งแทนที่ส่วนเหล่านั้นของรายการและภาษาที่ถูกถามตามคีย์ฟิลด์ รายการแทนที่ทั้งรายการ ลิงก์และสื่อที่ใช้ร่วมกันรับจากคำตอบของภาษาหลักเท่านั้น การกู้คืนรับเฉพาะ values และ seo Dee Wan บันทึก ผลลัพธ์ผ่านทุกกฎที่ข้อมูลจากผู้ใช้ต้องผ่าน หากกฎใดปฏิเสธ การเขียนจะถูกปฏิเสธเป็น plugin_policy_transform_invalid พร้อมเหตุผล สองนโยบายที่เปลี่ยนฟิลด์เดียวกันต่างกันจะถูกปฏิเสธเป็น plugin_policy_transform_conflict จากนั้นตัวแก้ไขจะแสดงว่านโยบายของคุณเปลี่ยนฟิลด์ใด พร้อมข้อความของคุณ และโหลดค่าที่บันทึกใหม่ ไม่มีใครต้องยืนยันซ้ำ
{
"decision": "transform",
"message": "Trimmed spaces and normalized the title.",
"values": { "title": "Harbour tours" }
}
  • การปฏิเสธหยุดการเขียนเสมอ หากหมดเวลา ระบบล่ม หรือคำตอบที่ Dee Wan ไม่รับ จะเป็นไปตามการตั้งค่าของเว็บไซต์ใน การตั้งค่า → ปลั๊กอิน: ค่าเริ่มต้นให้การเขียนผ่านพร้อมคำเตือน เมื่อเปิด บล็อกการเปลี่ยนแปลงถ้าไม่ตอบ การเขียนจะถูกปฏิเสธ
  • ทุกเส้นทางจะถาม: ตัวแก้ไข การบันทึกหน้า การกู้คืน ถังขยะ การทำงานแบบกลุ่ม สเปรดชีต การนำเข้า API การจัดการ และการเขียนของปลั๊กอินอื่น transform เปลี่ยนเฉพาะรายการที่กำลังเขียน คุณย้ายเวิร์กโฟลว์ไม่ได้เลย
  • มีเส้นทางเดียวที่ไม่ถาม: การล้างตามกำหนดที่ลบรายการที่อยู่ในถังขยะเกินระยะเก็บรักษา ไม่มีใครเป็นผู้กระทำที่นั่น จึงไม่มีใครให้ปฏิเสธ นโยบาย delete ถูกถามไปแล้วตอนรายการถูกย้ายลงถังขยะ และจะถูกถามอีกหากมีคนลบถาวรด้วยมือ
  • ประวัติเวอร์ชันแสดง Changed by <plugin> · <policy> บนเวอร์ชันที่ transform ของคุณเปลี่ยน

การดำเนินการ page เพิ่มคอลัมน์ในรายการเนื้อหาของโมเดลที่อยู่ในสิทธิ์ของคุณ การดำเนินการ entry ที่ติดธงจะกลายเป็น การทำงานกับแถวที่คนเลือก ทั้งสองใช้การเรียกที่อธิบายไว้ข้างต้น และ Dee Wan วาดทุกเซลล์และทุกผลลัพธ์

"operations": [
{
"id": "schedule-column",
"label": "Scheduled",
"endpoint": "https://plugin.example/dee-wan/operations/schedule-column",
"scope": "page",
"audience": "column",
"requires": ["content:read"],
"result": ["cells"],
"cell": "badge"
},
{
"id": "schedule-status",
"label": "Schedule status",
"endpoint": "https://plugin.example/dee-wan/operations/schedule-status",
"scope": "entry",
"audience": "interactive",
"requires": ["content:read"],
"result": ["rows"],
"list_action": true
}
]
  • คอลัมน์มี scope: "page", audience: "column", ชนิด cell หนึ่งชนิด (text, badge หรือ link), result: ["cells"] ไม่มี input และไม่มี destructive label ของมันคือหัวคอลัมน์
  • การแลกรหัสคืน $defs/column_request ได้แก่โมเดลและรหัสของแถวไม่เกิน 50 แถวที่คนนั้นอ่านได้ ไม่มีค่า อ่านสิ่งที่ต้องใช้ ผ่านสิทธิ์ของคุณเอง ตอบด้วยบล็อก cells หนึ่งบล็อก มีไม่เกินหนึ่งเซลล์ต่อรหัสที่ถูกถาม { "id", "text" } บวก tone สำหรับ ป้ายสี หรือ url แบบ https สำหรับลิงก์ ข้อความยาวไม่เกิน 80 ตัวอักษร เซลล์ของรหัสอื่น รหัสซ้ำ ลิงก์ที่ไม่มี url แบบ https หรือ url บนป้ายสี ทำให้คำตอบทั้งหมดใช้ไม่ได้ รายการว่างเป็นคำตอบที่ใช้ได้
  • คอลัมน์ไม่สร้างการมอบสิทธิ์ ไม่ถูกแสดงรายการหรือรันเป็นการดำเนินการทั่วไป ไม่เขียนแถวบันทึกการตรวจสอบ และไม่เก็บคำตอบที่เล่นซ้ำ อย่าเก็บอะไรเมื่อตอบ
  • การทำงานกับแถวคือการดำเนินการ entry แบบ interactive ที่มี list_action: true Dee Wan รันมันหนึ่งครั้งต่อแต่ละแถวจากไม่เกิน 25 แถวที่เลือก แต่ละแถวมีการเรียก การมอบสิทธิ์ คีย์ idempotency และรายการตรวจสอบของตัวเอง จึงตอบแต่ละแถวเหมือนการดำเนินการ entry

rich_blocks ประกาศชนิดบล็อกที่ผู้เขียนแทรกใน rich text ได้ คุณส่งเฉพาะข้อมูล Dee Wan วาดฟิลด์ แสดงพรีวิว บล็อก และ render บนไซต์จาก render tree แบบประกาศ ต้นไม้นี้ไม่มีสคริปต์ที่รันได้หรือการเรียกบริการระยะไกล HTML/CSS/JS ของผู้ให้บริการอยู่ในบล็อก snippet แยก โดยพรีวิวและผลลัพธ์สาธารณะทำงานในเฟรมแยก

"rich_blocks": [
{
"name": "callout",
"version": 2,
"label": "Callout",
"description": "A boxed note",
"fields": [
{ "key": "tone", "control": "select", "label": "Tone", "required": true, "default": "info",
"options": [{ "value": "info", "label": "Info" }, { "value": "warning", "label": "Warning" }] },
{ "key": "title", "control": "text", "label": "Title", "required": true, "validation": { "max_length": 80 } },
{ "key": "image", "control": "media", "label": "Image", "media_kind": "image" }
],
"preview": { "tone": "info", "title": "Heads up" },
"render": {
"tone": "tone",
"nodes": [
{ "element": "strong", "children": [{ "field": "title" }] },
{ "element": "img", "src": "image", "alt": "title" }
]
},
"migrations": [{ "from": 1, "to": 2, "steps": [{ "op": "rename", "from": "heading", "to": "title" }] }]
}
]
  • บล็อกถูกเก็บเป็น <your plugin id>/<name> พร้อมเวอร์ชันที่ใช้ตอนบันทึกและค่าของบล็อก
  • ตัวควบคุม: text, textarea, number, boolean, select, url และ media (media_kind: image, document, audio, video หรือ file โดย file คือไฟล์ดาวน์โหลดที่การตั้งค่าสื่อของไซต์อนุญาต) กฎ: required, min_length/max_length, min/max/integer และตัวเลือกของ select ไม่มี pattern ค่า url เป็นที่อยู่ http(s) หรือ path ของไซต์ ฟิลด์ไม่เกิน 24 ฟิลด์
  • rich_text ใช้ตัวแก้ไขข้อความแบบจัดรูปแบบของโฮสต์ ค่าเป็น { "richText": "<p>ข้อความ</p>" } ซึ่งเก็บ HTML มาตรฐานของโฮสต์ได้ไม่เกิน 10,000 อักขระ ใช้ { "field": "body" } ภายในองค์ประกอบแบบบล็อกเพื่อแสดงย่อหน้า รายการ ลิงก์ ตัวเน้น รูปภาพ และไฟล์ ข้อความเก็บใน <div data-rich-field="body"> ภายในบล็อกปลั๊กอิน การค้นหาสื่อ การผูกไฟล์กับเวอร์ชัน การนำเข้า และการส่งออกใช้กฎปกติของโฮสต์ ไม่อนุญาตบล็อกปลั๊กอิน สนิปเพ็ต หรือเนื้อหาฝังซ้อนภายใน และบล็อกปลั๊กอินแบบอินไลน์ประกาศ rich_text ไม่ได้ ไฟล์ส่วนตัวยังคงใช้ได้เฉพาะในโมเดลส่วนตัว
  • relation ใช้ models และ multiple ที่เป็นตัวเลือก พร้อมตัวเลือก relation ปกติและสิทธิ์อ่าน ปัจจุบันของบุคคล group ใช้ fields และตัวเลือก repeatable กับ max_items ซ้อนได้สามชั้น และไม่เกินห้าสิบรายการต่อกลุ่ม สื่อที่ซ้อนอยู่ยังมี media pin ตามปกติ ใช้ { "group", "children" } กับ { "each", "children" } เพื่อกำหนดขอบเขต binding ของลูกใน render tree
  • render.nodes คือ tree ของ: { "element", "children" } จาก div section aside p span strong em small h2 h3 h4 blockquote ul ol li figure figcaption hr br; { "element": "a", "href": <url or media field> }; { "element": "img", "src": <image field>, "alt"?: <text field> }; { "text" }; { "field" } (select แสดงป้ายของตัวเลือก) และ { "when", "equals"?, "children" } ไม่เกิน 64 node ลึกไม่เกิน 8 ระดับ render.tone ระบุฟิลด์ select ที่ค่า (neutral, info, success, warning, danger) ใช้แต้มสีบล็อก ทุกค่าถูก escape ที่อยู่ที่ไม่ปลอดภัยจะ render ลูกของ node โดยไม่มีลิงก์
  • หากไม่มี render บล็อกยังแก้ไขและเก็บได้ แต่ renderer แสดงว่าไม่รองรับ: ไม่มีอะไรบนหน้าสาธารณะ และ placeholder ในพรีวิว
  • เพิ่ม version เมื่อฟิลด์เปลี่ยน และเพิ่ม migration หนึ่งรายการต่อขั้น (to คือ from + 1): rename, set_default และ drop บล็อกที่เก็บไว้คงเวอร์ชันของตน renderer และตัวแก้ไข migrate ในหน่วยความจำ บล็อกที่เก็บไว้เปลี่ยนเมื่อผู้เขียนอัปเดต
  • การปิดหรือถอนการติดตั้งปลั๊กอินไม่เคยลบบล็อก บล็อกยังถูกเก็บพร้อมเวอร์ชันและค่า และรีลีสถัดไปของคุณ migrate ได้
  • เนื้อหาถูกตรวจตอนบันทึกกับปลั๊กอินที่ใช้งานอยู่เท่านั้น ดู กฎการบันทึก management API แสดงนิยามด้วย GET /api/manage/v1/rich-blocks (listRichBlockDefinitions) แอดมินอ่าน GET /api/plugins/rich-blocks และ renderer อ่าน GET /v1/_rich-blocks

media_sources ทำให้บริการของคุณเป็นคลังที่ผู้ใช้เรียกดูได้ในตัวเลือกสื่อของ Dee Wan ใต้ แหล่งที่มา ข้างคลังของไซต์เอง ขอ media:source และประกาศ source ได้ไม่เกินสี่รายการ:

"requested_capabilities": ["media:source"],
"media_sources": [
{
"id": "library",
"label": "Reference images",
"description": "Seven self-made CC0 files.",
"endpoint": "https://media-source.example/dee-wan/sources/library",
"modes": ["copy", "reference"],
"delivery_origins": ["https://media-source.example"]
}
]

modes จำเป็น copy ให้ผู้ใช้คัดลอกไฟล์เข้าคลังของไซต์ reference ให้ลิงก์ไฟล์แทน โดยเซอร์วิสของคุณเก็บไบต์ไว้ source ประกาศอย่างใดอย่างหนึ่งหรือทั้งสอง และ Dee Wan ไม่เดาโหมดจาก URL หรือชื่อ delivery_origins (หนึ่งถึงสี่ origin เปล่า origin ของ manifest อยู่ในนั้นได้) จำเป็นเมื่อมี reference และต้องไม่มีเมื่อไม่มี

  • core เรียก endpoint ผ่านการแลกการเรียกใช้แบบเดียวกับการดำเนินการ: POST ซอง คุณแลกรหัสด้วย token ของคุณ และข้อมูลที่ผูกมี kind: "media_source" id ของ source และไม่มี delegation ข้อมูลเข้าที่ผูกไว้คือ $defs/media_source_request: { "action": "browse", "query", "cursor", "limit" } หรือ { "action": "item", "item_id" }
  • ตอบการเรียกดูด้วย { "status": "completed", "items": [...], "next_cursor": "..." | null } และ item ด้วย { "status": "completed", "item": {...} } แต่ละ item มี id, title, file_name, mime_type, thumbnail_url, download_url (จำเป็นเมื่อ source ประกาศ copy), delivery_url (จำเป็นเมื่อประกาศ reference) และ attribution (source_name, license และ author, author_url, license_url, source_url ที่ไม่บังคับ) เพิ่ม width, height และ byte_size เมื่อรู้ description ของรูปภาพ (สูงสุด 125 ตัวอักษร) กลายเป็น alt text ในภาษาเริ่มต้นของไซต์ ปฏิเสธด้วย { "status": "refused", "code", "message" } ตัวเลือกจะแสดงข้อความของคุณ
  • thumbnail_url และ download_url ต้องอยู่บน origin ของ manifest ของคุณ และ delivery_url บนหนึ่งใน delivery_origins ของ source ตัวเลือกโหลดพรีวิวจากคุณโดยตรง โดยไม่มี referrer และไม่มีข้อมูลรับรองของ Dee Wan เมื่อผู้ใช้เพิ่ม item core ถาม item อีกครั้งและ GET download_url เอง: ไม่มีข้อมูลรับรอง ไม่มี redirect 20 วินาที และขีดจำกัดไฟล์สื่อ ลงนาม download_url หากไฟล์ของคุณไม่เป็นสาธารณะ
  • core ตรวจไบต์เหมือนการอัปโหลดทั่วไป: ชนิด รูปแบบ และขีดจำกัดขนาดของไซต์ แล้วจึงตรวจไฟล์ซ้ำ สำเนาคือไฟล์สื่อ ปกติที่ไซต์เป็นเจ้าของ คง attribution และ id ของปลั๊กอิน source และ item ของคุณไว้ ซึ่งรายละเอียดสื่อแสดงเป็น เครดิตและสำเนาไซต์พกไปด้วย หน้าที่เผยแพร่ได้เฉพาะ credit (author, source, license, url) ที่แสดง ใต้รูปภาพ หลังการคัดลอก ไม่มีอะไรบนไซต์โหลดจากคุณอีก หน้าจึงยังทำงานแม้คุณจะหายไป
  • โหมด reference ลิงก์ไฟล์แทนการคัดลอก core ไม่ดึงอะไร: แถวในคลังเก็บ delivery_url ของคุณเป็นที่อยู่ เก็บ id ของปลั๊กอิน source และ item ของคุณเป็นตัวตน พร้อมเมทาดาทาและเครดิตที่คุณประกาศ และ item เดิมที่เพิ่มอีก ทำตาม on_duplicate หน้าที่เผยแพร่ สำเนาไซต์ และไซต์ที่สร้างใช้ delivery_url ตามที่เป็น ดังนั้นให้เปิดให้บริการ และคงที่ตราบที่ไซต์ยังใช้ Dee Wan ไม่ proxy มัน แทนที่หรือแก้ไขไบต์ของมันไม่ได้ (media_external_bytes_unavailable) และไม่เรียกคุณเมื่อผู้ใช้ลบแถวในคลัง หากปลั๊กอินของคุณถูกถอนการติดตั้งหรือปิด หน้าที่มีอยู่คงที่อยู่ที่เก็บไว้ รายละเอียดสื่อบอกว่า source ใช้ไม่ได้ และลิงก์ใหม่จาก source ถูกปฏิเสธ เพิ่มโหมดด้วย mode ที่จำเป็นของคำขอ โหมดที่ source ไม่ได้ประกาศถูกปฏิเสธด้วย 422 media_source_mode_unsupported
  • การเรียกดูไม่เปลี่ยนอะไรและไม่ถูกบันทึกตรวจสอบ ผู้ใช้ต้องมี media:upload การปิดการติดตั้งหรือเอา media:source ออกจากสิทธิ์จะซ่อน source ทันที
  • management API มีความสามารถเดียวกัน: listMediaSources, browseMediaSource และ importMediaSourceItem (scope media:write เนื้อหา item_id และ mode) ดู management API
  • plugins/media-source-reference คือตัวอย่างเล็ก ๆ ที่สมบูรณ์พร้อมเทสต์ ชุด conformance ตรวจ source ด้วย subject mediaSource (ดู การทดสอบปลั๊กอิน)

hooks ให้บริการของคุณตัดสินใจ ก่อน ที่ Dee Wan จะรับสิ่งใด ประกาศได้ไม่เกินหนึ่งรายการต่อชนิด: upload_metadata ถูกถามก่อนเก็บไฟล์ใหม่ในคลัง ส่วน comment_moderation ถูกถามก่อนเก็บความคิดเห็นของผู้เยี่ยมชม

"hooks": [
{
"id": "upload-check",
"label": "Upload check",
"kind": "upload_metadata",
"endpoint": "https://plugin.example/dee-wan/hooks/upload",
"requires": []
},
{
"id": "comment-check",
"label": "Comment check",
"kind": "comment_moderation",
"endpoint": "https://plugin.example/dee-wan/hooks/comment",
"requires": []
}
]

hook ปิดอยู่จนกว่าผู้ดูแลไซต์จะเปิดให้ installation ใน การตั้งค่า → ปลั๊กอิน การประกาศไม่ได้ขออะไรและไม่ได้รันอะไร kind ที่ไม่รองรับจะถูกปฏิเสธตอนตรวจ manifest

  • Core เรียก endpoint ผ่านการแลกเปลี่ยน invocation เดียวกับ operation ใน binding มี kind: "hook", id ของ hook ของคุณ, caller: null และไม่มี delegation ข้อมูลนำเข้ามีเฉพาะสิ่งที่การตัดสินใจต้องใช้: สำหรับการอัปโหลดคือ $defs/hook_upload_facts (ชื่อไฟล์ ประเภท MIME ขนาด โฟลเดอร์ และข้อความแทนรูป ไม่มีไบต์ของไฟล์) สำหรับความคิดเห็นคือ $defs/hook_comment_facts (รายการเนื้อหา ID ของความคิดเห็น ความคิดเห็นแม่ ชื่อผู้เขียน เว็บไซต์ และข้อความ ไม่มีอีเมล) ไม่ระบุว่าใครเป็นผู้ถาม
  • ตอบการอัปโหลดด้วย $defs/hook_upload_reply: { "decision": "accept" } จะใส่ normalized ด้วยก็ได้ (filename ใหม่ที่คงนามสกุลเดิม และข้อความแทนรูปเฉพาะภาษาที่การอัปโหลดระบุไว้แล้ว) หรือ { "decision": "refuse", "code", "message" } ผู้อัปโหลดจะเห็นข้อความของคุณ (422 upload_hook_refused)
  • ตอบความคิดเห็นด้วย $defs/hook_comment_reply: { "decision": "approve" }, { "decision": "hold" } (ความคิดเห็นรอผู้ดูแล) หรือ { "decision": "reject" } (เก็บเป็นสแปม หรือเป็น refused เมื่อมี "as": "refused")
  • ตอบภายใน 5 วินาที hook ที่หมดเวลา เข้าถึงไม่ได้ ตอบนอกชุดคำตอบที่กำหนด หรือถูกปิดระหว่างทำงาน จะไม่อนุมัติเด็ดขาด: การอัปโหลดถูกปฏิเสธ (503 upload_hook_unavailable ไม่บันทึกอะไร) และความคิดเห็นยังรอการตรวจ
  • คำถามมี retry identity ที่คงที่ การอัปโหลดซ้ำหรือความคิดเห็นที่ส่งซ้ำจะได้คำตอบแรกที่ยอมรับแล้วของคุณกลับมานาน 24 ชั่วโมงโดยไม่เรียกใหม่ คำถามที่ล้มเหลวจะถูกถามใหม่

การติดตั้งคือการส่งมอบระหว่างสองฝ่าย ดังนั้นเซอร์วิสของคุณต้องมีหนึ่ง route ก่อนที่ใครจะติดตั้งมันได้: activation_url ออก activation code แบบใช้ครั้งเดียว มอบให้ผู้ดูแลผ่านช่องทางอื่น (out of band) แล้ว รอ โค้ดต้องตรงกับ ^[A-Za-z0-9_-]{16,128}$ สิ่งอื่นใดจะถูก Dee Wan ปฏิเสธด้วย invalid_activation_code ก่อนที่เซอร์วิสของคุณจะถูกเรียก เมื่อผู้ดูแลติดตั้ง Dee Wan จะ POST สิ่งนี้ไปยัง activation_url:

{
"kind": "activate",
"installation_id": "plg_0000000000000000000000000000d1ee",
"site_id": "cdocssite00000000000000001",
"dee_wan_base_url": "https://cms.example",
"protocol_version": 1,
"token": "dwp_ExampleTokenNotRealExampleTokenNotRealExamp",
"activation_code": "activation-code-0123456789"
}

สิ่งที่ handler ของคุณต้องทำ:

  1. ใช้ activation_code ครั้งเดียวเท่านั้น และปฏิเสธโค้ดที่ไม่รู้จัก ใช้ไปแล้ว หรือหมดอายุ นี่คือ สิ่งเดียวที่พิสูจน์ว่าผู้เรียกคือผู้ดูแลที่คุณให้โค้ดไป
  2. ตรวจว่า protocol_version เป็นเวอร์ชันที่คุณรองรับ และ dee_wan_base_url เป็น origin ที่คุณยอมรับ
  3. เก็บ token เป็นข้อมูลลับของคุณสำหรับการติดตั้งปลั๊กอินรายการนี้ โดยเข้ารหัสข้อมูลที่จัดเก็บ ไม่มีวันบันทึกลง log ไม่มีวันอยู่ใน URL หรือ error body
  4. ตอบ 2xx ภายใน 5 วินาที ด้วย body ขนาดไม่เกิน 32 KB core จะทำเครื่องหมาย installation เป็น active เฉพาะเมื่อนั้น และเก็บเฉพาะ hash SHA-256 ของ token เท่านั้น มันไม่สามารถแสดง token ได้อีก

จนกว่า handler ของคุณจะ return installation ยังคงเป็น pending และ token ของ installation ที่ pending จะได้คำตอบ plugin_unauthorized ดังนั้นอย่าเรียก /api/plugin/v1 จากภายใน activation handler — เก็บ token ตอบกลับ แล้วค่อยเรียกครั้งแรกหลังจากนั้น

จะเรียกกลับไปที่ใด: ทุก protocol request ไปที่ dee_wan_base_url ตามด้วย /api/plugin/v1 เช่น <dee_wan_base_url>/api/plugin/v1/context เก็บ base URL ไว้พร้อมกับ token และยอมรับ เฉพาะ origin ที่คุณคาดไว้

kind ที่เป็น rotate คือการส่งมอบแบบเดียวกันสำหรับ installation ที่มีอยู่แล้ว: แทนที่ token ที่เก็บไว้ ทั้งสองแบบมีโค้ดมาด้วย เพราะ rotation คือการส่งมอบ ไม่ใช่การ reset

เมื่อ activation ล้มเหลว คำตอบที่ไม่ใช่ 2xx, redirect, body ที่เกิน 32 KB หรือไม่มีคำตอบภายใน 5 วินาที ล้วนทำให้การติดตั้งล้มเหลว: core ลบ installation ที่ pending ของมัน และแสดงเหตุผลให้ ผู้ดูแลเห็น อย่างไรก็ตาม core มองไม่เห็นว่าฝั่งคุณทำอะไรไปแล้ว หาก handler ของคุณเก็บ token ไว้ แล้วตอบช้าเกินไป ตอนนี้คุณจะถือ token ของ installation ที่ไม่มีอยู่แล้ว และ activation code ของคุณ ก็ถูกใช้ไปแล้ว ให้วางแผนรับมือไว้:

  • ทำให้ handler สั้น และเสร็จก่อน timeout อย่างสบาย ๆ
  • ถือว่า installation ที่เก็บไว้ยังไม่ได้รับการยืนยัน จนกว่าจะเรียกด้วย token ของมันสำเร็จครั้งแรก และ ลบ installation ที่ token ไม่เคยใช้งานได้และตอนนี้ได้คำตอบ plugin_unauthorized
  • บอกผู้ดูแลให้ออก activation code ใหม่สำหรับการ retry การ retry คือการติดตั้งใหม่ ด้วย installation_id ใหม่ ไม่ใช่การพยายามครั้งที่สองกับ installation เดิม

ผู้ดูแลที่มี site:plugins จะ review manifest ก่อน การ review ไม่เก็บสิ่งใดไว้ — มัน มีไว้เพื่อให้การอนุมัติทำโดยรู้ข้อมูล

สิทธิ์และโมเดลที่ปลั๊กอินขอ หลังตรวจ manifest และก่อนเปิดใช้งาน

Review manifest

หน้าต่างเทอร์มินัล
curl -X POST "https://cms.example/api/plugins/review" \
-H "X-Site-Id: cdocssite00000000000000001" \
-H "Content-Type: application/json" \
-b "__Secure-better-auth.session_token=$SESSION" \
--data '{"manifest_url":"https://plugin.example/dee-wan/manifest.json"}'
{
"data": {
"manifest": {
"manifest_version": 1,
"id": "com.example.myplugin",
"name": "My Plugin",
"version": "0.1.0",
"protocol_versions": [1],
"base_url": "https://plugin.example",
"activation_url": "https://plugin.example/dee-wan/activate",
"management_url": "https://plugin.example/installations/{installation_id}",
"requested_capabilities": [
"content:read",
"workflow:read",
"workflow:publish",
"workflow:unpublish"
],
"content_actions": [{ "id": "schedule", "label": "Schedule" }]
},
"protocol_version": 1,
"review_digest": "5ad97eed147d0a0f9f8934fa20485f287e656d72499fc739c831d2748680e49a"
}
}

จากนั้นผู้ดูแลจึงติดตั้ง: capability ที่อนุมัติ, id ของโมเดลที่อนุมัติ และโค้ดแบบใช้ครั้งเดียวของคุณ การติดตั้งจะดึงและ validate manifest อีกครั้ง และสำเนาที่สองนั้นคือสิ่งที่ถูกเก็บ ชุดที่อนุมัติ ต้องเป็นส่วนย่อยของสิ่งที่ manifest นั้นร้องขอ model_ids ที่ว่างหมายถึงไม่มีสิทธิ์เข้าถึงเนื้อหา เลย — ไม่ใช่ทุกโมเดล ส่งค่า review_digest ที่ได้รับใน expected_review_digest เพื่อให้ติดตั้ง จาก URL และ manifest ที่ตรวจแล้วเท่านั้น หากมีการเปลี่ยนแปลง เซิร์ฟเวอร์จะตอบ 409 manifest_changed_since_review ก่อนเปิดใช้งาน

ติดตั้ง ซึ่งจะ activate ด้วย

หน้าต่างเทอร์มินัล
curl -X POST "https://cms.example/api/plugins" \
-H "X-Site-Id: cdocssite00000000000000001" \
-H "Content-Type: application/json" \
-b "__Secure-better-auth.session_token=$SESSION" \
--data '{"manifest_url":"https://plugin.example/dee-wan/manifest.json","activation_code":"activation-code-0123456789","expected_review_digest":"5ad97eed147d0a0f9f8934fa20485f287e656d72499fc739c831d2748680e49a","capabilities":["content:read","workflow:read","workflow:publish","workflow:unpublish"],"model_ids":["cdocsmodel0000000000000001"]}'
{
"data": {
"id": "plg_0000000000000000000000000000d1ee",
"plugin_id": "com.example.myplugin",
"plugin_name": "My Plugin",
"plugin_version": "0.1.0",
"protocol_version": 1,
"state": "active",
"capabilities": [
"content:read",
"workflow:read",
"workflow:publish",
"workflow:unpublish"
],
"requested_capabilities": [
"content:read",
"workflow:read",
"workflow:publish",
"workflow:unpublish"
],
"model_ids": ["cdocsmodel0000000000000001"],
"content_actions": [{ "id": "schedule", "label": "Schedule" }],
"manage_views": [],
"ui_extensions": [],
"operations": [],
"policies": [],
"policy_modes": {},
"hooks": [],
"hook_modes": {},
"manifest_url": "https://plugin.example/dee-wan/manifest.json",
"management_origin": "https://plugin.example",
"created_by_email": "[email protected]",
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}
}

ไม่มี token และไม่มี hash ปรากฏใน response นั้น ในการอ่านครั้งต่อ ๆ ไป หรือใน audit entry ที่การติดตั้ง เขียนไว้ เซอร์วิสของคุณถือสำเนาเพียงชุดเดียว

grant เปลี่ยนได้ทุกเมื่อ และเปลี่ยนได้เฉพาะภายในสิ่งที่ manifest ที่เก็บไว้ร้องขอ การลบ capability หรือโมเดลมีผลกับ request ถัดไปของคุณ — ไม่มี cache ให้ต้องรอ

นำ capability ที่ปรากฏว่าปลั๊กอินนี้ไม่ต้องใช้ออก

หน้าต่างเทอร์มินัล
curl -X PUT "https://cms.example/api/plugins/plg_0000000000000000000000000000d1ee/grant" \
-H "X-Site-Id: cdocssite00000000000000001" \
-H "Content-Type: application/json" \
-b "__Secure-better-auth.session_token=$SESSION" \
--data '{"capabilities":["content:read","workflow:read","workflow:publish"],"model_ids":["cdocsmodel0000000000000001"]}'
{
"data": {
"id": "plg_0000000000000000000000000000d1ee",
"plugin_id": "com.example.myplugin",
"plugin_name": "My Plugin",
"plugin_version": "0.1.0",
"protocol_version": 1,
"state": "active",
"capabilities": ["content:read", "workflow:read", "workflow:publish"],
"requested_capabilities": [
"content:read",
"workflow:read",
"workflow:publish",
"workflow:unpublish"
],
"model_ids": ["cdocsmodel0000000000000001"],
"content_actions": [{ "id": "schedule", "label": "Schedule" }],
"manage_views": [],
"ui_extensions": [],
"operations": [],
"policies": [],
"policy_modes": {},
"hooks": [],
"hook_modes": {},
"manifest_url": "https://plugin.example/dee-wan/manifest.json",
"management_origin": "https://plugin.example",
"created_by_email": "[email protected]",
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}
}

การขอ capability ที่อยู่นอก manifest ที่เก็บไว้คือ 400 invalid_capabilities grant สามารถ ขยายกลับได้ในภายหลัง แต่ไม่มีวันเกินสิ่งที่ manifest ที่เก็บไว้ร้องขอ

การเผยแพร่ manifest ใหม่ที่ URL ของคุณไม่เปลี่ยนอะไรเลยใน installation ที่มีอยู่ การกระทำที่ชัดเจนสองอย่าง โดยผู้ดูแลเท่านั้นที่เปลี่ยน:

  1. ยอมรับ release ด้วย POST /api/plugins/:id/upgrade และ body {"expected_version": "<the version they reviewed>"} core ดึง manifest อีกครั้งจาก URL ของ manifest ที่เก็บไว้ และปฏิเสธด้วย manifest_changed_since_review หาก version ของมันไม่ใช่ เวอร์ชันที่ระบุ ด้วย manifest_identity_changed หาก id หรือ origin ของ base_url เปลี่ยนไป และด้วย protocol_changed หาก protocol version ที่จะต่อรองได้เปลี่ยนไป เมื่อสำเร็จ มันจะเก็บ manifest ใหม่ และคงไว้เฉพาะ capability ที่ได้รับ grant ซึ่ง manifest ใหม่ยังร้องขออยู่ การยอมรับ release สามารถทำให้ grant แคบลงได้ แต่ไม่มีวันขยายมัน
  2. ขยาย grant หาก release ร้องขอ capability ใหม่ ด้วย PUT /api/plugins/:id/grant ตามข้างต้น ตอนนี้ทำได้เพราะ manifest ที่เก็บไว้ร้องขอมันแล้ว

ดังนั้น release ที่ต้องการ capability ใหม่ต้องระบุไว้ใน release notes ของตัวเอง: จนกว่า ผู้ดูแลจะทำทั้งสองขั้นตอน เซอร์วิสของคุณจะยังทำงานด้วย grant เดิม และคำสั่งที่ ต้องการ capability ใหม่จะได้คำตอบ capability_refused

delegated launch ปกติเปิดหน้าจัดการของคุณในแท็บใหม่ ส่วน frame ที่ประกาศและตรวจแล้ว เปิดตัวแก้ไขหรือมุมมองจัดการในเฟรมแยกภายในแอดมิน โดยโฮสต์ตรวจสิทธิ์ของการกระทำ เฟรมไม่ได้รับ session ของแอดมินหรือโค้ด launch cookie ของ Dee Wan อยู่ภายใน Dee Wan delegated launch ออกโค้ดแบบใช้ครั้งเดียว ใช้ได้ 60 วินาที เก็บแบบ hash ผูกกับ installation, ไซต์, ผู้ใช้, action และ — สำหรับ action บนเนื้อหา — instance หนึ่งรายการ

launch มีสองแบบ manage (ไม่มี instance_id) เปิด UI ของคุณสำหรับทั้ง installation และต้องการ site:plugins launch แบบ manage ที่ระบุ view ที่ประกาศไว้เป็นงานบรรณาธิการ: ไม่ต้องใช้ site:plugins ต้องมีเพียงโมเดลหนึ่งใน grant ที่บุคคลนั้นอ่านได้ ทุกรายการหรือเฉพาะรายการของตัวเอง ส่วน action บนเนื้อหา — หนึ่งใน id ของ content_actions ใน manifest ของคุณ พร้อม instance_id — ต้องการสิทธิ์อ่านรายการนั้น และถูกปฏิเสธด้วย model_not_granted เมื่อโมเดลของ รายการไม่อยู่ใน grant ของ installation โดยตั้งแต่แรก admin UI ก็แสดง action ของคุณเฉพาะบนรายการของ โมเดลที่ได้รับ grant อยู่แล้ว

launch แบบ manage อาจมี "view" ด้วย ซึ่งเป็นหนึ่งใน manage_views ของ manifest ของคุณ จากนั้น URL ของ launch จะเพิ่ม dee_wan_view=<view> ไว้ข้าง dee_wan_launch ค่านี้เป็นเพียงคำใบ้หน้าปลายทาง: แลกรหัสตาม ปกติ แล้วส่ง session ไปที่หน้านั้น ค่านี้ไม่ได้ให้สิทธิ์ใดๆ ดังนั้นให้ถือว่าค่าที่ไม่รู้จักคือไม่มีคำใบ้ view ที่ manifest ของคุณไม่ได้ประกาศจะถูกปฏิเสธด้วย invalid_launch_action

การเปิดเริ่มจากเอเจนต์ก็ได้ ข้อมูลรับรองการจัดการหรือแอปที่เชื่อมต่อซึ่งมี scope plugins:use สร้างลิงก์เดียวกันผ่าน API การจัดการหรือ MCP (ดู การเปิดปลั๊กอิน) แกนหลักตรวจแบบเดียวกัน และผู้ใช้ ยังเป็นคนเปิดลิงก์ หน้าของคุณเห็นการเปิดตามปกติ

ขอ launch URL

หน้าต่างเทอร์มินัล
curl -X POST "https://cms.example/api/plugins/plg_0000000000000000000000000000d1ee/launch" \
-H "X-Site-Id: cdocssite00000000000000001" \
-H "Content-Type: application/json" \
-b "__Secure-better-auth.session_token=$SESSION" \
--data '{"action":"schedule","instance_id":"cdocsinstance00000000000001"}'
{
"data": {
"url": "https://plugin.example/installations/plg_0000000000000000000000000000d1ee?dee_wan_launch=dwl_ExampleLaunchCodeExampleLaunchCodeExampleLa"
}
}

browser จะนำทางไปที่นั่น launch response จาก Dee Wan มี Referrer-Policy: no-referrer แต่หน้าที่ browser ไปถึงเป็นของคุณ และงานส่วนที่เหลือก็เป็นของคุณ เช่นกัน:

  • ส่ง Referrer-Policy: no-referrer บนหน้าจัดการของคุณเอง เพื่อไม่ให้โค้ดใน URL ของหน้านั้น รั่วไหลผ่าน referrer
  • ไม่โหลด resource จากบุคคลที่สามใด ๆ ก่อนการแลกเปลี่ยน
  • เมื่อการแลกเปลี่ยนสำเร็จ ให้ redirect ไปยังหน้าเดิมโดยไม่มี dee_wan_launch ใน URL เพื่อไม่ให้ โค้ดค้างอยู่ใน address bar หรือประวัติของ browser ปลั๊กอินอ้างอิงตอบ 303 See Other ไปยัง URL ที่สะอาด

แบ็กเอนด์ (BACKEND) ของคุณแลกเปลี่ยนโค้ดด้วย installation token ของคุณ:

แลกเปลี่ยนโค้ดเป็น delegation

หน้าต่างเทอร์มินัล
curl -X POST "https://cms.example/api/plugin/v1/launch/exchange" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
--data '{"code":"dwl_ExampleLaunchCodeExampleLaunchCodeExampleLa"}'
{
"data": {
"delegation_id": "pld_0000000000000000000000000000d1ee",
"installation_id": "plg_0000000000000000000000000000d1ee",
"site_id": "cdocssite00000000000000001",
"action": "schedule",
"instance_id": "cdocsinstance00000000000001",
"model_ids": ["cdocsmodel0000000000000001"],
"user": { "id": "cdocsuser00000000000000001", "email": "[email protected]" }
}
}

ตอนนี้ให้สร้าง session cookie ของคุณเอง (OWN) สำหรับ browser นั้น และเก็บ delegation_id ผูกไว้กับมัน delegation ไม่ใช่ credential และไม่มอบสิ่งใดด้วยตัวเอง: มันระบุบุคคลที่ร้องขอ และ ทุกคำสั่งที่คุณส่งในภายหลังจะแนบมันไปด้วย เพื่อให้ core อ่านสิทธิ์อำนาจของบุคคลนั้นใหม่ตอน execute

delegation ใช้ทำอะไรได้บ้าง:

  • delegation แบบ content-action (ที่มี instance_id) กระทำได้เฉพาะกับรายการใน translation group เดียวกับ instance นั้น การระบุมันกับรายการอื่นใดคือ requester_unauthorized
  • delegation แบบ manage (instance_id เป็น null) กระทำได้กับรายการของโมเดลใน model_ids ของมัน
  • ทุก delegation มี model_ids: โมเดลใน grant ที่บุคคลนั้นอ่านได้ตอน launch ทุกรายการหรือเฉพาะรายการของตัวเอง (สำหรับ action บนเนื้อหา คือโมเดลของรายการนั้น) แสดงและกระทำเฉพาะภายในนั้น รายการของโมเดลอื่น หรือรายการที่บุคคลนั้น อ่านไม่ได้แล้ว คือ requester_unauthorized
  • GET /api/plugin/v1/delegations/<delegation_id> บอกว่าตอนนี้ delegation เข้าถึงอะไรได้: แต่ละโมเดล ชนิดของ transition ที่บุคคลนั้นทำได้ที่นั่น และ entries ถามก่อนทุกหน้า เสนอการย้ายเวลาหรือยกเลิกงานที่ตั้งไว้ เฉพาะชนิดที่อยู่ในรายการ
  • entries: "all": บุคคลนั้นอ่านได้ทุกรายการของโมเดลนั้น และชนิดของ transition ใช้ได้กับทุกรายการ entries: "checked": บุคคลนั้นอ่านหรือแก้ไขได้เฉพาะรายการของตัวเองที่นั่น (read:own, update:own) ให้ถาม GET /api/plugin/v1/delegations/<delegation_id>/entries?instance_id=… (ไม่เกิน 100 id) ว่าจะแสดงรายการใดและแต่ละรายการทำอะไรได้ อย่าแสดงสิ่งที่คำตอบไม่ได้ระบุ อย่าคัดลอกว่าใครเป็นเจ้าของ อะไรไปเก็บในที่เก็บของคุณเอง ให้ถามใหม่ทุกคำขอ
  • delegation ที่เปิดโดย credential ของ management มีเพดาน capability: เฉพาะ capability ของ installation ที่ scopes ของ credential นั้นอนุญาต คำสั่งที่อยู่นอกเพดานคือ capability_refused
  • core ไม่ตรวจ action ของ delegation เมื่อคุณส่งคำสั่ง มันมีไว้ให้ UI ของคุณเอง รู้ว่าจะแสดงหน้าจอใด
  • delegation ไม่มีขีดจำกัดเวลา เพราะงานที่ตั้งเวลาไว้อาจรันหลังจากบุคคลนั้น launch UI ของคุณไปนานแล้ว มันหยุดใช้งานได้เมื่อบุคคลนั้นไม่มีสิทธิ์อำนาจที่คำสั่งต้องการอีกต่อไป — เขา เสียสิทธิ์ เสียบทบาทบนไซต์ หรือเสียบัญชี — และเมื่อ installation ถูก ปิดใช้งาน ถูก uninstall หรือ token ไม่ถูกต้องอีกต่อไป สิทธิ์อำนาจปัจจุบันของเขาถูกอ่านใหม่ใน ทุกคำสั่ง

ดู ความปลอดภัยของปลั๊กอิน

6. อ่านเนื้อหา และ pin สิ่งที่คุณตั้งใจจะเปลี่ยนให้ตรงตัว

หัวข้อที่มีชื่อว่า “6. อ่านเนื้อหา และ pin สิ่งที่คุณตั้งใจจะเปลี่ยนให้ตรงตัว”

ก่อนบันทึกความตั้งใจใด ๆ ให้อ่านรายการด้วย delegation บล็อก workflow บอกคุณ ว่าบุคคลนั้นทำอะไรได้จริงในตอนนี้ และให้ค่าที่ต้อง pin

การอ่านรายการต้องการ content:read บล็อก workflow — และพร้อมกับมันคือ workflow.revision ซึ่ง ทุกคำสั่งเปลี่ยนขั้นตอนต้องส่ง — จะถูกคืนก็ต่อเมื่อ installation ถือ workflow:read ด้วยเท่านั้น หากไม่มี คุณจะได้เพียงสรุป และไม่มี revision ให้ pin ปลั๊กอินที่ส่งคำสั่ง จึงต้องร้องขอทั้งสองอย่าง

อ่านรายการหนึ่งรายการในฐานะบุคคลที่ร้องขอ

หน้าต่างเทอร์มินัล
curl "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001?delegation_id=pld_0000000000000000000000000000d1ee" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"data": {
"instance_id": "cdocsinstance00000000000001",
"primary_instance_id": "cdocsinstance00000000000001",
"translation_group_id": "cdocsgroup0000000000000001",
"model_id": "cdocsmodel0000000000000001",
"model_slug": "article",
"lang": "en",
"label": "Hello",
"slug": "hello",
"current_version_id": "cdocsversion00000000000001",
"published_version_id": null,
"workflow": {
"state": "draft",
"revision": 0,
"transitions": [{ "to": "published", "kind": "publish" }]
}
}
}

การอ่านที่มี delegation ยังคืน saved โดยจำกัดข้อมูลตามสิทธิ์ฟิลด์และภาษาปัจจุบันของบุคคลนั้นด้วย

เก็บสิ่งต่อไปนี้ไว้กับ record ของคุณเอง:

  • primary_instance_id — instance ที่ transition อ้างถึง การเผยแพร่มีผลกับ translation group ทั้งกลุ่ม และ primary คือตัวแทน (handle) ของกลุ่ม
  • current_version_id สำหรับเผยแพร่ฉบับร่างปกติ หรือ published_version_id สำหรับยกเลิกการเผยแพร่ นั่นคือ pin
  • workflow.revision
  • to และ kind ที่คุณเลือก ซึ่งต้องเป็นหนึ่งใน transitions ที่เสนอให้

สร้างเวอร์ชันอนาคตแยกอิสระด้วย POST /api/plugin/v1/content/:instanceId/futures และอ่านด้วย GET /api/plugin/v1/content/:instanceId/futures/:futureId เมื่อจะเผยแพร่ ให้ pin current_version_id ที่บันทึกไว้ของสาขานั้น พร้อม future_draft_id และ expected_future_revision ระบบผูกสาขากับการติดตั้งปลั๊กอิน รุ่นเผยแพร่ และรายการ แล้วตรึงรีวิชันที่แน่นอนเมื่อคำสั่งเผยแพร่เริ่มทำงาน ระบบตรวจสิทธิ์ปัจจุบัน เวิร์กโฟลว์ ฟิลด์ที่จำเป็น และงานตรวจสอบกับเวอร์ชันนั้น โดยไม่เปลี่ยนฉบับร่างปกติ หากยังยืนยันผลไม่ได้ ให้ส่งคำสั่ง เวอร์ชัน และคีย์ idempotency เดิมซ้ำ ตัวอย่างด้านล่างเผยแพร่ฉบับร่างปกติ

การเปลี่ยนแปลงหนึ่งครั้ง โดยมีเงื่อนไขตามทุกสิ่งที่คุณ pin ไว้ พร้อม idempotency key ที่คุณสามารถสร้างซ้ำได้

เผยแพร่เวอร์ชันที่ถูก pin

หน้าต่างเทอร์มินัล
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/published" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sched:plg_0000000000000000000000000000d1ee:sch_0001:r1" \
--data '{"kind":"publish","expected_current_version_id":"cdocsversion00000000000001","expected_workflow_revision":0,"delegation_id":"pld_0000000000000000000000000000d1ee"}'
{
"outcome": {
"code": "transition_applied",
"message": "The transition was applied.",
"retry": false
},
"data": {
"instance_id": "cdocsinstance00000000000001",
"primary_instance_id": "cdocsinstance00000000000001",
"from": "draft",
"to": "published",
"published": true,
"published_changed": true,
"published_version_id": "cdocsversion00000000000001",
"revision": 1,
"redirect": {
"status": "none",
"reason": "no_public_address"
},
"policy_results": []
}
}

redirect มีอยู่เมื่อ transition เผยแพร่ มันรายงานการ redirect อัตโนมัติที่ core สร้างให้ที่อยู่ ที่เปลี่ยน หรือเหตุผลที่ไม่ได้สร้าง การส่งคำสั่งเดิมซ้ำจะได้ body เดียวกันนี้

สร้าง key จาก record ของคุณเองและ revision ของมัน ไม่มีวันสร้างจากนาฬิกา ปลั๊กอินอ้างอิง ใช้ sched:<installation_id>:<record_id>:r<revision> ซึ่งเป็นรูปแบบของ key ข้างต้น core ยอมรับอักขระ ASCII ที่พิมพ์ได้ 1 ถึง 128 ตัวใดก็ได้ที่ไม่มีช่องว่าง จากนั้น:

  • replay ที่ตรงกันทุกประการจะคืนผลลัพธ์แรกเป็น idempotent_replay การ retry หลัง timeout จึงปลอดภัย
  • การเผยแพร่ที่ย้ายที่อยู่จะเขียน redirect หลัง transition commit จนกว่าจะบันทึกผลนั้น คำสั่งและการส่งซ้ำจะตอบ temporary_failure พร้อมข้อความว่า transition ถูกนำไปใช้แล้ว ลองใหม่ด้วย key และ body เดิม คุณจะไม่ได้ผลลัพธ์ที่ไม่มี redirect
  • key เดียวกันกับ input ที่ต่างออกไปคือ idempotency_key_reused การตั้งเวลาใหม่คือ revision ใหม่ ดังนั้น จึงเป็น key ใหม่
  • ทุกการปฏิเสธมีชื่อและเป็นแบบ terminal ยกเว้น rate_limited และ temporary_failure อ่านตาราง ใน Protocol v1 และจัดการแต่ละตัว อย่าเปลี่ยนความล้มเหลวที่ไม่รู้จัก ให้เป็นความสำเร็จ และอย่า retry ไปตลอดกาล

audit trail บันทึกสิ่งนี้ว่าเป็นการกระทำของปลั๊กอิน ไม่ใช่ของผู้ใช้: workflow event ระบุ installation, plugin id ชื่อและเวอร์ชันที่ได้รับการยอมรับของคุณ, เวอร์ชันของโปรโตคอล, digest ของ idempotency key และบุคคลที่ delegation ของเขาอนุญาตการกระทำนั้น admin UI แสดง ชื่อปลั๊กอินของคุณเป็นผู้กระทำ

การ uninstall จะ revoke ก่อน สถานะกลายเป็น revoked hash ของ token ทั้งสองถูกทิ้ง และ token นั้น ไม่มีวันใช้ได้อีก

Uninstall

หน้าต่างเทอร์มินัล
curl -X DELETE "https://cms.example/api/plugins/plg_0000000000000000000000000000d1ee" \
-H "X-Site-Id: cdocssite00000000000000001" \
-b "__Secure-better-auth.session_token=$SESSION"
{ "data": { "id": "plg_0000000000000000000000000000d1ee", "state": "revoked" } }

ทุก request หลังจากนั้น รวมถึง request ที่อยู่ระหว่างทางในคิวของคุณแล้ว

หน้าต่างเทอร์มินัล
curl "https://cms.example/api/plugin/v1/context" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"outcome": {
"code": "plugin_unauthorized",
"message": "The installation token is missing, unknown, disabled or revoked.",
"retry": false
}
}

มีสองสิ่งตามมา และทั้งสองควรอยู่ในเอกสารของคุณเอง:

  • การระบุผู้กระทำใน audit ยังคงอยู่ workflow event เก็บ snapshot ของ installation ของคุณแทนที่จะเป็น foreign key ดังนั้นการ uninstall จึงไม่สามารถลบว่าใครเผยแพร่อะไรได้
  • Dee Wan ไม่อ้างว่าได้ลบสิ่งใดที่คุณถืออยู่ การลบฝั่งระยะไกลเป็นการกระทำของคุณ และ เอกสารของคุณต้องบอกว่าคุณเก็บอะไร เก็บนานเท่าใด และผู้ดูแลระบบ (operator) ลบมันได้อย่างไร คำตอบของ ปลั๊กอินอ้างอิงอยู่ใน Scheduling แบบ end to end

Protocol v1 จงใจไม่ให้ scheduler, คิว หรือ event stream แก่คุณ หากปลั๊กอินของคุณทำงาน ในภายหลัง คุณต้องรับผิดชอบ:

  • lease เพื่อไม่ให้ worker สองตัวของคุณ execute record เดียวกันพร้อมกัน — Idempotency-Key คือ ตัวป้องกันชั้นที่สองของ core ไม่ใช่ชั้นแรกของคุณ
  • retry ที่มีขอบเขตพร้อม jitter เพดานจำนวนครั้ง และเพดานอายุ และเคารพ Retry-After ภายในค่าสูงสุด ที่คุณเลือก
  • ตารางความล้มเหลวแบบ terminal ที่ผู้ใช้ของคุณอ่านได้ เพราะทุกการปฏิเสธแบบมีเงื่อนไขหมายความว่ามีคน ต้องตัดสินใจบางอย่าง
  • การอนุญาต (authorisation) ของคุณเองสำหรับ UI ของคุณเอง core ตรวจ launch หลังจากนั้น session เป็นของคุณ

ซอร์สของปลั๊กอิน Scheduling (plugins/scheduling ใน repository ของ Dee Wan) คือตัวอย่างที่ทำจริง ครบถ้วนของทั้งสี่ข้อ พฤติกรรมของมันอธิบายไว้ในหน้า ปลั๊กอิน Scheduling

ฟิลด์ความสัมพันธ์เก็บ ID ของรายการ การอ่านสาธารณะเลือกภาษาที่เผยแพร่และอ่านได้ของเป้าหมาย { "field": "target" } แสดงชื่อปัจจุบัน ส่วน { "field": "target", "property": "address" } แสดงฟิลด์ scalar ของเป้าหมายที่เปิดเผยตามสิทธิ์ ลิงก์ href: "target" ใช้ที่อยู่ Page/Blog/managed route ที่เผยแพร่จากตัวแก้ที่อยู่ร่วม หรือฟิลด์ url สาธารณะ เมื่อไม่มีที่อยู่สาธารณะจะแสดงเฉพาะข้อความ เป้าหมายที่เป็นฉบับร่าง ถูกถอนเผยแพร่ อยู่ในถังขยะ หรืออ่านไม่ได้จะไม่เปิดเผยชื่อและที่อยู่ ฟิลด์ส่วนตัวถูกตัดออก การบันทึกรับ ID ไม่รับชื่อหรือ URL ที่ส่งมา

สำเนาไซต์และประวัติมี rich-relations.json พร้อมจำนวนและ checksum ระบุรุ่น pointer ของ rich HTML pointer ของ ID ในบล็อก ID เป้าหมาย และโมเดล การนำเข้า remap ID ซ้อนและซ้ำหลังสร้างรายการ เป้าหมายปัจจุบันที่หายไปบล็อกการนำเข้า เป้าหมายประวัติเก่าที่ไม่ย้ายใช้คำเตือนการตัดความสัมพันธ์เดิม snippet ใช้เจ้าของ snippet:<source id> หากไม่มี declaration ต้นทางที่ตรงกัน การส่งออกปฏิเสธด้วยชื่อสาเหตุ คืน declaration ต้นทางแล้วส่งออกอีกครั้ง โดยไม่ใช้ declaration ปลายทางเดาว่าสตริงใดเป็น ID

ตั้ง inline: true ในการประกาศ rich_blocks เพื่อให้แทรกรายการในย่อหน้าได้ ฟิลด์ ค่า การอ้างอิงไฟล์ และรายการ เวอร์ชัน และการย้ายค่าใช้สัญญาของบล็อกเดิม องค์ประกอบที่เก็บคือ <span data-inline="plugin"> พร้อม data-type, data-version และ data-values ไฟล์ที่ประกาศยังเก็บเป็นลิงก์อ้างอิงว่างภายใน อย่าส่ง HTML ที่แสดงผลแล้ว

ต้นไม้การแสดงผลอนุญาตเฉพาะ span, strong, em, small, a, img และ br รวมถึงภายในเงื่อนไข และกลุ่ม องค์ประกอบแบบบล็อกและโทนบล็อกจะถูกปฏิเสธ ห้ามวางรายการในบรรทัดไว้ภายในลิงก์ที่มีอยู่ กล่องแก้ไขใช้ฟิลด์ของ Dee Wan ชุดเดิม และการแสดงผลสาธารณะใช้ตัวแสดงผลที่ escape ค่าเหมือนบล็อกปกติ

ประกาศข้อมูลเพิ่มเติมที่มีขอบเขตใน renderer_metadata และขอ renderer:metadata แต่ละประกาศมี key, label, meta_names, link_rels และ schema_types ผู้ดูแลต้องตรวจและให้สิทธิ์ตอนติดตั้ง สิทธิ์ใหม่หลัง อัปเกรดยังปิดอยู่จนกว่าจะตรวจ ปลั๊กอินส่งได้เฉพาะชื่อเมทาดาทา ชนิดลิงก์ (alternate, related, author, license) และชนิด JSON-LD ที่ประกาศไว้

ใช้ POST /content/:instanceId/renderer-metadata แบบมอบสิทธิ์ พร้อมส่วนหัว Idempotency-Key และเนื้อหา { delegation_id, locale, expected_version_id, contributions } แต่ละข้อมูลเพิ่มเติมมี key ที่ประกาศ และ meta, links, json_ld ตามต้องการ การเขียนจะแทนที่เฉพาะข้อมูลของปลั๊กอินนี้ในภาษาที่ระบุ สร้างเวอร์ชันเนื้อหาตามปกติ และยังเป็นฉบับร่างจนกว่าจะเผยแพร่ หากเวอร์ชันเก่า ไม่มีสิทธิ์ หรือเกินสิทธิ์ฟิลด์ หรือภาษา ระบบปฏิเสธการเขียนทั้งหมด

ข้อมูลอยู่ในเวอร์ชันเนื้อหาที่ยอมรับแล้ว การส่งข้อมูลสาธารณะไม่เรียกปลั๊กอิน ชื่อและคำอธิบาย SEO, canonical/robots, รหัสยืนยันไซต์ และที่อยู่ภาษาที่เผยแพร่มีลำดับความสำคัญสูงกว่า รวมข้อมูลตาม ID ปลั๊กอิน และคีย์ โดยแก้ชื่อหรือภาษาลิงก์ที่ซ้ำด้วยลำดับที่แน่นอน ระบบปฏิเสธมาร์กอัป ที่อยู่ที่รันโค้ดได้ บริบทตามอำเภอใจ และลิงก์ที่ไม่ถูกต้อง เว็บไซต์แบบจัดการ เว็บไซต์ที่สร้าง และพรีวิวแบบลงนามใช้ตัวแปลง SEO ร่วมกัน พรีวิวยังไม่อนุญาตให้เครื่องมือค้นหาจัดทำดัชนี

ประกาศ mail_templates และขอ mail:send ผู้ดูแลตรวจข้อความและวิธีเลือกผู้รับใน การตั้งค่า → ปลั๊กอิน → แม่แบบอีเมล ปลั๊กอินไม่ส่งอีเมลผู้รับหรือข้อความอิสระ

สำหรับแม่แบบ entry_author หรือ entry_assignee เรียก POST /api/plugin/v1/mail/send พร้อม Idempotency-Key และ {delegation_id, template, instance_id, variables} รายการต้องอยู่ในขอบเขตโมเดลและรายการของ installation และ delegation โดยผู้เริ่มส่งต้องยังอ่านรายการและมี site:plugins ใช้ delegation ของการทำงานกับรายการที่ตรงกันได้ ส่วนแบบระบุที่อยู่ยังต้องใช้ manage และไม่ส่ง instance_id

entry_author เลือกบัญชีที่สร้างรายการที่เลือกใน CMS (content_instance.created_by_id) ไม่ใช่ผู้เขียนบล็อกสาธารณะ โปรไฟล์ที่เชื่อมโยง หรือผู้แก้ไขล่าสุด ส่วน entry_assignee เลือกผู้ได้รับมอบหมายปัจจุบันในเวิร์กโฟลว์ของรายการนั้น (content_workflow.assigned_to_id) บัญชีต้องเปิดใช้งาน มีสิทธิ์เข้าถึงเว็บไซต์ในปัจจุบัน และอ่านรายการได้ ผู้ดูแลระบบสูงสุดที่เปิดใช้งานอาจเข้าเงื่อนไขได้โดยไม่ต้องมีบทบาทแยกสำหรับเว็บไซต์นี้ หากไม่มีผู้รับที่เข้าเงื่อนไขจะได้ validation_failed คำตอบไม่คืนอีเมล หากยังไม่ทราบผล ให้ลองคำขอเดิมด้วยคีย์เดิม ระบบใช้ข้อความและผู้รับที่เก็บไว้แล้ว และตรวจบัญชีเดิมอีกครั้ง การเปลี่ยนผู้ตรวจทานไม่เปลี่ยนปลายทางของการลองใหม่ ดูข้อจำกัดและผลลัพธ์ใน โปรโตคอล


ยังไม่เปิดตัว (pre-MVP) และยังไม่มีเวอร์ชันเสถียร
คอมมิตซอร์สโค้ด: e207e63239a627fbd2ae9c414ef7c6eb19fcfe5a (มีการแก้ไขที่ยังไม่คอมมิต)
วันที่สร้างเอกสาร (UTC): 2026-10-21T09:28:24.214Z
คอมมิตสำหรับอัปเกรด: ระบุไม่ได้: มีการแก้ไขที่ยังไม่คอมมิต. บันทึกการเปลี่ยนแปลง