$npx -y skills add caffeinelabs/skills --skill extension-object-storageGeneral file/object storage, such as for images, videos, files, documents and other bulk data. Perfect fit for image galleries, video galleries, and other file or object management. Supports large files beyond IC limit, with browser-cached HTTP URL access.
| 1 | # Object Storage |
| 2 | Object storage extension for [Caffeine AI](https://caffeine.ai?utm_source=caffeine-skill&utm_medium=referral). |
| 3 | |
| 4 | ## Overview |
| 5 | |
| 6 | This skill adds off-chain file/object storage with on-chain references. The `MixinObjectStorage` mixin provides infrastructure for file operations; you track uploaded files in your own data structures using `Storage.ExternalBlob`. |
| 7 | |
| 8 | ## Required Setup Checklist |
| 9 | |
| 10 | All four steps are mandatory. Skipping any one causes `403 Forbidden: Invalid payload` at upload time. |
| 11 | |
| 12 | 1. **mops dependency** — add `caffeineai-object-storage` to `mops.toml` under `[dependencies]`. |
| 13 | 2. **Mixin invocation** — `include MixinObjectStorage()` in `main.mo` (imported from `"mo:caffeineai-object-storage/Mixin"`). |
| 14 | 3. **Storage.ExternalBlob types** — every data field that represents a file MUST use `Storage.ExternalBlob`, never `Text`. |
| 15 | 4. **Frontend npm package** — `@caffeineai/object-storage` installed and `ExternalBlob.fromBytes(bytes, file.type, file.name)` used at the call site. |
| 16 | |
| 17 | CRITICAL: The frontend package (`@caffeineai/object-storage`) does NOT work without the backend mops package (`caffeineai-object-storage`). Installing only the npm package and not the mops package causes silent upload failures (403 from the storage gateway). You MUST install both together. |
| 18 | |
| 19 | # Backend |
| 20 | |
| 21 | File content is stored off-chain. The backend manages references to external files using the `Storage.ExternalBlob` type from `mo:caffeineai-object-storage/Storage`. The frontend handles the actual upload/download; the backend only stores the reference. |
| 22 | |
| 23 | CRITICAL: ANY data field that represents a file, image, photo, document, or media MUST use `Storage.ExternalBlob` as its type -- NEVER `Text`. Using `Text` breaks the upload/download proxy. Method parameters that accept file uploads MUST also use `Storage.ExternalBlob`, not `Text`. |
| 24 | |
| 25 | Correct: |
| 26 | ``` |
| 27 | blob : Storage.ExternalBlob |
| 28 | ``` |
| 29 | |
| 30 | Wrong: |
| 31 | ``` |
| 32 | blobId : Text |
| 33 | imageUrl : Text |
| 34 | fileRef : Text |
| 35 | ``` |
| 36 | |
| 37 | ## Module API |
| 38 | |
| 39 | The only type you use from `mo:caffeineai-object-storage/Storage` is `ExternalBlob` (which is `Blob`). All other functions in `Storage.mo` are internal infrastructure used by `MixinObjectStorage` -- do not call them directly. |
| 40 | |
| 41 | ## Setup in main.mo |
| 42 | |
| 43 | `include MixinObjectStorage()` MUST be placed in `main.mo`, not in a custom mixin file. Your own file-tracking logic goes in a separate mixin. |
| 44 | |
| 45 | ```motoko filepath=src/backend/main.mo |
| 46 | import MixinObjectStorage "mo:caffeineai-object-storage/Mixin"; |
| 47 | import Storage "mo:caffeineai-object-storage/Storage"; |
| 48 | |
| 49 | actor { |
| 50 | include MixinObjectStorage(); |
| 51 | |
| 52 | // Track file references |
| 53 | type Data = { |
| 54 | id: Text; |
| 55 | blob: Storage.ExternalBlob; |
| 56 | name: Text; |
| 57 | // other metadata |
| 58 | }; |
| 59 | }; |
| 60 | ``` |
| 61 | |
| 62 | ## Wrong: Do NOT Implement Storage Methods Yourself |
| 63 | |
| 64 | NEVER create your own implementation of `_immutableObjectStorageCreateCertificate` or any other `_immutableObjectStorage*` method. These are platform-reserved method names provided exclusively by the `MixinObjectStorage` mixin from the mops package. Hand-written implementations produce wrong return types and cause `403 Forbidden: Invalid payload` at upload time. |
| 65 | |
| 66 | Wrong — inline stub in main.mo: |
| 67 | ```motoko filepath=wrong.mo |
| 68 | // WRONG: Do not write this yourself |
| 69 | public shared func _immutableObjectStorageCreateCertificate(fileHash : Text) : async Blob { |
| 70 | CertifiedData.set(Blob.fromArray(hashBytes)); |
| 71 | Blob.fromArray([]) |
| 72 | }; |
| 73 | ``` |
| 74 | |
| 75 | Wrong — custom mixin file mimicking the platform shape: |
| 76 | ```motoko filepath=wrong-mixin.mo |
| 77 | // WRONG: Do not create src/backend/mixins/object-storage-api.mo |
| 78 | import ObjectStorageMixin "mixins/object-storage-api"; |
| 79 | include ObjectStorageMixin(); |
| 80 | ``` |
| 81 | |
| 82 | The correct import path is ALWAYS `"mo:caffeineai-object-storage/Mixin"` — a mops package, never a relative path. Any relative import like `"mixins/object-storage-api"` or `"./ObjectStorage"` is wrong. |
| 83 | |
| 84 | The correct signature produced by the platform mixin is: |
| 85 | ``` |
| 86 | _immutableObjectStorageCreateCertificate : (blobHash : Text) -> async record { method : Text; blob_hash : Text } |
| 87 | ``` |
| 88 | |
| 89 | Any other return type (`Blob`, `()`, `Text`, etc.) will fail gateway validation. |
| 90 | |
| 91 | # Frontend |
| 92 | |
| 93 | Backend `Blob` fields are represented as `ExternalBlob` on the frontend. |
| 94 | |
| 95 | |
| 96 | ```typescript |
| 97 | import { ExternalBlob } from "@caffeineai/object-storage"; |
| 98 | import type { FileRecord } from "@caffeineai/object-storage"; |
| 99 | ``` |
| 100 | |
| 101 | ## ExternalBlob API |
| 102 | |
| 103 | ```typescript |
| 104 | class ExternalBlob { |
| 105 | getBytes(): Promise<Uint8Array<ArrayBuffer>>; |
| 106 | getDirectURL(): string; |
| 107 | static fromURL(url: string): ExternalBlob; |
| 108 | static fromBytes( |
| 109 | blob: Uint8Array<ArrayBuffer>, |
| 110 | content |