Rendering images and files

Unlike other headless CMSs, Flashboard does not provide a REST or GraphQL API. You’re responsible for loading the data directly using SQL or a backend like Supabase.

Images inside HTML

Images uploaded inside HTML content will be rendered together with your HTML. Follow our guide on rendering HTML content and that's it.

Images and files uploaded on fields

When you upload images and files on fields, they are stored in your database column using a JSON format that includes everything you need to generate secure URLs for the files and render them. Here's an example:

[
  {
    "flashboardStorage": "v1",
    "serviceName": "s3",
    "bucketName": "my-secure-bucket",
    "key": "001b8672-76cf-4f6e-9950-798ca09b68bc",
    "filename": "my-document.pdf",
    "contentType": "application/pdf",
    "size": 500316
  },
  {
    "flashboardStorage": "v1",
    "serviceName": "s3",
    "bucketName": "my-secure-bucket",
    "key": "b866d33e-eb03-41c8-b5f9-10496acf9caf",
    "filename": "my-image.png",
    "contentType": "image/png",
    "size": 20165
  }
]

If you upload your files on a text field, it will contain a stringified version of the above format. For json or jsonb columns, the actual array of objects will be stored.

Even if you only upload a single file, it will be stored as an array with one object. Here's the format of the object representing each uploaded file:

  • flashboardStorage (string): the version of the format. Currently, it will always be v1, and we don't intend to make new versions anytime soon. But we have it there just in case we need to make any breaking changes to the stored format.

  • serviceName (string): the service used to upload the file. Currently, it will always be s3, since we only support S3-compatible storage services at the moment. But we plan to add other services in the future.

  • bucketName (string): the name of the bucket where the file is stored.

  • key (string): the key of the object in your bucket in UUID format.

  • filename (string): the original name of the uploaded file.

  • contentType (string): the Content-Type of the file.

  • size (number): the size of the file in bytes.

All this information allows you to render your images by connecting to your storage service and generating a signed URL. Follow this guide for in-depth instructions, but here's a simple example in TypeScript using Zod and @aws-sdk/client-s3:

import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import { z } from 'zod'

const fileSchema = z.object({
  bucketName: z.string(),
  key: z.string(),
  contentType: z.string(),
})

const filesSchema = z.array(fileSchema)

function loadMyFilesField() {
  // Here you would load the field from the database instead
  const myFilesValue = [
    {
      bucketName: 'my-secure-bucket',
      ket: '001b8672-76cf-4f6e-9950-798ca09b68bc',
      contentType: 'image/png',
    },
    {
      bucketName: 'my-secure-bucket',
      ket: 'b866d33e-eb03-41c8-b5f9-10496acf9caf',
      contentType: 'application/pdf',
    },
  ]

  return filesSchema.parse(myFilesValue)
}

const myFiles = loadMyFilesField()

const client = new S3Client({
  endpoint: 'https://s3.us-east-2.amazonaws.com',
  region: 'us-east-2',
  credentials: {
    accessKeyId: 'my-access-key-id',
    secretAccessKey: 'my-secret-access-key',
  },
})

myFiles.forEach((file) => {
  const { bucketName, key, contentType } = file

  const command = new GetObjectCommand({
    Bucket: bucketName,
    Key: key,
  })

  // For images, set ResponseContentDisposition to inline for browser rendering
  // For other files, set to attachment for immediate download
  const disposition = contentType.startsWith('image/') ? 'inline' : 'attachment'

  command.input.ResponseContentType = contentType
  command.input.ResponseContentDisposition = disposition

  // Generate a signed URL that's valid for 15 minutes
  const url = getSignedUrl(client, command, { expiresIn: 60 * 15 })

  console.log('URL', url)
})
Felipe Freitag, Flashboard founder

Need help?

Hey! I'm Felipe, Flashboard's founder. I'll personally help you and make sure you get your panel up and running.