|
| 1 | +# Blob Storage |
| 2 | + |
| 3 | +Binary Large Object Storage (aka “Blob”) is a way for your app to store data—like images, videos, and logs—in a scalable, cloud-based system. You can treat it like a cloud file system. Objects are similar to files and are stored at unique **keys** (for example, `path/to/my-object.json`). |
| 4 | + |
| 5 | +:::note |
| 6 | +This is an experimental feature, which means you’ll need to request access to this capability. Use modmail at [r/devvit](https://www.reddit.com/r/Devvit/) to apply. |
| 7 | +::: |
| 8 | + |
| 9 | +## How it works |
| 10 | + |
| 11 | +Since this is a beta program, you’ll need to send us a couple of things before you can get started: |
| 12 | + |
| 13 | +* The app identifier you want to use in the beta test. |
| 14 | +* An overview that describes your use case. |
| 15 | +* An estimate of how much storage you will need. |
| 16 | + |
| 17 | +Devvit blob manages an [AWS S3 bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) for you to use. Under the hood, Devvit blob creates a dedicated namespace for your app’s installation. This means that separate installations cannot share blob storage. |
| 18 | + |
| 19 | +Public-facing file serving is not supported through blob storage at this time. Access to your namespace is restricted to just your app. |
| 20 | + |
| 21 | +## When to use blob storage |
| 22 | + |
| 23 | +Blob storage is best suited for data that is either bulky or infrequently accessed, like: |
| 24 | + |
| 25 | +* User generated content (screenshots, replays, save files, etc.) |
| 26 | +* Backups and archives |
| 27 | + |
| 28 | +You can store up to 50 GB in blob storage. If your redis storage is filling up, you can migrate your most bulky data to blob storage to free up capacity. |
| 29 | + |
| 30 | +## Limits and quotas |
| 31 | + |
| 32 | +* Max requests per second: 100 |
| 33 | +* Max request size: 30 MB |
| 34 | +* Max object path length: 900 bytes |
| 35 | +* Max storage: 50 GB |
| 36 | + |
| 37 | +## Adding blob storage to your app |
| 38 | + |
| 39 | +The @devvit/blob package provides a preconfigured client to use in your app’s server-side code. |
| 40 | + |
| 41 | +### Supported commands |
| 42 | + |
| 43 | +The client can only perform commands within your installation’s namespace in the S3 bucket. If you pass in a “Bucket” parameter to an S3 command, it will be overridden. The following commands are supported: |
| 44 | + |
| 45 | +* `PutObject` |
| 46 | +* `GetObject` |
| 47 | +* `DeleteObject` |
| 48 | +* `ListObjects` |
| 49 | +* `ListObjectsV2` |
| 50 | +* `CreateMultipartUpload` |
| 51 | +* `UploadPart` |
| 52 | +* `ListParts` |
| 53 | +* `AbortMultipartUpload` |
| 54 | +* `CompleteMultipartUpload` |
| 55 | +* `HeadObject` |
| 56 | +* `SelectObjectContent` |
| 57 | + |
| 58 | + |
| 59 | +See the [AWS SDK documentation](https://github.com/aws/aws-sdk-js-v3/tree/main/clients/client-s3#client-commands-operations-list) for the available input parameters for each command. Note that some [S3 commands are unsupported](#unsupported-commands) and will not work with blob storage. |
| 60 | + |
| 61 | +### Steps |
| 62 | + |
| 63 | +1. In the root directory of your app, run npm install @devvit/blob to install the package. |
| 64 | + |
| 65 | +2. Make sure all @devvit dependencies in your app’s `package.json` are using the same version as the blob package you just installed. |
| 66 | + |
| 67 | +3. Install the latest version of the AWS SDK for S3: npm install @aws-sdk/client-s3 |
| 68 | + |
| 69 | +4. Set up the following APIs in your app server, which you can then call to use the supported commands in your client code: |
| 70 | + |
| 71 | +```ts |
| 72 | +import { newS3Client } from '@devvit/blob'; |
| 73 | +import { |
| 74 | + PutObjectCommand, |
| 75 | + type PutObjectCommandInput, |
| 76 | + GetObjectCommand, |
| 77 | + type GetObjectCommandInput, |
| 78 | + DeleteObjectCommand, |
| 79 | + type DeleteObjectCommandInput, |
| 80 | + ListObjectsV2Command, |
| 81 | + type ListObjectsV2CommandInput, |
| 82 | +} from '@aws-sdk/client-s3'; |
| 83 | + |
| 84 | +// -- Server side of webbit app -- |
| 85 | + |
| 86 | +// Uploading content to S3. |
| 87 | +router.post< |
| 88 | + Record<string, never>, |
| 89 | + { ok: boolean } | { status: string; message: string }, |
| 90 | + { key: string; body: string }, |
| 91 | + Record<string, never> |
| 92 | +>("/api/s3-put", async (req, res): Promise<void> => { |
| 93 | + const input: PutObjectCommandInput = { |
| 94 | + Bucket: '', |
| 95 | + Key: req.body.key, |
| 96 | + Body: req.body.body, |
| 97 | + ContentType: "text/plain", |
| 98 | + }; |
| 99 | + const client = await newS3Client(); |
| 100 | + await client.send(new PutObjectCommand(input)); |
| 101 | + res.json({ ok: true }); |
| 102 | +}); |
| 103 | + |
| 104 | +// Getting content from S3. |
| 105 | +router.get< |
| 106 | + Record<string, never>, |
| 107 | + string | { status: string; message: string }, |
| 108 | + unknown, |
| 109 | + { path?: string } |
| 110 | +>("/api/s3-get", async (req, res): Promise<void> => { |
| 111 | + const client = await newS3Client(); |
| 112 | + const input: GetObjectCommandInput = { |
| 113 | + Bucket: '', Key: req.query.path, |
| 114 | + }; |
| 115 | + const response = await client.send(new GetObjectCommand(input)); |
| 116 | + const text = (await response.Body?.transformToString?.()) ?? ""; |
| 117 | + res.type("text/plain").send(text); |
| 118 | +}); |
| 119 | + |
| 120 | +// Deleting content from S3. |
| 121 | +router.delete< |
| 122 | + Record<string, never>, |
| 123 | + { deleted: boolean } | { status: string; message: string }, |
| 124 | + { key?: string }, |
| 125 | + { key?: string } |
| 126 | +>("/api/s3-delete", async (req, res): Promise<void> => { |
| 127 | + const key = (req.query.key as string | undefined) ?? req.body?.key; |
| 128 | + const input: DeleteObjectCommandInput = { |
| 129 | + Bucket: '', Key: key, |
| 130 | + }; |
| 131 | + const client = await newS3Client(); |
| 132 | + await client.send(new DeleteObjectCommand(input)); |
| 133 | + res.json({ deleted: true }); |
| 134 | +}); |
| 135 | + |
| 136 | +// List the objects, or "keys", that exist at a path prefix in S3. |
| 137 | +router.get< |
| 138 | + Record<string, never>, |
| 139 | + { keys: string[] } | { status: string; message: string }, |
| 140 | + unknown, |
| 141 | + { prefix?: string } |
| 142 | +>("/api/s3-list", async (req, res): Promise<void> => { |
| 143 | + const prefix = (req.query.prefix as string) ?? ""; |
| 144 | + const input: ListObjectsV2CommandInput = { |
| 145 | + Bucket: '', Prefix: prefix, |
| 146 | + }; |
| 147 | + const client = await newS3Client(); |
| 148 | + const listResp = await client.send(new ListObjectsV2Command(input)); |
| 149 | + const keys: string[] = listResp.Contents?.map((c) => c.Key).filter(Boolean) as string[]; |
| 150 | + res.json({ keys }); |
| 151 | +}); |
| 152 | +``` |
| 153 | + |
| 154 | +## FAQs |
| 155 | + |
| 156 | +* **What is a bucket?** |
| 157 | + A bucket is the top-level container for storing data in S3. Buckets contain objects. |
| 158 | + |
| 159 | +* **What is an object?** |
| 160 | + An object is the basic unit of storage in S3. It’s similar to a file in a traditional file system. |
| 161 | + |
| 162 | +* **What is a prefix?** |
| 163 | + A prefix is used to organize objects within a bucket. It works like a file path, for example: `this/is/a/prefix/`. |
| 164 | + |
| 165 | +* **Can I create a new bucket?** |
| 166 | + No. Bucket creation isn’t supported. Your installation’s namespace acts as its own bucket. |
| 167 | + |
| 168 | +* **Can I have multiple buckets?** |
| 169 | + No. Multiple buckets aren’t supported. Your installation’s namespace functions as a single bucket. |
| 170 | + |
| 171 | +* **Why do some S3 commands not work with the blob client?** |
| 172 | + The blob client uses restricted credentials and a custom middleware layer, so some S3 operations aren’t available. |
| 173 | + |
| 174 | +* **Why did my remote build fail?** |
| 175 | + If you added “permissions”: { “blob”: true } to your devvit.json, that could cause build issues. The blob package should work without it. |
| 176 | + |
| 177 | +## Unsupported commands |
| 178 | + |
| 179 | +The following commands are **not** supported for blob storage: |
| 180 | + |
| 181 | +* `UploadPartCopy` |
| 182 | +* `ListMultipartUploads` |
| 183 | +* `CopyObject` |
| 184 | +* `DeleteObjects` |
| 185 | +* `DeleteObjectTagging` |
| 186 | +* `CreateBucket` |
| 187 | +* `CreateBucketMetadataTableConfiguration` |
| 188 | +* `CreateSession` |
| 189 | +* all commands starting with `DeleteBucket` |
| 190 | +* `DeletePublicAccessBlock` |
| 191 | +* all commands starting with `GetBucket` |
| 192 | +* `GetObjectAcl` |
| 193 | +* `GetObjectAttributes` |
| 194 | +* `GetObjectLegalHold` |
| 195 | +* `GetObjectLockConfiguration` |
| 196 | +* `GetObjectRetention` |
| 197 | +* `GetObjectTagging` |
| 198 | +* `GetObjectTorrent` |
| 199 | +* `GetPublicAccessBlock` |
| 200 | +* `HeadBucket` |
| 201 | +* all commands starting with `ListBucket` |
| 202 | +* `ListDirectoryBuckets` |
| 203 | +* `ListObjectVersions` |
| 204 | +* all commands starting with `PutBucket` |
| 205 | +* `PutObjectAcl` |
| 206 | +* `PutObjectLegalHold` |
| 207 | +* `PutObjectLockConfiguration` |
| 208 | +* `PutObjectRetention` |
| 209 | +* `PutObjectTagging` |
| 210 | +* `PutPublicAccessBlock` |
| 211 | +* `RenameObject` |
| 212 | +* `RestoreObject` |
| 213 | +* all commands starting with `UpdateBucket` |
| 214 | +* `UpdateObjectEncryption` |
| 215 | +* `WriteGetObjectResponsePutObject` |
| 216 | + |
| 217 | + |
0 commit comments