Skip to main content
You can log arbitrary binary data, like images, audio, video, PDFs, and large JSON objects, as attachments. Attachments are useful for building multimodal evaluations, handling large data structures, and can enable advanced scenarios like summarizing visual content or analyzing document metadata.

Upload attachments

You can upload attachments from either your code or the UI. Your files are securely stored in an object store and associated with the uploading user’s organization. Only you can access your attachments.

Via code

To upload an attachment, create a new Attachment object to represent the file path or in-memory buffer that you want to upload:
import { Attachment, initLogger } from "braintrust";

const logger = initLogger();

logger.log({
  input: {
    question: "What is this?",
    context: new Attachment({
      data: "path/to/input_image.jpg",
      filename: "user_input.jpg",
      contentType: "image/jpeg",
    }),
  },
  output: "Example response.",
});
You can place the Attachment anywhere in a log, dataset, or feedback log. Behind the scenes, the Braintrust SDK automatically detects and uploads attachments in the background, in parallel to the original logs. This ensures that the latency of your logs isn’t affected by any additional processing.

Use external files as attachments

The ExternalAttachment feature is supported only in self-hosted deployments. It is not supported in Braintrust-hosted environments.
Braintrust also supports references to files in external object stores with the ExternalAttachment object. You can use this anywhere you would use an Attachment. Currently S3 is the only supported option for external files.
import { ExternalAttachment, initLogger } from "braintrust";

const logger = initLogger({ projectName: "ExternalAttachment Example" });

logger.log({
  input: {
    question: "What is this?",
    additional_context: new ExternalAttachment({
      url: "s3://an_existing_bucket/path/to/file.pdf",
      filename: "file.pdf",
      contentType: "application/pdf",
    }),
  },
  output: "Example response.",
});
Just like attachments uploaded to Braintrust, external attachments can be previewed and downloaded for local viewing.

JSON attachments

For large JSON objects that would bloat your trace size, you can use JSONAttachment. This is particularly useful for:
  • Lengthy conversation transcripts
  • Extensive document collections or knowledge bases
  • Complex nested data structures with embeddings
  • Large evaluation datasets
  • Any JSON data that exceeds the 6MB trace limit
JSONAttachment automatically serializes your JSON data and stores it as an attachment with content type application/json. The data is:
  • Uploaded separately as an attachment, bypassing the 6MB trace limit
  • Not indexed, which saves storage space and speeds up ingestion
  • Still fully viewable in the UI with all the features of the JSON viewer (collapsible nodes, syntax highlighting, search, etc.)
import { JSONAttachment, initLogger } from "braintrust";

const logger = initLogger();

// Example: Large conversation transcript
const transcript = Array.from({ length: 100 }, (_, i) => ({
  role: i % 2 === 0 ? "user" : "assistant",
  content: `Message content ${i}...`,
  timestamp: new Date().toISOString(),
}));

logger.log({
  input: {
    type: "chat_completion",
    // Store large transcript as an attachment
    transcript: new JSONAttachment(transcript, {
      filename: "conversation_transcript.json",
      pretty: true, // Optional: pretty-print the JSON
    }),
    config: {
      temperature: 0.7,
      model: "gpt-5-mini",
    },
  },
  output: "Completed conversation successfully",
});
Just like other attachments, JSON attachments can be previewed directly in the UI and downloaded for local viewing. Check out the Upload large traces section for more examples and details.

Upload attachments in the UI

You can upload attachments directly through the UI for any editable span field. This includes:
  • Any dataset fields, including datasets in playgrounds
  • Log span fields
  • Experiment span fields
You can also include attachments in prompt messages when using models that support multimodal inputs.

Inline attachments

