Tina supports using external media providers, however a light backend media handler needs to be setup/hosted by the user. Tina offers some helpers to make this easy, for cloudinary
,s3
, & dos
("Digital Ocean Spaces")
yarn add @tinacms/auth
Depending on your site's framework & hosting provider, there are multiple ways to host the media handler.
In the following examples, replace<YOUR_MEDIA_STORE_NAME>
withcloudinary
,s3
, ordos
.
Set up a new API route in the pages
directory of your Next.js app at pages/api/<YOUR_MEDIA_STORE_NAME>/[...media].ts
.
Then add a new catch-all API route for media by calling createMediaHandler method of your media store library.
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/<YOUR_MEDIA_STORE_NAME>/[...media].tsimport { createMediaHandler } from 'next-tinacms-<YOUR_MEDIA_STORE_NAME>/dist/handlers'import { isAuthorized } from '@tinacms/auth'export default createMediaHandler({// ...authorized: async (req, _res) => {try {if (process.env.NODE_ENV == 'development') {return true}const user = await isAuthorized(req)return user && user.verified} catch (e) {console.error(e)return false}},})
Vercel supports creating Serverless functions by creating a /api
directory at the project root. To set this up, follow the above NextJS-specific instructions, but use /api/<YOUR_MEDIA_STORE_NAME>/[...media].ts
instead of /pages/api/<YOUR_MEDIA_STORE_NAME>/[...media].ts
Note: You may notice that the package names may contain "next" (e.g: next-tinacms-cloudinary
). You can still use these packages for other frameworks.
If your site is hosted on Netlify, you can use "Netlify Functions" to host your media handler.
First, you must set up redirects so that all requests to /api/*
can be redirected to Netlify Functions. You can set up redirects at netlify.toml
. We are also going to build our functions with esbuild so we will set that in the netlify.toml
as well.
Add the following to the netlify.toml
file in the root of your project.
[[redirects]]from = '/api/*'to = '/.netlify/functions/api/:splat'status = 200[functions]node_bundler = 'esbuild'
Next, you must set up api routes for the media handler.
Install the following dependencies.
yarn add serverless-http express @tinacms/auth next-tinacms-<YOUR_MEDIA_STORE_NAME>
Make a new file called netlify/functions/api/api.js
and add the following code.
Note: the file path could be different if you are using a different functions directory.
import ServerlessHttp from 'serverless-http'import express, { Router } from 'express'import { isAuthorized } from '@tinacms/auth'import { createMediaHandler } from 'next-tinacms-<YOUR_MEDIA_STORE_NAME>/dist/handlers'const app = express()const router = Router()const mediaHandler = createMediaHandler({// ...// See the next section for more details on what goes in the createMediaHandlerauthorized: async (req, _res) => {try {if (process.env.NODE_ENV == 'development') {return true}const user = await isAuthorized(req)return user && user.verified} catch (e) {console.error(e)return false}},})router.get('/cloudinary/media', mediaHandler)router.post('/cloudinary/media', mediaHandler)router.delete('/cloudinary/media/:media', (req, res) => {req.query.media = ['media', req.params.media]return mediaHandler(req, res)})app.use('/api/', router)app.use('/.netlify/functions/api/', router)export const handler = ServerlessHttp(app)
If your site is hosted on AWS, you can use AWS Lambda to host your media handler. The following example uses the S3 media handler, but you can use any media handler.
npm install express @vendia/serverless-express @tinacms/auth body-parser
// index.tsimport express, { Router } from 'express'import serverlessExpress from '@vendia/serverless-express'import { isAuthorized } from '@tinacms/auth'import { createMediaHandler } from 'next-tinacms-s3/dist/handlers'import bodyParser from 'body-parser'// Configure TinaCMSconst mediaHandler = createMediaHandler({config: {credentials: {accessKeyId: process.env.TINA_AWS_ACCESS_KEY_ID || '',secretAccessKey: process.env.TINA_AWS_SECRET_ACCESS_KEY || '',},region: process.env.TINA_AWS_REGION,},bucket: process.env.TINA_AWS_BUCKET_NAME || '',authorized: async (req, _res): Promise<any> => {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}},})// Set up the express app and routerconst app = express()const router = Router()app.use(bodyParser.json())// Define routes for media handlingrouter.get('/s3/media', mediaHandler)router.post('/s3/media', mediaHandler)router.delete('/s3/media/:media', (req, res) => {req.query.media = ['media', req.params.media]return mediaHandler(req, res)})// Mount the router on the appapp.use('/api/', router)// Export the handler functionexports.handler = serverlessExpress({ app })
TINA_AWS_ACCESS_KEY_ID=******************TINA_AWS_BUCKET_NAME=******************TINA_AWS_REGION=********TINA_AWS_SECRET_ACCESS_KEY=******************
Create API
/api
by going to Resources and selecting Create Resource/api
child paths by creating a resource that uses the {proxy+}
special syntax. Make sure to tick the Configure as proxy resource.save
and allow API Gateway to add permission to the Lambda Function[New Stage]
for the Deployment Stage and type a Stage name/*
wildcard/api/s3/media*
path and use the API Gateway origin that was just created. Make sure to allow the following HTTP methods: GET
, HEAD
, OPTIONS
, PUT
, POST
, PATCH
, and DELETE
./api/s3/media/*
path.Now, you can replace the default repo-based media with the external media store. You can register a media store via the loadCustomStore
prop.
The loadCustomStore
prop can be configured within tina/config
file.
// tina/config.{ts,js,jsx}// ...export default defineConfig({// ...media: {- tina: {- publicFolder: "",- mediaRoot: ""- },+ loadCustomStore: async () => {+ const pack = await import("next-tinacms-<YOUR_MEDIA_STORE_NAME>");+ return pack.TinaCloud<YOUR_MEDIA_STORE>;+ },}})
Make sure you commit your changes to the config andtina-lock.json
file at the same time as you push to production on TinaCloud as otherwise your assets will still be prefixed withhttps://assets.tina.io
as if you were still using repo based media
Now you can manage external media store inside TinaCMS. To learn more about each media store in detail, please refer to the next sections.
© TinaCMS 2019–2024