เนื้อหา › อัปโหลดและ media
เริ่มต้น
เนื้อหา
Plugins
การดูแลระบบ
สูตรสำเร็จ
การอัปโหลดและ media
หน้านี้สอนอะไร
คลังสื่อ โฟลเดอร์และชนิดไฟล์ ขนาดรูป ข้อจำกัดของไฟล์ และการเก็บไฟล์บนดิสก์หรือ S3, Cloudflare R2 และ MinIO
ควรอ่านก่อน: Fields


ไฟล์ถูกเก็บไว้ใน collection media ที่มีมาในตัว เชื่อมโยงไฟล์ด้วย field ประเภท upload:
{ name: 'cover', type: 'upload' }ผู้แก้ไขเนื้อหาอัปโหลดไฟล์ได้ที่คลังสื่อในหน้า admin หรือจากตัวเลือกไฟล์ของ upload field เลือกหรือลากมาวางได้
ทีละหลายไฟล์ ระบบอัปโหลดพร้อมกันครั้งละ 3 ไฟล์ แต่ละไฟล์มีแถบความคืบหน้า ยกเลิกทีละไฟล์ได้ และลองใหม่เฉพาะไฟล์ที่ล้มได้
ไฟล์ที่ใหญ่กว่า upload.maxFileSize จะถูกปฏิเสธก่อนส่ง ไฟล์ที่เพิ่งอัปโหลดจะถูกเลือกไว้ พร้อมย้ายเข้าโฟลเดอร์ต่อ
หน้า Media แสดงเป็นกริดของการ์ด (รูป หรือ icon สีตามชนิดไฟล์) หรือสลับเป็นตารางได้ที่ปุ่มข้างช่องค้นหา หน้าของแต่ละไฟล์ แสดงตัวอย่างตามชนิด: รูป ตัวเล่นเสียงและวิดีโอ ตัวอ่าน PDF ของเบราว์เซอร์ และส่วนต้นของไฟล์ข้อความและ CSV (CSV เป็นตาราง ข้อความภาษาไทยจาก Excel อ่านเป็น Windows-874 ได้) ส่วน Word, Excel, PowerPoint และ zip แสดง icon กับปุ่ม ดาวน์โหลด และไม่ถูกส่งไปให้บริการภายนอกเปิดดู


หลายไฟล์: แกลเลอรี


hasMany ทำให้ upload field เก็บได้หลายไฟล์ เรียงตามที่ผู้แก้จัดไว้
{
name: 'gallery',
type: 'upload',
hasMany: true,
maxRows: 12, // และ minRows
mimeTypes: ['image/*'], // เฉพาะรูป: ช่องเลือกแสดงเฉพาะรูป และตรวจซ้ำตอนบันทึก
}ในหน้า admin ผู้แก้ลากหลายไฟล์มาวางพร้อมกันได้ เลือกจากคลังทีละหลายรูปได้ ลากเพื่อเรียงลำดับ (หรือใช้ปุ่มลูกศร)
และนำออกได้ การอ่านข้อมูลได้เอกสาร media ตามลำดับนั้น (ได้ id เมื่อใช้ depth: 0)
<ul class="gallery">
<li v-for="image in post.gallery" :key="image.id">
<img :src="image.sizes?.thumbnail?.url ?? image.url" :alt="image.alt" />
</li>
</ul>คำบรรยายของแต่ละรูปคือ alt ในคลังสื่อ ถ้าคำบรรยายเป็นของหน้านี้เท่านั้น ให้ใช้ array ที่มี upload กับ text
แทน ดูสูตร แกลเลอรีรูปภาพ
mimeTypes ใช้กับ upload แบบไฟล์เดียวได้ด้วย เช่น รูปปกที่ต้องเป็นรูปภาพ หรือ ['application/pdf'] สำหรับโบรชัวร์
โฟลเดอร์


