Budibase/budibase · error · Error

Attachments must have both "url" and "filename" keys. You ha

Error message

Attachments must have both "url" and "filename" keys. You have provided: ${providedKeys}

What it means

normalizeSingleAttachment coerces automation attachment inputs into {url, filename} objects. If the input object has no url key (and no resolvable URL), an Error is thrown listing the keys that were actually provided, since both url and filename are required for attachment handling.

Source

Thrown at packages/server/src/automations/automationUtils.ts:158

  }
}

function normalizeSingleAttachment(
  input: string | AutomationAttachment
): AutomationAttachment | null {
  if (typeof input === "string") {
    try {
      const parsed = JSON.parse(input)
      return normalizeSingleAttachment(parsed)
    } catch {
      return { url: input, filename: deriveFilenameFromUrl(input) }
    }
  }

  const url: string | undefined = input.url
  if (!url) {
    const providedKeys = Object.keys(input).join(", ")
    throw new Error(
      `Attachments must have both "url" and "filename" keys. You have provided: ${providedKeys}`
    )
  }
  const filename: string =
    input.filename ?? input.name ?? deriveFilenameFromUrl(url)

  return { url, filename }
}

function normalizeAttachmentValue(
  value: string | AutomationAttachment | AutomationAttachment[]
): AutomationAttachment | AutomationAttachment[] | null {
  if (value == null) return null

  if (typeof value === "string") {
    try {
      const parsed = JSON.parse(value)
      return normalizeAttachmentValue(parsed)

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Ensure the attachment input object includes a url key pointing to an accessible file URL.
  2. Check the upstream step/binding that should supply the URL — fix the binding so it resolves at runtime.
  3. Provide the full {url, filename} shape from the previous step's output.
  4. If filenames differ, pass {url, filename} explicitly rather than relying on name/derivation.

Example fix

// before
attachments: [{ filename: "report.pdf" }]
// after
attachments: [{ url: "https://example.com/report.pdf", filename: "report.pdf" }]
Defensive patterns

Strategy: validation

Validate before calling

const input = maybeAttachment
if (typeof input !== "object" || input === null || !("url" in input) || !(input as any).url) {
  throw new Error(`Attachment requires a url; got keys: ${Object.keys(input ?? {}).join(", ")}`)
}

Type guard

const isAttachment = (v: unknown): v is { url: string; filename?: string } =>
  typeof v === "object" && v !== null && "url" in v && typeof (v as { url: unknown }).url === "string" && (v as { url: string }).url !== ""

Try / catch

try {
  await runAutomation(inputs)
} catch (e) {
  if (/Attachments must have both/.test(e.message)) {
    // inspect the binding that supplies the attachment url
  } else throw e
}

Prevention

When it happens

Trigger: An automation step (e.g. send email or webhook) receives an attachment object missing url — e.g. {filename: "a.txt"}, an empty object, or a mapped field whose URL binding resolved to null/undefined.

Common situations: Bindings that resolve to empty values at runtime; users pasting attachment objects from other systems with different key names; storage upload step output shape changed so only filename is produced.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/07bc04969b70e2f8. Report an issue: GitHub.