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

  1. Convert the image to a base64 data URL client-side (read file, base64-encode, prefix with data:<mime>;base64,).
  2. Store external URLs under a different metadata key if remote URLs are intended.
  3. 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

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


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