เปิดใช้โฟลเดอร์เพื่อจัดระเบียบคลังสื่อ:
upload: { folders: true }หน้า Media จะมีต้นไม้โฟลเดอร์ เส้นทางของโฟลเดอร์ และโฟลเดอร์ย่อยของโฟลเดอร์ที่เปิดอยู่เหนือรายการไฟล์ โฟลเดอร์ใหม่ สร้างโฟลเดอร์ในโฟลเดอร์ที่เปิดอยู่ ไฟล์ที่อัปโหลดจะเข้าโฟลเดอร์นั้น ลากไฟล์ไปวางบนโฟลเดอร์ หรือเลือกหลายไฟล์แล้วกด ย้ายไป… เมนู ⋯ ของโฟลเดอร์ใช้เปลี่ยนชื่อ ย้าย หรือลบ การลบโฟลเดอร์ไม่ลบไฟล์ใด ของข้างในจะย้ายขึ้นไปที่โฟลเดอร์แม่ การค้นหาจะค้นในโฟลเดอร์ที่เปิดอยู่และโฟลเดอร์ย่อย ตัวเลือกไฟล์ของ upload field ก็เรียกดูโฟลเดอร์ได้ และเปิดที่โฟลเดอร์ล่าสุดที่ใช้
ไฟล์หนึ่งอยู่ได้โฟลเดอร์เดียว โฟลเดอร์เป็นข้อมูลใน collection media-folders (name, parent) ส่วนไฟล์มี
folder URL ของไฟล์ไม่เปลี่ยนเมื่อย้าย ผ่าน REST:
GET /api/cms/media?where[folder][equals]=4 ไฟล์ในโฟลเดอร์ 4
GET /api/cms/media?where[folder][exists]=false ไฟล์ที่ชั้นนอกสุด
POST /api/cms/media-folders { "name": "แบนเนอร์", "parent": 4 }การอัปโหลดรับ folder ด้วย (เป็นช่องในฟอร์ม หรือใส่ใน JSON คู่กับ url) ชื่อโฟลเดอร์ต้องไม่ซ้ำกันในโฟลเดอร์เดียวกัน
โดยไม่สนตัวพิมพ์เล็กใหญ่ โฟลเดอร์ใช้สิทธิ์ Media ของบทบาท และเห็นได้เฉพาะผู้ที่ล็อกอิน
ใครใช้โฟลเดอร์ได้บ้าง
เมื่อเปิดบทบาท (auth.rbac) admin เลือกได้ว่าใครใช้แต่ละโฟลเดอร์ได้ จากเมนูของโฟลเดอร์ ใครใช้ได้บ้าง
โฟลเดอร์จะใช้ตามโฟลเดอร์ที่อยู่ จนกว่าจะตั้งรายการของตัวเอง เมื่อตั้งแล้ว แต่ละบทบาทได้ระดับใดระดับหนึ่ง:
| ดู | เห็นโฟลเดอร์และไฟล์ข้างใน และเลือกไปใช้ในเนื้อหาได้ |
| แก้ไข | อัปโหลด เปลี่ยนชื่อ ย้าย และลบไฟล์ในโฟลเดอร์ได้ด้วย |
| จัดการ | สร้าง เปลี่ยนชื่อ ย้าย และลบโฟลเดอร์ย่อยได้ด้วย |
บทบาทที่ไม่อยู่ในรายการจะไม่เห็นโฟลเดอร์นั้น ชั้นนอกสุดเปิดให้ทุกบทบาทที่ใช้ Media ได้


