Node.js applications frequently read files before sending them to an API, processing uploads, or passing binary data to a library. The traditional approach is to use readFileSync() or readFile() to load the file into a Buffer. That works well for many tasks, but it can require allocating memory for the entire file before processing begins.

The Web Platform's Blob interface provides another way to represent binary data. Node.js already supports the asynchronous fs.openAsBlob() API, and Node.js 26.10.0 introduced fs.openAsBlobSync() for applications that need to create a file-backed Blob synchronously. <Cite refs={["turn404746search3","turn404746search4"]}/>

This API is particularly useful when an existing synchronous workflow needs to pass file data into an API that accepts a Blob, such as fetch() or FormData.

In this article, we'll explore how openAsBlobSync() works, how to use it with file uploads, when it is preferable to a Buffer, and which file-handling constraints matter in production.

What Is openAsBlobSync()?

The openAsBlobSync() function belongs to Node.js's built-in node:fs module. It creates a Blob backed by a file on disk rather than requiring the application to read the entire file into a Buffer first.

Its basic signature is:

openAsBlobSync(path[, options])

The path argument accepts a string, a Buffer, or a URL identifying the file. The optional options object supports a type property for specifying the MIME type of the resulting Blob.

For example:

import { openAsBlobSync } from 'node:fs';

const file = openAsBlobSync('./report.pdf', {
  type: 'application/pdf'
});

console.log(file instanceof Blob); // true
console.log(file.type);            // application/pdf
console.log(file.size);            // File size in bytes

Unlike fs.openAsBlob(), which returns a promise, openAsBlobSync() returns the Blob directly. This makes it convenient for synchronous initialization code and scripts that need a Blob immediately.

The API was introduced in Node.js 26.10.0. Use a compatible Node.js release when adopting it; older runtimes will not expose the function. <Cite refs={["turn404746search3","turn404746search4"]}/>

How File-Backed Blobs Differ From Buffers

A Buffer represents bytes held in memory. A file-backed Blob provides a standard binary-data interface whose content is backed by the file.

Consider the two approaches:

import { readFileSync } from 'node:fs';

const buffer = readFileSync('./large-report.pdf');

The file contents are read into a Buffer. For large files, this can increase memory consumption because the complete file must be represented in memory.

With a file-backed Blob:

import { openAsBlobSync } from 'node:fs';

const blob = openAsBlobSync('./large-report.pdf', {
  type: 'application/pdf'
});

The application gets a Blob without first loading the entire file into a Buffer.

This can be advantageous when the next API already accepts a Blob or consumes data through a stream. However, do not assume that every operation remains memory-efficient. Calling blob.arrayBuffer() still materializes the entire contents in memory, and downstream APIs may buffer data internally.

Choose the representation based on the next operation, not merely on which API creates it.

Upload a File Using Fetch and FormData

One practical use case is sending a file to an HTTP endpoint using FormData.

Node.js provides built-in fetch() and FormData APIs, so an application can construct a multipart request without installing a separate HTTP client.

import { openAsBlobSync } from 'node:fs';

const file = openAsBlobSync('./report.pdf', {
  type: 'application/pdf'
});

const form = new FormData();

form.append('document', file, 'report.pdf');

const response = await fetch('https://api.example.com/upload', {
  method: 'POST',
  body: form
});

if (!response.ok) {
  throw new Error(`Upload failed: HTTP ${response.status}`);
}

console.log('Upload completed.');

Replace the example endpoint with the actual upload API. The server must accept multipart form data and recognize the document field.

Notice that the code does not set the Content-Type request header manually. When fetch() sends a FormData body, it generates the multipart content type, including the boundary required to separate fields.

Setting Content-Type: multipart/form-data yourself without the matching boundary can cause the server to reject the request or fail to parse the uploaded file.

Also, openAsBlobSync() only prepares the file representation. It does not perform the upload, validate the destination, or guarantee that the remote server will accept the file.

Process File Data Through a Stream

A Blob can expose its contents as a ReadableStream. This is useful when the consumer can process the file incrementally.

import { openAsBlobSync } from 'node:fs';

const file = openAsBlobSync('./application.log', {
  type: 'text/plain'
});

const reader = file.stream().getReader();

try {
  while (true) {
    const { done, value } = await reader.read();

    if (done) {
      break;
    }

    console.log(`Received ${value.byteLength} bytes`);
  }
} finally {
  reader.releaseLock();
}

The stream yields byte chunks rather than one complete ArrayBuffer. In a real application, those chunks would typically be passed to a parser, transform, compression operation, or other streaming consumer.

This example demonstrates the stream interface, but it is not a complete line-by-line log parser. If you need to process text by line, use a suitable streaming text decoder and line-splitting strategy rather than assuming that each chunk ends at a newline.

Streaming can reduce peak memory usage when the consumer processes each chunk and releases it before requesting more data. It does not automatically guarantee bounded memory if the consumer accumulates all chunks or if a downstream operation buffers the entire input.

Read the Entire File When Necessary

Sometimes a downstream library requires a complete ArrayBuffer or Buffer. A Blob can still provide a convenient interface for those cases.

