Origin private file system
The origin private file system (OPFS) is a storage endpoint provided as part of the File System API, which is private to the origin of the page and not visible to the user like the regular file system. It provides access to a special kind of file that is highly optimized for performance and offers in-place write access to its content.
Working with files using the File System Access API
The File System Access API, which extends the File System API, provides access to files using picker methods. For example:
-
Window.showOpenFilePicker()
allows the user to choose a file to access, which results in a FileSystemFileHandle
object being returned. -
FileSystemFileHandle.getFile()
is called to get access to the file's contents, the content is modified using FileSystemFileHandle.createWritable()
/ FileSystemWritableFileStream.write()
. -
FileSystemHandle.requestPermission({mode: 'readwrite'})
is used to request the user's permission to save the changes. - If the user accepts the permission request, the changes are saved back to the original file.
This works, but it has some restrictions. These changes are being made to the user-visible file system, so there are a lot of security checks in place (for example, safe browsing in Chrome) to guard against malicious content being written to that file system. These writes are not in-place, and instead use a temporary file. The original is not modified unless it passes all the security checks.
As a result, these operations are fairly slow. It is not so noticeable when you are making small text updates, but the performance suffers when making more significant, large-scale file updates such as SQLite database modifications.
How does the OPFS solve such problems?
The OPFS offers low-level, byte-by-byte file access, which is private to the origin of the page and not visible to the user. As a result, it doesn't require the same series of security checks and permission grants and is therefore faster than File System Access API calls. It also has a set of synchronous calls available (other File System API calls are asynchronous) that can be run inside web workers only so as not to block the main thread.
To summarize how the OPFS differs from the user-visible file system:
- The OPFS is subject to browser storage quota restrictions, just like any other origin-partitioned storage mechanism (for example IndexedDB API). You can access the amount of storage space the OPFS is using via
navigator.storage.estimate()
. - Clearing storage data for the site deletes the OPFS.
- Permission prompts and security checks are not required to access files in the OPFS.
- Browsers persist the contents of the OPFS to disk somewhere, but you cannot expect to find the created files matched one-to-one. The OPFS is not intended to be visible to the user.
How do you access the OPFS?
To access the OPFS in the first place, you call the navigator.storage.getDirectory()
method. This returns a reference to a FileSystemDirectoryHandle
object that represents the root of the OPFS.
Manipulating the OPFS from the main thread
When accessing the OPFS from the main thread, you will use asynchronous, Promise
-based APIs. You can access file (FileSystemFileHandle
) and directory (FileSystemDirectoryHandle
) handles by calling FileSystemDirectoryHandle.getFileHandle()
and FileSystemDirectoryHandle.getDirectoryHandle()
respectively on the FileSystemDirectoryHandle
object representing the OPFS root (and child directories, as they are created).
Note: Passing { create: true }
into the above methods causes the file or folder to be created if it doesn't exist.
const fileHandle = await opfsRoot
.getFileHandle('my first file', {create: true});
const directoryHandle = await opfsRoot
.getDirectoryHandle('my first folder', {create: true});
const nestedFileHandle = await directoryHandle
.getFileHandle('my first nested file', {create: true});
const nestedDirectoryHandle = await directoryHandle
.getDirectoryHandle('my first nested folder', {create: true});
const existingFileHandle = await opfsRoot.getFileHandle('my first file');
const existingDirectoryHandle = await opfsRoot
.getDirectoryHandle('my first folder);
Reading a file
- Make a
FileSystemDirectoryHandle.getFileHandle()
call to return a FileSystemFileHandle
object. - Call the
FileSystemFileHandle.getFile()
object to return a File
object. This is a specialized type of Blob
, and as such can be manipulated just like any other Blob
. For example, you could access the text content directly via Blob.text()
.
Writing a file
Deleting a file or folder
You can call FileSystemDirectoryHandle.removeEntry()
on the parent directory, passing it the name of the item you want to remove:
directoryHandle.removeEntry("my first nested file");
You can also call FileSystemHandle.remove()
on the FileSystemFileHandle
or FileSystemDirectoryHandle
representing the item you want to remove. To delete a folder including all subfolders, pass the { recursive: true }
option.
await fileHandle.remove();
await directoryHandle.remove({ recursive: true });
The following provides a quick way to clear the entire OPFS:
await (await navigator.storage.getDirectory()).remove({ recursive: true });
Listing the contents of a folder
FileSystemDirectoryHandle
is an asynchronous iterator. As such, you can iterate over it with a for await…of
loop and standard methods such as entries()
, values()
, and keys()
.
For example:
for await (let [name, handle] of directoryHandle) {
}
for await (let [name, handle] of directoryHandle.entries()) {
}
for await (let handle of directoryHandle.values()) {
}
for await (let name of directoryHandle.keys()) {
}
Manipulating the OPFS from a web worker
Web Workers don't block the main thread, which means you can use the synchronous file access APIs in this context. Synchronous APIs are faster as they avoid having to deal with promises.
You can synchronously access a file by calling FileSystemFileHandle.createSyncAccessHandle()
on a regular FileSystemFileHandle
:
Note: Despite having "Sync" in its name, the createSyncAccessHandle()
method itself is asynchronous.
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("my highspeed file.txt", {
create: true,
});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();
There are a number of synchronous methods available on the returned FileSystemSyncAccessHandle
:
-
getSize()
: Returns the size of the file in bytes. -
write()
: Writes the content of a buffer into the file, optionally at a given offset, and returns the number of written bytes. Checking the returned number of written bytes allows callers to detect and handle errors and partial writes. -
read()
: Reads the contents of the file into a buffer, optionally at a given offset. -
truncate()
: Resizes the file to the given size. -
flush()
: Ensures that the file contents contain all the modifications done through write()
. -
close()
: Closes the access handle.
Here is an example that uses all the methods mentioned above:
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle("fast", { create: true });
const accessHandle = await fileHandle.createSyncAccessHandle();
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
let size;
size = accessHandle.getSize();
const content = textEncoder.encode("Some text");
accessHandle.write(content, { at: size });
accessHandle.flush();
size = accessHandle.getSize();
const moreContent = textEncoder.encode("More content");
accessHandle.write(moreContent, { at: size });
accessHandle.flush();
size = accessHandle.getSize();
const dataView = new DataView(new ArrayBuffer(size));
accessHandle.read(dataView);
console.log(textDecoder.decode(dataView));
accessHandle.read(dataView, { at: 9 });
console.log(textDecoder.decode(dataView));
accessHandle.truncate(4);
Browser compatibility
|
Desktop |
Mobile |
|
Chrome |
Edge |
Firefox |
Internet Explorer |
Opera |
Safari |
WebView Android |
Chrome Android |
Firefox for Android |
Opera Android |
Safari on IOS |
Samsung Internet |
Origin_private_file_system |
86 |
86 |
111 |
No |
72 |
15.2 |
109 |
109 |
111 |
74 |
15.2 |
21.0 |
See also