remix-run/remix · error · TypeError

Cannot convert LazyFile to string. Use .stream() to get a Re

Error message

Cannot convert LazyFile to string. Use .stream() to get a ReadableStream for Response and other streaming APIs, or .toFile()/.toBlob() for non-streaming APIs that require a complete File/Blob (e.g. FormData). Always prefer .stream() when possible.

What it means

LazyFile intentionally throws a TypeError from its toString() method to prevent implicit string coercion of lazy file content. LazyFile defers loading file data, so converting it to a string would force eager loading and is almost never what streaming code wants. The error directs you to the supported alternatives: .stream() for ReadableStream-based APIs, or .toFile()/.toBlob() when a complete File/Blob is required.

Source

Thrown at packages/lazy-file/src/lib/lazy-file.ts:354

   * **Warning:** This reads the entire content into memory, which defeats the purpose of using
   * a lazy file for large files. Only use this for non-streaming APIs that require a complete `File`
   * (e.g. `FormData`). For streaming, use `.stream()` instead.
   *
   * @returns A promise that resolves to a native `File`
   */
  async toFile(): Promise<File> {
    return new File([await this.bytes()], this.name, {
      type: this.type,
      lastModified: this.lastModified,
    })
  }

  /**
   * @throws Always throws a TypeError. LazyFile cannot be implicitly converted to a string.
   * Use `.stream()` to get a `ReadableStream` for `Response` and other streaming APIs, or `.toFile()`/`.toBlob()` for non-streaming APIs that require a complete `File`/`Blob` (e.g. `FormData`). Always prefer `.stream()` when possible.
   */
  toString(): never {
    throw new TypeError(
      'Cannot convert LazyFile to string. Use .stream() to get a ReadableStream for Response and other streaming APIs, or .toFile()/.toBlob() for non-streaming APIs that require a complete File/Blob (e.g. FormData). Always prefer .stream() when possible.',
    )
  }
}

/**
 * Union of Blob and lazy blob types.
 */
type BlobLike = Blob | LazyBlob | LazyFile

/**
 * Union of BlobPart and lazy blob types. Used for constructor signatures.
 */
type BlobPartLike = BlobPart | LazyBlob | LazyFile

function isBlobLike(value: unknown): value is BlobLike {
  return value instanceof Blob || value instanceof LazyBlob || value instanceof LazyFile
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Replace string coercion with file.stream() when feeding Response, fetch bodies, or other streaming APIs
  2. Use await file.toFile() or await file.toBlob() when the target API needs a complete File/Blob (e.g. FormData)
  3. If you need a filename or metadata rather than content, read the specific property (e.g. file.name) instead of coercing the whole object
  4. Audit template literals and String() calls around upload values coming from Remix form data

Example fix

// before
let s = `${lazyFile}` // throws
await fetch(url, { method: 'POST', body: lazyFile })

// after
await fetch(url, { method: 'POST', body: lazyFile.stream() })
// or for FormData:
let form = new FormData()
form.append('file', await lazyFile.toFile(), lazyFile.name)
Defensive patterns

Strategy: type-guard

Validate before calling

let isLazyFile = (v: unknown): v is LazyFile =>
  typeof v === 'object' && v !== null && 'stream' in v && 'toFile' in v

if (isLazyFile(value)) {
  body = value.stream()
} else {
  body = String(value)
}

Type guard

import type { LazyFile } from 'lazy-file'

function isLazyFile(value: unknown): value is LazyFile {
  return (
    typeof value === 'object' &&
    value !== null &&
    typeof (value as LazyFile).stream === 'function' &&
    typeof (value as LazyFile).toFile === 'function'
  )
}

Try / catch

try {
  payload = buildBody(value)
} catch (error) {
  if (error instanceof TypeError && /Cannot convert LazyFile to string/.test(error.message)) {
    payload = value.stream()
  } else throw error
}

Prevention

When it happens

Trigger: Any implicit or explicit string conversion of a LazyFile: template literals like `${file}`, string concatenation (file + '...'), String(file), JSON.stringify of an object containing a LazyFile, or passing it to an API that internally coerces to string instead of using .stream()/.toFile()/.toBlob().

Common situations: Using LazyFile upload values with libraries that expect strings (e.g. building a path, encoding into a URL, S3 key construction), logging a form value that is a LazyFile, or serializing a request payload containing a LazyFile to JSON.


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/2ed6e0ac03a8e7e8. Report an issue: GitHub.