Manage S3 Bucket media assets in TinaCMS.
The following guide relies on NextJS's API functions to authenticate the 3rd-party media interactions. We hope to document a framework-agnostic approach soon.
yarn add next-tinacms-s3
npm install next-tinacms-s3
You need some credentials provided to access AWS S3 Bucket to set this up properly.
Add the following variables to an .env
file.
S3_REGION=<Your S3 Bucket Name: ex. us-east-1>S3_BUCKET=<Your S3 Bucket Name: ex. my-bucket>S3_ACCESS_KEY=<Your S3 Bucket access key>S3_SECRET_KEY=<Your S3 Bucket access secret>
You need to setup S3 Bucket and IAM user correctly.
s3:ListBucket
s3:PutObject
s3:PutObjectAcl
s3:DeleteObject
You should be able to go to the AWS S3 console and navigate to the bucket details for the bucket you try to write objects to. You'll see a tab called 'Permissions'. There you have the option to change the "Object Ownership" at a block with the same title.
Once there, you can choose the option "ACLs enabled".
i.e. You can disable block public access settings
and set up the bucket policy like following:
{"Version": "2012-10-17","Statement": [{"Sid": "PublicRead","Effect": "Allow","Principal": "*","Action": "s3:GetObject","Resource": "arn:aws:s3:::<S3-Bucket-NAME>/*"},{"Sid": "LimitedWrite","Effect": "Allow","Principal": {"AWS": "<ARN of the IAM user>"},"Action": ["s3:PutObject", "s3:PutObjectAcl", "s3:DeleteObject"],"Resource": "arn:aws:s3:::<S3-Bucket-NAME>/*"},{"Sid": "ListBucket","Effect": "Allow","Principal": {"AWS": "<ARN of the IAM user>"},"Action": "s3:ListBucket","Resource": "arn:aws:s3:::<S3-Bucket-NAME>"}]}
Images are uploaded using S3 pre-signed URLs. This requires the bucket to have a CORS configuration that allows the origin of the request to access the bucket.
[{"AllowedHeaders": ["Authorization","Content-Range","Accept","Content-Type","Origin","Range"],"AllowedMethods": ["PUT"],"AllowedOrigins": ["*"],"ExposeHeaders": ["Content-Range","Content-Length","ETag"],"MaxAgeSeconds": 3000}]
You can register the S3 Media store via the loadCustomStore
prop.
The loadCustomStore
prop can be configured within tina/config.{js,ts,tsx}
.
//tina/config.{ts,js}//...export default defineConfig({//...media: {loadCustomStore: async () => {const pack = await import('next-tinacms-s3')return pack.TinaCloudS3MediaStore},},})
Tina's "external media provider" support requires a light backend media handler, that needs to be setup/hosted by the user. There are multiple ways to do this, including the framework-agnostic Netlify Functions implementation.
NOTE: this step will show you how to set up an API route for Next.js. If you are using a different framework, you will need to set up your own API route.
Set up a new API route in the pages
directory of your Next.js app, e.g. pages/api/s3/[...media].ts
.
Then add a new catch-all API route for media.
Call createMediaHandler
to set up routes and connect your instance of the Media Store to your S3 Bucket.
Import isAuthorized
from @tinacms/auth
.
The authorized
key will make it so only authorized users within TinaCloud can upload and make media edits.
// pages/api/s3/[...media].tsimport {mediaHandlerConfig,createMediaHandler,} from 'next-tinacms-s3/dist/handlers'import { isAuthorized } from '@tinacms/auth'export const config = mediaHandlerConfigexport default createMediaHandler({config: {credentials: {accessKeyId: process.env.S3_ACCESS_KEY || '',secretAccessKey: process.env.S3_SECRET_KEY || '',},region: process.env.S3_REGION,},bucket: process.env.S3_BUCKET || '',authorized: async (req, _res) => {if (process.env.NODE_ENV === 'development') {return true}try {const user = await isAuthorized(req)return user && user.verified} catch (e) {console.error(e)return false}},})
In the above example, we showed how to host the backend handler as a NextJS API function. If you are using Vercel with another framework, the same approach applies (with the small difference that you need to use /api/...
instead of /pages/api/...
for your handler).
You can also check out our Netlify Functions and AWS Lambda implementations.
If you're using a custom URL for your S3 bucket, you can pass in a cdnUrl
value to createMediaHandler
.
export default createMediaHandler({config: ...,bucket: ...,authorized: ...,},{cdnUrl: "https://my-custom-domain.com"})
Now that the media store is registered and the API route for media set up, let's add an image to your schema.
In your schema add a new field for the image, e.g:
{name: 'hero',type: 'image',label: 'Hero Image',}
Now, when editing your site, the image field will allow you to connect to your S3 Bucket via the Media Store to manage your media assets.
© TinaCMS 2019–2024