การเขียนปลั๊กอิน
ปลั๊กอินเป็นเซอร์วิสที่ 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 ทั้งหมดเป็นข้อมูลสังเคราะห์และจงใจ ให้ใช้ไม่ได้
- Protocol v1 — ทุก route, header, body, outcome และกฎการ retry
- ความปลอดภัยของปลั๊กอิน — token, delegation, ขอบเขต และ threat model
- การทดสอบปลั๊กอิน — checklist ของ contract และชุดติดตั้งแบบสองเซอร์วิสบนเครื่อง local
- แบบที่เครื่องอ่านได้:
schemas/manifest-v1.schema.json,schemas/protocol-v1.schema.json
สิ่งที่คุณต้องรับผิดชอบ
หัวข้อที่มีชื่อว่า “สิ่งที่คุณต้องรับผิดชอบ”| คุณเป็นเจ้าของ | Dee Wan เป็นเจ้าของ |
|---|---|
| เซอร์วิสของคุณ ฐานข้อมูล นาฬิกา และการ retry ของมัน | เนื้อหา, เวอร์ชัน, กฎ workflow, สิทธิ์, audit |
| browser session ของคุณเองและ UI ของคุณเอง | transition นั้นถูกต้องตามกฎหรือไม่ และการนำไปใช้แบบ atomic |
| การตัดสินใจว่าจะร้องขอเมื่อใด | การตัดสินใจว่าคำตอบคือใช่หรือไม่ |
| การลบข้อมูลของคุณเอง | การเพิกถอนสิทธิ์เข้าถึงของคุณ |
Dee Wan ไม่มีวันเชื่อคำกล่าวอ้างของปลั๊กอินว่า transition ได้รับอนุญาต ว่าเวอร์ชันเป็นปัจจุบัน หรือ ว่าการยกเลิกการเผยแพร่ปลอดภัย สิ่งเหล่านั้นถูกตรวจซ้ำภายในการเปลี่ยนแปลง (mutation)
1. ให้บริการ manifest
หัวข้อที่มีชื่อว่า “1. ให้บริการ manifest”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_unsupportedrequested_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และreleasescore render ลิงก์พร้อมป้ายชื่อของตัวเองสำหรับแต่ละรายการบน installation ใน การตั้งค่า → ปลั๊กอิน และเสนอแต่ละรายการข้างปฏิทินบรรณาธิการบน แดชบอร์ด และรายการ เนื้อหา ปลั๊กอินไม่ได้กำหนดป้ายชื่อmanagement_urlอาจมี{installation_id}และใช้สำหรับการนำทางเท่านั้น มันไม่มีวันได้รับ credential อายุยาวใน URLui_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มีoptions1 ถึง 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 หนึ่งฟิลด์เป็นแบบฟอร์มเล็ก ๆ ระบุmembers1 ถึง 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 เดียวของ manifestrequiresระบุ capability ที่คุณขอไว้ด้วย ระหว่างที่สิทธิ์ ยังขาดอย่างใดอย่างหนึ่ง การดำเนินการนั้นจะไม่ถูกเสนอinputมีได้ไม่เกิน 8 ช่อง:text,number,booleanหรือselectDee 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และseoDee 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และไม่มีdestructivelabelของมันคือหัวคอลัมน์ - การแลกรหัสคืน
$defs/column_requestได้แก่โมเดลและรหัสของแถวไม่เกิน 50 แถวที่คนนั้นอ่านได้ ไม่มีค่า อ่านสิ่งที่ต้องใช้ ผ่านสิทธิ์ของคุณเอง ตอบด้วยบล็อกcellsหนึ่งบล็อก มีไม่เกินหนึ่งเซลล์ต่อรหัสที่ถูกถาม{ "id", "text" }บวกtoneสำหรับ ป้ายสี หรือurlแบบ https สำหรับลิงก์ ข้อความยาวไม่เกิน 80 ตัวอักษร เซลล์ของรหัสอื่น รหัสซ้ำ ลิงก์ที่ไม่มีurlแบบ https หรือurlบนป้ายสี ทำให้คำตอบทั้งหมดใช้ไม่ได้ รายการว่างเป็นคำตอบที่ใช้ได้ - คอลัมน์ไม่สร้างการมอบสิทธิ์ ไม่ถูกแสดงรายการหรือรันเป็นการดำเนินการทั่วไป ไม่เขียนแถวบันทึกการตรวจสอบ และไม่เก็บคำตอบที่เล่นซ้ำ อย่าเก็บอะไรเมื่อตอบ
- การทำงานกับแถวคือการดำเนินการ
entryแบบ interactive ที่มีlist_action: trueDee Wan รันมันหนึ่งครั้งต่อแต่ละแถวจากไม่เกิน 25 แถวที่เลือก แต่ละแถวมีการเรียก การมอบสิทธิ์ คีย์ idempotency และรายการตรวจสอบของตัวเอง จึงตอบแต่ละแถวเหมือนการดำเนินการentry
บล็อก rich text
หัวข้อที่มีชื่อว่า “บล็อก rich text”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 treerender.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 source
หัวข้อที่มีชื่อว่า “Media source”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อีกครั้งและ GETdownload_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 ไม่ได้ประกาศถูกปฏิเสธด้วย 422media_source_mode_unsupported - การเรียกดูไม่เปลี่ยนอะไรและไม่ถูกบันทึกตรวจสอบ ผู้ใช้ต้องมี
media:uploadการปิดการติดตั้งหรือเอาmedia:sourceออกจากสิทธิ์จะซ่อน source ทันที - management API มีความสามารถเดียวกัน:
listMediaSources,browseMediaSourceและimportMediaSourceItem(scopemedia:writeเนื้อหาitem_idและmode) ดู management API plugins/media-source-referenceคือตัวอย่างเล็ก ๆ ที่สมบูรณ์พร้อมเทสต์ ชุด conformance ตรวจ source ด้วย subjectmediaSource(ดู การทดสอบปลั๊กอิน)
Hook ก่อนการดำเนินการ
หัวข้อที่มีชื่อว่า “Hook ก่อนการดำเนินการ”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" }ผู้อัปโหลดจะเห็นข้อความของคุณ (422upload_hook_refused) - ตอบความคิดเห็นด้วย
$defs/hook_comment_reply:{ "decision": "approve" },{ "decision": "hold" }(ความคิดเห็นรอผู้ดูแล) หรือ{ "decision": "reject" }(เก็บเป็นสแปม หรือเป็น refused เมื่อมี"as": "refused") - ตอบภายใน 5 วินาที hook ที่หมดเวลา เข้าถึงไม่ได้ ตอบนอกชุดคำตอบที่กำหนด หรือถูกปิดระหว่างทำงาน จะไม่อนุมัติเด็ดขาด: การอัปโหลดถูกปฏิเสธ (503
upload_hook_unavailableไม่บันทึกอะไร) และความคิดเห็นยังรอการตรวจ - คำถามมี retry identity ที่คงที่ การอัปโหลดซ้ำหรือความคิดเห็นที่ส่งซ้ำจะได้คำตอบแรกที่ยอมรับแล้วของคุณกลับมานาน 24 ชั่วโมงโดยไม่เรียกใหม่ คำถามที่ล้มเหลวจะถูกถามใหม่
2. รับการส่งมอบตอน activation
หัวข้อที่มีชื่อว่า “2. รับการส่งมอบตอน activation”การติดตั้งคือการส่งมอบระหว่างสองฝ่าย ดังนั้นเซอร์วิสของคุณต้องมีหนึ่ง 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 ของคุณต้องทำ:
- ใช้
activation_codeครั้งเดียวเท่านั้น และปฏิเสธโค้ดที่ไม่รู้จัก ใช้ไปแล้ว หรือหมดอายุ นี่คือ สิ่งเดียวที่พิสูจน์ว่าผู้เรียกคือผู้ดูแลที่คุณให้โค้ดไป - ตรวจว่า
protocol_versionเป็นเวอร์ชันที่คุณรองรับ และdee_wan_base_urlเป็น origin ที่คุณยอมรับ - เก็บ
tokenเป็นข้อมูลลับของคุณสำหรับการติดตั้งปลั๊กอินรายการนี้ โดยเข้ารหัสข้อมูลที่จัดเก็บ ไม่มีวันบันทึกลง log ไม่มีวันอยู่ใน URL หรือ error body - ตอบ 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 เดิม
3. Review แล้วจึงติดตั้ง
หัวข้อที่มีชื่อว่า “3. Review แล้วจึงติดตั้ง”ผู้ดูแลที่มี site:plugins จะ review manifest ก่อน การ review ไม่เก็บสิ่งใดไว้ — มัน
มีไว้เพื่อให้การอนุมัติทำโดยรู้ข้อมูล

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_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" }}ไม่มี token และไม่มี hash ปรากฏใน response นั้น ในการอ่านครั้งต่อ ๆ ไป หรือใน audit entry ที่การติดตั้ง เขียนไว้ เซอร์วิสของคุณถือสำเนาเพียงชุดเดียว
4. จำกัด grant ให้แคบลง
หัวข้อที่มีชื่อว่า “4. จำกัด grant ให้แคบลง”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_at": "2026-01-01T00:00:00.000Z", "updated_at": "2026-01-01T00:00:00.000Z" }}การขอ capability ที่อยู่นอก manifest ที่เก็บไว้คือ 400 invalid_capabilities grant สามารถ
ขยายกลับได้ในภายหลัง แต่ไม่มีวันเกินสิ่งที่ manifest ที่เก็บไว้ร้องขอ
การส่ง release ใหม่
หัวข้อที่มีชื่อว่า “การส่ง release ใหม่”การเผยแพร่ manifest ใหม่ที่ URL ของคุณไม่เปลี่ยนอะไรเลยใน installation ที่มีอยู่ การกระทำที่ชัดเจนสองอย่าง โดยผู้ดูแลเท่านั้นที่เปลี่ยน:
- ยอมรับ 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 แคบลงได้ แต่ไม่มีวันขยายมัน - ขยาย grant หาก release ร้องขอ capability ใหม่ ด้วย
PUT /api/plugins/:id/grantตามข้างต้น ตอนนี้ทำได้เพราะ manifest ที่เก็บไว้ร้องขอมันแล้ว
ดังนั้น release ที่ต้องการ capability ใหม่ต้องระบุไว้ใน release notes ของตัวเอง: จนกว่า
ผู้ดูแลจะทำทั้งสองขั้นตอน เซอร์วิสของคุณจะยังทำงานด้วย grant เดิม และคำสั่งที่
ต้องการ capability ใหม่จะได้คำตอบ capability_refused
5. เปิด UI ของคุณผ่าน delegated launch
หัวข้อที่มีชื่อว่า “5. เปิด UI ของคุณผ่าน delegated launch”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"], }}ตอนนี้ให้สร้าง 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สำหรับยกเลิกการเผยแพร่ นั่นคือ pinworkflow.revisiontoและ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 เดิมซ้ำ ตัวอย่างด้านล่างเผยแพร่ฉบับร่างปกติ
7. ส่งคำสั่ง
หัวข้อที่มีชื่อว่า “7. ส่งคำสั่ง”การเปลี่ยนแปลงหนึ่งครั้ง โดยมีเงื่อนไขตามทุกสิ่งที่คุณ 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 แสดง ชื่อปลั๊กอินของคุณเป็นผู้กระทำ
8. Uninstall
หัวข้อที่มีชื่อว่า “8. Uninstall”การ 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
คอมมิตสำหรับอัปเกรด: ระบุไม่ได้: มีการแก้ไขที่ยังไม่คอมมิต. บันทึกการเปลี่ยนแปลง