import { openAsBlobSync } from 'node:fs';

const blob = openAsBlobSync('./configuration.json', {
  type: 'application/json'
});

const text = await blob.text();
const configuration = JSON.parse(text);

console.log(configuration);

The text() method reads the file contents and decodes them as text. For a small configuration file, this is a reasonable approach.

However, it is not a memory optimization for a large file. The application materializes the file contents as a string before parsing the JSON. For large datasets, consider a streaming parser or a format designed for incremental processing.

The important distinction is that openAsBlobSync() changes how the file is represented initially. It does not eliminate the cost of reading the data when an operation requires the complete contents.

Do Not Modify the File After Creating the Blob

The most important constraint is that the underlying file must not be modified after the Blob is created.

Node.js checks file metadata when creating the Blob and before reading its contents to detect modifications. If the file changes, reading the Blob data fails with a DOMException. <Cite refs={["turn404746search0","turn404746search4"]}/>

For example, avoid this pattern:

import {
  appendFileSync,
  openAsBlobSync
} from 'node:fs';

const blob = openAsBlobSync('./report.txt', {
  type: 'text/plain'
});

appendFileSync('./report.txt', '\nNew content');

const text = await blob.text();

The modification invalidates the assumption that the file contents remain unchanged. The read can fail instead of returning a consistent snapshot.

This matters when files are generated dynamically, updated by another process, or replaced while an upload is in progress.

A safer workflow is to finish writing the file first, then create the Blob, and leave the file unchanged until all consumers have finished reading it.

For temporary files, coordinate ownership and cleanup carefully. Do not delete or replace a file while a consumer may still need to read its contents.

Handle Errors and File Lifecycles

Because openAsBlobSync() is synchronous, file-opening errors are thrown immediately. Examples include a missing path, insufficient permissions, or a path that does not identify a readable file.

A small helper can make the error boundary explicit:

import { openAsBlobSync } from 'node:fs';

function createFileBlob(path, type) {
  try {
    return openAsBlobSync(path, { type });
  } catch (error) {
    throw new Error(
      `Unable to create a Blob for the requested file: ${path}`,
      { cause: error }
    );
  }
}

The wrapper preserves the underlying error as cause, which can help with authorized diagnostics. In an HTTP application, avoid exposing filesystem paths or internal error details to untrusted clients.

The function does not return a file descriptor or an explicit close handle. Treat the returned Blob as the file representation and coordinate its use through the operations that consume it.

If your application needs explicit file-descriptor control, random-access reads, or a writable file handle, the lower-level fs.open() and related APIs may be more appropriate.

When Should You Use openAsBlobSync()?

Use openAsBlobSync() when all of the following are true:

  • Your runtime supports the API.

  • Your application needs a Blob rather than a Buffer.

  • Synchronous creation is appropriate for the current execution context.

  • The file will remain unchanged while the Blob is consumed.

For an upload script that runs once, creating a file-backed Blob before constructing a FormData request is a reasonable use case.

For a high-throughput server, synchronous filesystem operations can block the event loop. If requests frequently create Blob objects from files, evaluate the asynchronous openAsBlob() API instead.

The asynchronous version was introduced earlier and is stable in current Node.js releases. It returns a promise, so callers can await creation without synchronously blocking the event loop during the file-opening operation. <Cite refs={["turn404746search1","turn404746search5"]}/>

For small files that must be transformed or inspected immediately, readFile() or readFileSync() may remain the simpler option. For streaming operations that need explicit file-handle control, use the relevant lower-level filesystem APIs.

Common Mistakes to Avoid

Assuming the entire operation is synchronous. openAsBlobSync() returns immediately, but methods such as text(), arrayBuffer(), and stream reads still use asynchronous interfaces.

Modifying the file after creating the Blob. Node.js detects file changes and can reject subsequent reads. Finish writing the file before creating the Blob.

Calling arrayBuffer() on a very large file without considering memory. This materializes the complete file contents. Use a streaming consumer if incremental processing is appropriate.

Using synchronous operations in a request-heavy server without measuring the impact. Synchronous filesystem work blocks the JavaScript thread. Prefer asynchronous alternatives when event-loop responsiveness matters.

Setting the multipart content type manually. Let fetch() construct the appropriate Content-Type header and boundary when sending FormData.

Assuming a Blob guarantees an immutable snapshot. The Blob is backed by a file, and modifications to that file can invalidate reads. Keep the file stable for the lifetime of the operation.

Summary

fs.openAsBlobSync() provides a synchronous way to create a file-backed Blob in Node.js. Introduced in Node.js 26.10.0, it is useful when working with APIs such as fetch() and FormData that already accept Blob objects. <Cite refs={["turn404746search3","turn404746search4"]}/>

It avoids initially loading the complete file into a Buffer, but it does not guarantee that downstream processing will remain memory-efficient. Operations that request the complete contents still allocate memory, and synchronous file operations can affect event-loop responsiveness.

Use it when a synchronous workflow benefits from a file-backed binary representation, keep the underlying file unchanged while it is being consumed, and prefer asynchronous or lower-level filesystem APIs when their lifecycle and performance characteristics better match the application.