mastra-ai/mastra · error · HTTPException
metadata.avatarUrl must be a valid data URL (data:<mime>;bas
Error message
metadata.avatarUrl must be a valid data URL (data:<mime>;base64,<data>)
What it means
HTTP 400 from validateMetadataAvatarUrl: an avatarUrl string must match the strict data-URL format data:<mime>;base64,<data>. Plain http(s) URLs, bare base64 strings, or malformed data URLs are rejected.
Source
Thrown at packages/server/src/server/handlers/validate-avatar.ts:20
const AVATAR_MAX_BYTES = 512 * 1024; // 512 KB
/**
* Validates `metadata.avatarUrl` if present.
* Ensures it's a well-formed data URL and the decoded payload is ≤ 512 KB.
* No-ops when metadata is absent or doesn't contain avatarUrl.
*/
export function validateMetadataAvatarUrl(metadata: Record<string, unknown> | undefined): void {
if (!metadata || !('avatarUrl' in metadata) || metadata.avatarUrl === null || metadata.avatarUrl === undefined)
return;
if (typeof metadata.avatarUrl !== 'string') {
throw new HTTPException(400, { message: 'metadata.avatarUrl must be a string' });
}
const dataUrl = metadata.avatarUrl;
const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
if (!match) {
throw new HTTPException(400, {
message: 'metadata.avatarUrl must be a valid data URL (data:<mime>;base64,<data>)',
});
}
// `Buffer.from(..., 'base64')` decodes leniently — it silently ignores
// invalid characters and never throws. Validate the payload format strictly
// before measuring its byte length so malformed input is rejected.
const base64Payload = match[2]!;
const isStrictBase64 =
base64Payload.length > 0 &&
base64Payload.length % 4 === 0 &&
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(base64Payload);
if (!isStrictBase64) {
throw new HTTPException(400, { message: 'metadata.avatarUrl contains invalid base64' });
}
const byteLength = Buffer.from(base64Payload, 'base64').byteLength;
if (byteLength === 0) {View on GitHub (pinned to 75dd419e61)
Solutions
- Convert the image to a base64 data URL client-side (read file, base64-encode, prefix with data:<mime>;base64,).
- Store external URLs under a different metadata key if remote URLs are intended.
- Validate the format with a regex before submitting.
Example fix
// before
metadata: { avatarUrl: 'https://example.com/avatar.png' }
// after
metadata: { avatarUrl: `data:image/png;base64,${fs.readFileSync('avatar.png').toString('base64')}` } Defensive patterns
Strategy: validation
Validate before calling
const DATA_URL_RE = /^data:([^;]+);base64,(.+)$/;
if (typeof metadata?.avatarUrl === 'string' && !DATA_URL_RE.test(metadata.avatarUrl)) {
throw new Error('avatarUrl must be a base64 data URL');
} Type guard
function isBase64DataUrl(v: unknown): v is string {
return typeof v === 'string' && /^data:[^;]+;base64,.+$/.test(v);
} Try / catch
try {
await saveAgent({ metadata });
} catch (e) {
if (e instanceof HTTPException && e.status === 400 && e.message.includes('valid data URL')) {
metadata.avatarUrl = await fileToDataUrl(metadata.rawAvatar);
await saveAgent({ metadata });
} else throw e;
} Prevention
- Always convert images to data URLs at the input boundary (file picker handler).
- Never store external http(s) URLs in avatarUrl.
- Add the data-URL regex to shared client-side form validation.
When it happens
Trigger: Passing metadata.avatarUrl as 'https://example.com/avatar.png', 'iVBORw0KGgo...' (no data: prefix), or 'data:image/png,urlencoded' (not base64).
Common situations: Sending remote image URLs expecting the server to fetch them; stripping the data: prefix when processing images client-side; legacy agents storing external URLs in avatarUrl.
Related errors
- Could not derive skill ID from registry skill metadata.
- metadata.avatarUrl must be a string
- metadata.avatarUrl is empty
- bad request: ${responseText}
- Rule metadata is too large.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/4c62b162961acea5.
Report an issue: GitHub.