• 5 min read

Integrate Backblaze B2 with TinaCMS media library

As part of an experiment to migrate my website to TinaCMS away from Sanity, I decided I wanted to move my media files to Backblaze B2 buckets. This, along with migrating my content back to markdown, allows for more control over my files, easier backups, and doesn’t lock it behind a proprietary format. Working with the media library was my main hiccup with TinaCMS, almost causing me to give up. But, with a bit more persistence (and digging through Tina’s S3 integration adapter source code) I figured out a relatively straightforward solution to provide the functionality I wanted.

TinaCMS by default supports locating media alongside your content files in a git repo, but I decided against this because I regularly post photos to my website and already have several hundred. Git isn’t optimized to handle files like this. An external media storage service (like Amazon S3, Cloudflare R2, etc) will reduce storage costs and give more control over file versioning (or avoidance of said file versioning).

After struggling for a while, I decided to use a Vercel Functions route instead (this website is currently deployed on Vercel). TinaCMS has an excellent example of how to create a custom API function in a NextJS-centric way, but NextJS and Astro API routes (this website is built on Astro) use different functional arguments and return values, because under the hood one uses Express.js syntax, and the other uses plain Node syntax. These differences, both in syntax and functionality, made integrating TinaCMS’ media library API difficult, so Vercel Functions was the best alternative.

The first step to set up this media library is the easiest, let’s tell TinaCMS that we’re going to use an S3-compatible data store. This guide assumes you already have a functioning TinaCMS installation. If not, see their setup docs.

Add TinaCMS media store config

Install the packages @tinacms/auth and next-tinacms-s3 using your favorite package manager. Mine is pnpm:

pnpm install @tinacms/auth next-tinacms-s3

In the tina/config.ts, add (or replace if it already exists), the media key as follows:

...
  media: {
    loadCustomStore: async () => {
      const pack = await import('next-tinacms-s3');
      return pack.TinaCloudS3MediaStore;
    },
  },
...

That’s it for the TinaCMS config! A painless change. We’ve still got some work to do, though, as it will error out if you try to browse the media library now.

Setup B2 and API token

We need a few key pieces of information from Backblaze B2. If you haven’t already, create an account and a B2 bucket that you’d like to use for your media library. Take note of a few pieces of information:

  • The name of the bucket you created (example, tinacms-media-library).
  • The bucket’s “Endpoint” (looks something like s3.us-west-000.backblazeb2.com).
  • Then create a new API key in the Application Keys section. Allow the key both read and write access to your new bucket.
    • Save both the keyID and the applicationKey for later.

Create Vercel API route

For this next step, let’s assume you are also deploying your application on Vercel. If not, TinaCMS docs have examples for other providers. Let’s set up the Vercel API route:

  1. In the root of your project, create an api directory, and then next another directory, s3, inside of it.
  2. Inside the s3 directory, create a file called [...media].mjs
  3. Paste these contents in the file:
import { createMediaHandler } from 'next-tinacms-s3/dist/handlers.js';
import tinaAuth from '@tinacms/auth';
const { isAuthorized } = tinaAuth;

export default createMediaHandler({
  config: {
    credentials: {
      accessKeyId: process.env.S3_ACCESS_KEY || '',
      secretAccessKey: process.env.S3_SECRET_KEY || '',
    },
    region: ##INSERT-REGION-HERE##,
    endpoint: ##INSERT-ENDPOINT-HERE##,
  },
  bucket: ##INSERT-BUCKET-NAME-HERE##,
  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;
    }
  },
});

Most of this file replicates what TinaCMS gives us, but adds the endpoint key under the config object that allows us to use Backblaze B2 in place of Amazon S3.

When filling out the file above:

  1. Set bucket to the name of the bucket you created (example, tinacms-media-library).
  2. Set endpoint to the “Endpoint” value you recorded from your B2 bucket (example, s3.us-west-000.backblazeb2.com).
  3. Set region to the subdomain of your “Endpoint”. For example, if your endpoint is s3.us-west-000.backblazeb2.com then set the region to us-west-000.

The above configuration pattern, specifically the endpoint value, allow use of any other S3-compatible storage service, though I only tested with Backblaze B2.

Finalize setup in Vercel

Your project should be created in Vercel already (docs). We need to add two environment variables (environment variable docs):

  • S3_ACCESS_KEY: set this to the keyID you saved from the B2 API key setup
  • S3_SECRET_KEY: set this to the applicationKey from the B2 API key

Your application should be ready to deploy now! Wait for Vercel to deploy, and then navigate to the media library in your TinaCMS instance. It should be able to read and upload files to Backblaze B2!


A profile photo of Anson Lichtfuss
Written by Anson Lichtfuss, a frontend engineer building useful, beautiful interfaces in Seattle.