- โฟลเดอร์ทำได้แค่จำกัดสิ่งที่บทบาททำกับ Media ได้ใน Settings → Roles ให้แคบลง บทบาทที่ไม่มีสิทธิ์ลบ Media ลบไฟล์ไม่ได้ในทุกโฟลเดอร์
- admin ทำได้ทุกอย่าง และมีแต่ admin ที่ตั้งได้ว่าใครใช้โฟลเดอร์ได้ การเปลี่ยนจะอยู่ในaudit log เป็น เปลี่ยนสิทธิ์โฟลเดอร์
- API key ใช้สิทธิ์ Media ของตัวเอง โฟลเดอร์ไม่จำกัด API key เว้นแต่ key นั้นจำกัดไว้เฉพาะบางโฟลเดอร์
- บทความเก็บไฟล์จากโฟลเดอร์ที่ผู้แก้มองไม่เห็นได้ ช่องนั้นจะแสดงว่า "ไฟล์ที่ไม่มีสิทธิ์ดู" และไฟล์ยังอยู่จนกว่าจะเปลี่ยนหรือนำออก
ใช้แบ่งงานของทีม
ไฟล์ในโฟลเดอร์อื่นยังเป็นสาธารณะ คนที่มีลิงก์ของไฟล์เปิดได้ และคำขอที่ไม่ได้ล็อกอินยังเห็นรายการไฟล์เหมือนเดิม ไฟล์ที่ต้องเป็นความลับให้ใช้โฟลเดอร์ส่วนตัว
โฟลเดอร์ส่วนตัว
admin ติ๊ก ส่วนตัว ใน ใครใช้ได้บ้าง ของโฟลเดอร์ ไฟล์และโฟลเดอร์ย่อยข้างในจะเป็นส่วนตัว เก็บแยกจากไฟล์สาธารณะ
และเสิร์ฟผ่าน API ที่ /api/cms/media/private/<name> เท่านั้น:
- ให้ผู้ที่ล็อกอินและมีสิทธิ์ดูโฟลเดอร์นั้น (ตามบทบาทและสิทธิ์ของโฟลเดอร์)
- ให้ทุกคนที่มี ลิงก์ที่เซ็น จากโค้ดของคุณ จนกว่าลิงก์จะหมดอายุ
คำขอที่ไม่ได้ล็อกอินจะไม่เห็นไฟล์ส่วนตัวในรายการ และรูปปกที่เป็นไฟล์ส่วนตัวจะไม่ถูกส่งให้ผู้เยี่ยมชม การย้ายไฟล์เข้าหรือออก จากโฟลเดอร์ส่วนตัว (หรือเปลี่ยนโฟลเดอร์ให้เป็นส่วนตัวหรือสาธารณะ) จะย้ายไฟล์ไปอีกที่เก็บและได้ URL ใหม่ admin จะถามก่อน พร้อมบอกว่ามีกี่เอกสารที่ใช้ไฟล์นั้น
// ลิงก์ดาวน์โหลดให้สมาชิก ใช้ได้ 1 ชั่วโมง (ค่าเริ่มต้น สูงสุด 7 วัน)
const link = cms.signedMediaURL(report, { expiresIn: '1h' })
// รูปย่อ:
cms.signedMediaURL(photo, { expiresIn: '30m', size: 'thumbnail' })ลิงก์ใช้ไม่ได้เมื่อหมดอายุ เมื่อเปลี่ยน secret หรือเมื่อไฟล์ไม่เป็นส่วนตัวแล้ว ไฟล์สาธารณะได้ URL ปกติของมัน
ไฟล์ส่วนตัวเก็บที่ไหน บนดิสก์ในเครื่องและ Netlify Blobs ซึ่งไม่เสิร์ฟอะไรเป็นสาธารณะ เก็บรวมกับไฟล์อื่นได้ ส่วน storage ที่มี
URL สาธารณะ (S3 ที่ตั้ง publicUrl, Vercel Blob แบบ public) ต้องตั้ง upload.privateStorage ก่อนจึงทำโฟลเดอร์ส่วนตัวได้:
upload: {
folders: true,
storage: vercelBlobStorage(),
privateStorage: vercelBlobStorage({ access: 'private', prefix: 'private/' }),
}

