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
- Set contentType on the attachment (e.g. 'application/pdf') when constructing it.
- Derive the MIME type from the file extension or the storage object's metadata before adding the message.
- If the source is a browser upload, use the File object's native type property.
- 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
- Always persist and pass contentType from the original upload.
- Derive MIME from file extension server-side as a fallback.
- Return a 400 to clients that submit non-image attachments without a MIME type.
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
- If the attachment is not an image or text, it must specify a
- Invalid URL: ${attachment.url}
- Unsupported URL protocol: ${url.protocol}
- Invalid \`mediaTypes\` pattern: ${JSON.stringify(pattern)}.
- ClaudeSDKAgent resumeData must include sessionId or continue
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/bbbd81df2966fe37.
Report an issue: GitHub.