Sometimes your attachments are pre-hosted files which you do not want to upload explicitly, but would like to display as if they were attachments. Inline attachments allow you to do this, by specifying the URL and content type of the file. Create a JSON object anywhere in the log data with type: "inline_attachment" and src and content_type fields. The filename field is optional.
{
  "file": {
    "type": "inline_attachment",
    "src": "https://robohash.org/example",
    "content_type": "image/png",
    "filename": "A robot"
  }
}
Screenshot of inline attachment

View attachments in the UI

You can preview images, audio files, videos, PDFs, and JSON files in the Braintrust UI. You can also download any file to view it locally. We provide built-in support to preview attachments directly in playground input cells and traces. In the playground, you can preview attachments in an inline embedded view for easy visual verification during experimentation: Screenshot of attachment inline in a playground In the trace pane, attachments appear as an additional list under the data viewer: Screenshot of attachment list in Braintrust

Read attachments via SDK

You can programmatically read and process attachments using the Braintrust SDK. This allows you to access attachment data in your code for analysis, processing, or integration with other systems. When accessing a dataset or experiment, the TypeScript and Python SDKs automatically create a ReadonlyAttachment object for each attachment. For attachments in scorers or logs, use the ReadonlyAttachment class to access attachment data, check metadata, and process different content types.

Access attachments from a dataset

import { initDataset } from "braintrust";
import { Buffer } from "buffer";

async function processDatasetWithAttachments() {
  // Load a dataset that contains attachments
  const dataset = initDataset({
    project: "my-project",
    dataset: "my-dataset-with-images",
  });

  // Get the single row from the dataset
  const records = dataset.fetch();
  const row = await records.next();
  const record = row.value;

  // The record contains attachment references that are automatically converted to ReadonlyAttachment objects
  const imageAttachment = record.input.image;
  const documentAttachment = record.input.document;

  // Access image attachment data
  const imageData = await imageAttachment.data();

  // Process the image data
  const arrayBuffer = await imageData.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);

  // Access document attachment data
  const documentData = await documentAttachment.data();
  const documentText = await documentData.text();
}

processDatasetWithAttachments();

Create ReadonlyAttachment from raw logs data

import { ReadonlyAttachment } from "braintrust";
import { Buffer } from "buffer";

async function processRawLogsWithAttachments() {
  // Example raw log data that contains attachment references
  const rawLogData = {
    id: "log-123",
    input: {
      question: "What is in this image?",
      image: {
        type: "braintrust_attachment" as const,
        key: "attachments/abc123def456",
        filename: "sample_image.jpg",
        content_type: "image/jpeg",
      },
      document: {
        type: "braintrust_attachment" as const,
        key: "attachments/xyz789ghi012",
        filename: "context.pdf",
        content_type: "application/pdf",
      },
    },
    output: "This image shows a cat sitting on a windowsill.",
  };

  // Manually create ReadonlyAttachment objects from raw attachment references
  const imageAttachment = new ReadonlyAttachment(rawLogData.input.image);
  const documentAttachment = new ReadonlyAttachment(rawLogData.input.document);

  // Access image attachment data
  const imageData = await imageAttachment.data();

  // Process the image data
  const arrayBuffer = await imageData.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);

  // Access document attachment data
  const documentData = await documentAttachment.data();
  const documentText = await documentData.text();
}

processRawLogsWithAttachments();

Handle external attachments

Work with external attachments (like S3 files) using the same patterns.
import { ReadonlyAttachment } from "braintrust";
import { Buffer } from "buffer";

async function processExternalAttachment() {
  // Example external attachment reference
  const externalAttachment = new ReadonlyAttachment({
    type: "external_attachment" as const,
    url: "s3://bucket/path/to/file.pdf",
    filename: "document.pdf",
    content_type: "application/pdf",
  });

  // Access external attachment data
  const data = await externalAttachment.data();
  console.log(`External file size: ${data.size} bytes`);

  // Convert Blob to Buffer for file writing
  const arrayBuffer = await data.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);

  // Save to local file
  console.log("External attachment ready for processing");
}

processExternalAttachment();