upload field ที่ระบุโฟลเดอร์
ชี้ upload field ไปที่โฟลเดอร์ด้วย key ตัวเลือกไฟล์จะเปิดที่นั่น และไฟล์ที่อัปโหลดจากช่องนี้จะเข้าโฟลเดอร์นั้น ถ้ายังไม่มี โฟลเดอร์ ระบบสร้างให้ที่ชั้นนอกสุดเมื่อใช้ครั้งแรก เปลี่ยนชื่อหรือย้ายโฟลเดอร์ใน admin ได้โดย key ไม่เปลี่ยน
{ name: 'banner', type: 'upload', folder: 'banners' }
// เลือกได้เฉพาะไฟล์ในโฟลเดอร์นั้น (และโฟลเดอร์ย่อย) ตรวจตอนบันทึก:
{ name: 'logo', type: 'upload', folder: 'brand', folderOnly: true }การเปิดโฟลเดอร์จะเพิ่มตาราง media-folders และคอลัมน์ folder กับ private ของ media:
npx easy-cms migrate:create media-foldersการอัปโหลดจากโค้ด
const media = await cms.upload({ data: bytes, name: 'photo.jpg' }, { alt: 'A photo' })
media.url // "/api/cms/media/file/photo-3f9a2c1b.jpg"ผ่าน REST: POST /api/cms/media ด้วย multipart/form-data โดยใส่ไฟล์ใน file และ field อื่น
(เช่น alt) เป็น text part
จากลิงก์
กำลังย้ายเนื้อหาจากเว็บอื่นอยู่ใช่ไหม ให้ server ดาวน์โหลดไฟล์จากลิงก์แทนได้:
upload: {
fromURL: {
// host ที่ดึงไฟล์ได้: ชื่อตรงตัว, `*.example.com` สำหรับ subdomain หรือ `*` สำหรับทุก host
allowedHosts: ['images.oldsite.com', '*.cdn.example.com'],
},
},คลังสื่อ ตัวเลือกสื่อ และแกลเลอรีจะมีช่อง จากลิงก์ ผู้แก้ไขยังวางลิงก์ (หรือหลายลิงก์ บรรทัดละลิงก์) ลงใน พื้นที่อัปโหลด หรือลากรูปจากหน้าเว็บอื่นมาวางได้ด้วย ในโค้ด:
const media = await cms.uploadFromURL('https://images.oldsite.com/2024/beach.jpg', { alt: 'The beach' })ผ่าน REST: POST /api/cms/media ด้วย JSON { "url": "https://…", "alt": "…" }
จากนั้นไฟล์ถูกตรวจเหมือนการอัปโหลดทั่วไป: ชนิดไฟล์จากเนื้อไฟล์ maxFileSize และสิทธิ์สร้าง media ชื่อไฟล์มาจาก
Content-Disposition ของ server ปลายทาง ถ้าไม่มีใช้จากลิงก์
ลิงก์เข้าถึงเครือข่ายของคุณได้
server เป็นผู้ส่ง request จากภายในเครือข่ายของคุณ เพื่อไม่ให้ลิงก์เข้าถึงสิ่งที่ไม่ได้เปิดสาธารณะ server จะปฏิเสธ:
- scheme อื่นนอกจาก
httpและhttpsและลิงก์ที่มีรหัสผ่าน - ที่อยู่ในเครือข่ายภายใน:
localhost,10.x,172.16–31.x,192.168.x, link-local (169.254.xที่ cloud ใช้ให้ข้อมูล metadata) รวมถึงของ IPv6 ชื่อ host ตรวจหลัง resolve DNS และตรวจซ้ำทุกครั้งที่ redirect (ไม่เกินสามครั้ง) - host ที่ไม่อยู่ใน
allowedHostsรวมถึงหลัง redirect - การดาวน์โหลดที่นานเกิน 15 วินาทีหรือใหญ่กว่า
maxFileSize
allowPrivate: true ยกเลิกกฎเรื่องที่อยู่ภายใน เช่น สำหรับ server เก็บรูปในอินทราเน็ต ตั้งเฉพาะเมื่อทุกคนที่อัปโหลด
ได้มีสิทธิ์เข้าถึงเครือข่ายภายในของคุณอยู่แล้ว
cms.uploadFromURL() ที่ไม่มี user คือโค้ดของคุณเอง (เช่น script ย้ายข้อมูล) จึงไม่ต้องตั้ง upload.fromURL
และไม่สน allowedHosts แต่ยังปฏิเสธที่อยู่ภายในเว้นแต่ตั้ง allowPrivate ถ้าเรียกพร้อม user และ
overrideAccess: false แบบที่ REST API ทำ ต้องผ่านทั้งสองข้อ
ชนิดไฟล์
Easy CMS รู้จักไฟล์เหล่านี้จากเนื้อหา:
| ชนิด | |
|---|---|
| รูป | JPEG, PNG, GIF, WebP, AVIF, SVG |
| เอกสาร | PDF; Word, Excel, PowerPoint (docx, xlsx, pptx); OpenDocument (odt, ods, odp); CSV; ข้อความ |
| เสียง | MP3, WAV, Ogg, M4A |
| วิดีโอ | MP4, WebM, MOV |
| ไฟล์บีบอัด | zip |
ไฟล์ Office แบบเก่า (doc, xls, ppt) ยังไม่รองรับ อนุญาตชนิดที่ต้องการด้วย MIME type, type/* หรือชื่อกลุ่ม:
upload: {
// documents: PDF, Word, Excel, PowerPoint, OpenDocument, CSV และข้อความ
// office: docx, xlsx, pptx · archives: zip
mimeTypes: ['image/*', 'documents', 'audio/*', 'video/*'],
}mimeTypes ของ upload field ใช้ชื่อเดียวกันได้ เช่น { type: 'upload', mimeTypes: ['office'] }
ไฟล์ใหญ่
โฮสต์จำกัดขนาดของแต่ละ request: ราว 4.5 MB บน Vercel และ 6 MB บน Netlify เมื่อใช้ S3 (R2, MinIO) หรือ Vercel Blob
admin จะส่งไฟล์ที่ใหญ่กว่า 4 MB จาก browser ตรงไปที่ storage จึงจำกัดด้วย upload.maxFileSize อย่างเดียว:
POST /api/cms/media/uploadsพร้อม{ "name", "size", "type", ...fields }ตรวจว่าใครอัปอะไรไปที่ไหนเหมือนการอัปโหลดปกติ แล้วคืนticketที่เซ็นไว้ และที่ส่งไฟล์upload: { url, method, headers }(ใช้ได้ 15 นาที) หรือupload: nullเมื่อ storage รับไฟล์ตรงไม่ได้ (ให้ส่งที่POST /api/cms/mediaแทน)- browser ส่งไฟล์ไปที่นั่น
POST /api/cms/media/uploads/completeพร้อม{ "ticket" }ตรวจไฟล์ใน storage ทั้งขนาดและชนิดจากเนื้อหา แล้วสร้าง เอกสาร media ไฟล์ที่ไม่ผ่านจะถูกลบ
จากโค้ด: cms.createUpload({ name, size }, fields, options) และ cms.completeUpload(ticket)
- S3, R2, MinIO: bucket ต้องตั้ง CORS ให้ origin ของ admin ใช้
PUTพร้อม headercontent-typeได้ - Vercel Blob: ไม่ต้องตั้งอะไร
- Netlify Blobs และดิสก์ในเครื่อง รับไฟล์ผ่าน server บน Netlify ได้ราว 6 MB
ไฟล์ที่ส่งแล้วแต่ไม่ได้ complete (ปิดหน้าไประหว่างนั้น) จะค้างอยู่ใน storage โดยไม่มีที่ไหนลิงก์ถึง บน S3 ตั้ง lifecycle rule ลบได้
สิ่งที่เกิดขึ้นกับไฟล์
- ตรวจสอบประเภทไฟล์จากเนื้อหา ไม่ใช่จากชื่อไฟล์หรือ Content-Type ที่ client ส่งมา ประเภทต้อง
ตรงกับ
upload.mimeTypes(ค่าเริ่มต้นimage/*,application/pdf) มีแค่ CSV ที่แยกจากข้อความธรรมดาด้วยชื่อไฟล์ - ขนาด ถูกจำกัดด้วย
upload.maxFileSize(ค่าเริ่มต้น 10 MB) ไฟล์ที่ใหญ่กว่าจะได้413 - ชื่อไฟล์ จะถูกเปลี่ยนเป็น
<name>-<random>.<detected extension>จึงปลอดภัยและไม่ซ้ำกัน และ ชื่อภาษาไทยยังคงอ่านออก - รูปภาพ จะได้
widthและheightหากติดตั้งsharpไว้upload.imageSizesจะสร้างสำเนาที่ปรับขนาดแล้วไว้ในsizes - การลบเอกสาร media จะลบไฟล์ของเอกสารนั้นด้วย
upload: {
maxFileSize: 5 * 1024 * 1024,
mimeTypes: ['image/*'],
imageSizes: [
{ name: 'thumbnail', width: 400, height: 300 },
{ name: 'wide', width: 1600 },
],
}เอกสาร media มีหน้าตาดังนี้:
{
"id": 7,
"filename": "photo-3f9a2c1b.jpg",
"originalName": "photo.jpg",
"mimeType": "image/jpeg",
"filesize": 184213,
"width": 2400,
"height": 1600,
"alt": "A photo",
"url": "/api/cms/media/file/photo-3f9a2c1b.jpg",
"sizes": { "thumbnail": { "width": 400, "height": 300, "url": "…" } }
}หลังอัปโหลดแล้ว แก้ไขได้เฉพาะ alt (และ field ที่คุณเพิ่มเอง) metadata ของ media ทุกคนอ่านได้ และ
ผู้ใช้ที่เข้าสู่ระบบแล้วเขียนได้ ประกาศ collection media เองเพื่อเปลี่ยนพฤติกรรมนี้หรือเพิ่ม field
การเสิร์ฟไฟล์และการจัดเก็บ
ไฟล์ถูกเสิร์ฟที่ /api/cms/media/file/<name> พร้อม caching ระยะยาวและ Content Security Policy
แบบ sandbox ทำให้ SVG ที่อัปโหลดมาไม่สามารถรันสคริปต์ได้ ส่วน PDF เสิร์ฟโดยไม่มี sandbox เพราะเบราว์เซอร์ไม่แสดง PDF
ใน sandbox และตัวอ่าน PDF ของเบราว์เซอร์ทำงานแยกจากเว็บของคุณ ตั้งค่า serverURL เพื่อให้ได้ URL แบบเต็ม
การจัดเก็บเริ่มต้นคือดิสก์ในเครื่อง (upload.dir ค่าเริ่มต้น uploads/) ซึ่งต้องใช้ filesystem
ที่คงอยู่ถาวร บนโฮสต์แบบ serverless (Vercel, Netlify) และใน container ที่ไม่มี volume ให้ใช้ S3
S3, Cloudflare R2 และ MinIO
npm install @easy-cms/storage-s3pnpm add @easy-cms/storage-s3yarn add @easy-cms/storage-s3bun add @easy-cms/storage-s3import { s3Storage } from '@easy-cms/storage-s3'
export default defineConfig({
// …
upload: {
storage: s3Storage({ bucket: 'my-site-media', region: 'eu-central-1' }),
},
})ข้อมูลรับรองอ่านจาก AWS_ACCESS_KEY_ID และ AWS_SECRET_ACCESS_KEY (และ AWS_SESSION_TOKEN,
AWS_REGION) เว้นแต่คุณจะส่ง accessKeyId / secretAccessKey มาเอง ค่าเหล่านี้ถูกอ่านตอนที่ CMS
เริ่มทำงาน ดังนั้นการ build โดยไม่มีค่าเหล่านี้ก็ยังทำได้
| ตัวเลือก | ค่าเริ่มต้น | |
|---|---|---|
bucket | จำเป็น | ชื่อ bucket |
region | AWS_REGION จากนั้น us-east-1 | auto สำหรับ Cloudflare R2 |
endpoint | AWS S3 | สำหรับบริการที่เข้ากันได้กับ S3 เช่น https://<account>.r2.cloudflarestorage.com |
prefix | ไม่มี | โฟลเดอร์สำหรับ object เช่น media/ |
publicUrl | ไม่มี | เสิร์ฟไฟล์จากที่นี่ (CDN หรือ public bucket) แทนการเสิร์ฟผ่าน API |
forcePathStyle | true เมื่อมี endpoint | การอ้างอิงแบบ <endpoint>/<bucket>/<key> |
Cloudflare R2:
s3Storage({
bucket: 'my-site-media',
region: 'auto',
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
})access key ต้องมีสิทธิ์ s3:PutObject, s3:GetObject และ s3:DeleteObject บน bucket
Private bucket (ค่าเริ่มต้น) หากไม่มี publicUrl ไฟล์จะถูก stream ผ่าน
/api/cms/media/file/<name> พร้อม header สำหรับ caching และ sandbox แบบเดียวกับการจัดเก็บในเครื่อง
bucket จึงยังคงเป็น private วาง CDN ไว้หน้าเว็บไซต์เพื่อไม่ต้องดึงไฟล์จาก S3 ทุกครั้งที่มี
request
Public bucket เมื่อมี publicUrl URL ของ media จะชี้ไปที่ bucket หรือ CDN โดยตรง และ server
ของคุณไม่เกี่ยวข้อง ให้เสิร์ฟจาก โดเมนที่ต่างจาก เว็บไซต์ของคุณ เพราะ Content Security Policy
แบบ sandbox ไม่ได้ถูกใช้ที่นั่น มิฉะนั้น SVG ที่อัปโหลดมาอาจรันสคริปต์ภายใต้ origin
ของเว็บไซต์คุณได้
Vercel Blob
บน Vercel ซึ่งดิสก์ไม่เก็บไฟล์ถาวร ให้เชื่อม Blob store กับโปรเจกต์ (Storage → Blob) ระบบจะตั้ง BLOB_READ_WRITE_TOKEN ให้
import { vercelBlobStorage } from '@easy-cms/storage-vercel-blob'
upload: { storage: vercelBlobStorage() }, // ไฟล์สาธารณะบน CDN ของ BlobvercelBlobStorage({ access: 'private', prefix: 'backups/' }) เก็บไฟล์แบบส่วนตัว (เสิร์ฟผ่าน API) เช่น สำหรับ
backups.storage
Netlify Blobs
บน Netlify ใช้ Blobs ได้โดยไม่ต้องตั้งค่า:
import { netlifyBlobsStorage } from '@easy-cms/storage-netlify-blobs'
upload: { storage: netlifyBlobsStorage() }, // เสิร์ฟผ่าน <api>/media/file/…นอก runtime ของ Netlify (สคริปต์ หรือ host อื่น) ให้ส่ง siteID และ personal access token
การจัดเก็บแบบกำหนดเอง
implement StorageAdapter (put, get, delete และ url กับ init ที่ไม่บังคับ) แล้วส่งเป็น
upload.storage สำหรับไฟล์ใหญ่ที่ส่งตรงจาก browser ให้เพิ่ม uploadURL(key, { contentType, size, expiresIn }) (ส่งไฟล์ไปที่ไหน
อย่างไร) และ getStart(key, bytes) (ส่วนต้นของไฟล์และขนาด โดยไม่ต้องอ่านทั้งไฟล์)
ขั้นต่อไป
- Rich text: รูปใน rich text
- Backup และการอัปเกรด: สำรองไฟล์ที่อัปโหลด