{"record":{"id":"1ab6c44d899fb87e","repo":"langgenius/dify","slug":"formdata-is-required-for-audio-uploads","errorCode":null,"errorMessage":"FormData is required for audio uploads","messagePattern":"FormData is required for audio uploads","errorType":"validation","errorClass":"FileUploadError","httpStatus":null,"severity":"error","filePath":"sdks/nodejs-client/src/client/base.ts","lineNumber":205,"sourceCode":"  }\n\n  filePreview(fileId: string, user: string, asAttachment?: boolean): Promise<DifyResponse<Buffer>> {\n    ensureNonEmptyString(fileId, 'fileId')\n    ensureNonEmptyString(user, 'user')\n    return this.http.request<Buffer, 'bytes'>({\n      method: 'GET',\n      path: `/files/${fileId}/preview`,\n      query: {\n        user,\n        as_attachment: asAttachment ? 'true' : undefined,\n      },\n      responseType: 'bytes',\n    })\n  }\n\n  audioToText(form: unknown, user: string): Promise<DifyResponse<JsonObject>> {\n    if (!isFormData(form)) {\n      throw new FileUploadError('FormData is required for audio uploads')\n    }\n    ensureNonEmptyString(user, 'user')\n    appendUserToFormData(form, user)\n    return this.http.request({\n      method: 'POST',\n      path: '/audio-to-text',\n      data: form,\n    })\n  }\n\n  textToAudio(request: TextToAudioRequest): Promise<DifyResponse<Buffer> | BinaryStream>\n  textToAudio(\n    text: string,\n    user: string,\n    streaming?: boolean,\n    voice?: string,\n  ): Promise<DifyResponse<Buffer> | BinaryStream>\n  textToAudio(","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/sdks/nodejs-client/src/client/base.ts#L187-L223","documentation":"Thrown by audioToText() in base.ts:205 as a FileUploadError when the first argument does not satisfy isFormData(). The SDK requires a real FormData instance (browser FormData, or a Node FormData-like object exposing append/getHeaders) because the /audio-to-text endpoint consumes multipart/form-data. Passing a plain object, Buffer, ReadStream, or undefined is rejected before any HTTP call is made.","triggerScenarios":"Calling client.audioToText(payload, user) where payload is a JS object literal, a Buffer holding audio bytes, undefined/null, or a form library whose object is not recognized by isFormData() (no getHeaders method and constructor.name !== 'FormData'). The guard at base.ts:204 fails and FileUploadError is thrown synchronously.","commonSituations":"Migrating from an older SDK that accepted an object with a file path; using form-urlencoded instead of multipart; passing a Node Buffer directly; bundlers shimming FormData incorrectly; Node versions < 18 where global FormData is absent and the caller forgot to import a polyfill.","solutions":["Construct an actual FormData, append the audio file under the field name expected by the server (typically 'file'), then pass it: const form = new FormData(); form.append('file', fileBlob, 'audio.mp3'); await client.audioToText(form, user).","On Node < 18, install and import a FormData polyfill (e.g. form-data) so isFormData() recognizes it via getHeaders() or constructor.name === 'FormData'.","Confirm the value is not undefined by building the FormData in the same scope that calls audioToText rather than receiving it from an untyped source.","If reading from disk in Node, convert the buffer to a Blob/File before appending rather than passing the Buffer as the form argument."],"exampleFix":"// before\nawait client.audioToText({ file: '/tmp/a.mp3' }, user) // object, not FormData\n\n// after\nimport FormData from 'form-data'\nconst form = new FormData()\nform.append('file', fs.createReadStream('/tmp/a.mp3'), 'a.mp3')\nawait client.audioToText(form, user)","handlingStrategy":"type-guard","validationCode":"import { isFormData } from '@dify-platform/dify-client/src/http/form-data'\nfunction asAudioForm(value: unknown): FormData {\n  if (!isFormData(value)) throw new Error('audioToText requires a real FormData instance')\n  return value as FormData\n}","typeGuard":"import { isFormData } from '@dify-platform/dify-client/src/http/form-data'\nfunction isUploadableForm(value: unknown): value is FormData {\n  return isFormData(value)\n}","tryCatchPattern":"try {\n  await client.audioToText(form, user)\n} catch (err) {\n  if (err instanceof Error && err.name === 'FileUploadError') {\n    // log form construction issue, surface user-facing message\n  } else throw err\n}","preventionTips":["Build the FormData in the same function that calls audioToText so the variable's type is obvious.","On Node < 18, install a form-data polyfill once at process startup.","Add a unit test asserting isFormData(yourBuilder()) returns true."],"tags":["validation","form-data","audio","node","browser","multipart"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}