mastra-ai/mastra · error

If the attachment is not an image, it must specify a content

Error message

If the attachment is not an image, it must specify a content type

What it means

For remote (http/https/gs/s3/data-protocol family) attachments that are not images, attachmentsToParts emits a 'file' part that requires a mimeType. If attachment.contentType is missing (and the attachment isn't an image), the function throws because the downstream provider file part has no way to interpret the data.

Source

Thrown at packages/core/src/agent/message-list/prompt/attachments-to-parts.ts:54

    let url;
    try {
      url = new URL(urlString);
    } catch {
      throw new Error(`Invalid URL: ${attachment.url}`);
    }

    switch (url.protocol) {
      case 'http:':
      case 'https:':
      // Cloud storage protocols supported by AI providers (e.g., Vertex AI for gs://, Bedrock for s3://)
      case 'gs:':
      case 's3:': {
        if (attachment.contentType?.startsWith('image/')) {
          parts.push({ type: 'image', image: url.toString(), mimeType: attachment.contentType });
        } else {
          if (!attachment.contentType) {
            throw new Error('If the attachment is not an image, it must specify a content type');
          }

          parts.push({
            type: 'file',
            data: url.toString(),
            mimeType: attachment.contentType,
          });
        }
        break;
      }

      case 'data:': {
        if (attachment.contentType?.startsWith('image/')) {
          parts.push({
            type: 'image',
            image: urlString,
            mimeType: attachment.contentType,
          });

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Set contentType on the attachment (e.g. 'application/pdf') when constructing it.
  2. Derive the MIME type from the file extension or the storage object's metadata before adding the message.
  3. If the source is a browser upload, use the File object's native type property.
  4. Return an explicit validation error in your app layer when contentType is absent so users get a clearer message.

Example fix

// before
{ url: 'gs://bucket/doc.pdf' }
// after
{ url: 'gs://bucket/doc.pdf', contentType: 'application/pdf' }
Defensive patterns

Strategy: validation

Validate before calling

function assertRemoteAttachment(a: { url: string; contentType?: string }) {
  const protocol = new URL(a.url).protocol;
  if (['http:', 'https:', 'gs:', 's3:'].includes(protocol) && !a.contentType) {
    throw new Error(`Attachment ${a.url} needs a contentType`);
  }
}

Type guard

function hasContentType(a: { contentType?: string }): a is { contentType: string } {
  return typeof a.contentType === 'string' && a.contentType.length > 0;
}

Try / catch

try {
  messageList.add(toUserContent(attachment));
} catch (e) {
  if (e instanceof Error && e.message.includes('must specify a content type')) {
    attachment.contentType = lookupMimeFromExtension(attachment.url) ?? 'application/octet-stream';
    messageList.add(toUserContent(attachment));
  } else throw e;
}

Prevention

When it happens

Trigger: Adding a user message with a cloud/HTTP attachment (gs://, s3://, http(s)://) that has no contentType and is not image/* — e.g. { url: 's3://bucket/report.pdf' } with no contentType field.

Common situations: Custom upload pipelines that don't persist MIME type; S3/Vertex references built manually without metadata; UIs that only capture the file name/URL.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/bbbd81df2966fe37. Report an issue: GitHub.