Javascript Read Local File From Path Linux
The File System Access API: simplifying admission to local files
The File System Access API allows web apps to read or save changes directly to files and folders on the user's device.
— Updated
What is the File System Admission API? #
The File Arrangement Admission API (formerly known as Native File System API and prior to that information technology was called Writeable Files API) enables developers to build powerful spider web apps that collaborate with files on the user'south local device, like IDEs, photograph and video editors, text editors, and more. Afterwards a user grants a web app admission, this API allows them to read or save changes straight to files and folders on the user's device. Beyond reading and writing files, the File Arrangement Admission API provides the power to open a directory and enumerate its contents.
If you've worked with reading and writing files before, much of what I'm about to share will be familiar to you. I encourage you lot to read it anyhow, because not all systems are alike.
Current status #
Browser support #
Browser support: chrome 86, Supported 86 firefox, Not supported × border 86, Supported 86 safari, Non supported × Source
The File Organization Access API is currently supported on most Chromium browsers on Windows, macOS, Chrome OS, and Linux. A notable exception is Brave (dauntless/dauntless-browser#11407). Android support is planned; yous tin track progress by starring crbug.com/1011535.
Using the File Arrangement Access API #
To show off the power and usefulness of the File System Access API, I wrote a single file text editor. Information technology lets you open up a text file, edit it, save the changes back to deejay, or start a new file and save the changes to disk. Information technology's zip fancy, just provides enough to aid you sympathise the concepts.
Effort it #
See the File System Access API in action in the text editor demo.
Read a file from the local file system #
The first apply case I wanted to tackle was to ask the user to choose a file, and so open and read that file from disk.
Ask the user to pick a file to read #
The entry point to the File Organisation Access API is window.showOpenFilePicker()
. When called, it shows a file picker dialog box, and prompts the user to select a file. After they select a file, the API returns an array of file handles. An optional options
parameter lets yous influence the beliefs of the file picker, for example, by allowing the user to select multiple files, or directories, or unlike file types. Without whatever options specified, the file picker allows the user to select a unmarried file. This is perfect for a text editor.
Like many other powerful APIs, calling showOpenFilePicker()
must be done in a secure context, and must be chosen from inside a user gesture.
permit fileHandle;
butOpenFile. addEventListener ( 'click' , async ( ) => {
// Destructure the one-element array.
[fileHandle] = look window. showOpenFilePicker ( ) ;
// Do something with the file handle.
} ) ;
Once the user selects a file, showOpenFilePicker()
returns an array of handles, in this example a one-element array with i FileSystemFileHandle
that contains the properties and methods needed to collaborate with the file.
It'due south helpful to keep a reference to the file handle around so that it tin can exist used later. It'll be needed to relieve changes back to the file, or to perform whatever other file operations.
Read a file from the file system #
At present that you lot have a handle to a file, you can get the file'southward properties, or access the file itself. For now, I'll simply read its contents. Calling handle.getFile()
returns a File
object, which contains a blob. To go the information from the blob, phone call i of its methods, (slice()
, stream()
, text()
, or arrayBuffer()
).
const file = await fileHandle. getFile ( ) ;
const contents = expect file. text ( ) ;
The File
object returned by FileSystemFileHandle.getFile()
is only readable as long as the underlying file on disk hasn't changed. If the file on disk is modified, the File
object becomes unreadable and you'll demand to call getFile()
again to get a new File
object to read the changed data.
Putting it all together #
When users click the Open push, the browser shows a file picker. One time they've selected a file, the app reads the contents and puts them into a <textarea>
.
let fileHandle;
butOpenFile. addEventListener ( 'click' , async ( ) => {
[fileHandle] = await window. showOpenFilePicker ( ) ;
const file = await fileHandle. getFile ( ) ;
const contents = await file. text ( ) ;
textArea.value = contents;
} ) ;
Write the file to the local file system #
In the text editor, there are two ways to save a file: Save, and Save Every bit. Save simply writes the changes back to the original file using the file handle retrieved earlier. But Salve Every bit creates a new file, and thus requires a new file handle.
Create a new file #
To save a file, call showSaveFilePicker()
, which will testify the file picker in "save" fashion, allowing the user to selection a new file they want to apply for saving. For the text editor, I also wanted information technology to automatically add together a .txt
extension, so I provided some additional parameters.
async function getNewFileHandle ( ) {
const options = {
types: [
{
description: 'Text Files' ,
accept: {
'text/evidently' : [ '.txt' ] ,
} ,
} ,
] ,
} ;
const handle = wait window. showSaveFilePicker (options) ;
return handle;
}
Save changes to deejay #
You can observe all the code for saving changes to a file in my text editor demo on GitHub. The core file system interactions are in fs-helpers.js
. At its simplest, the process looks like the code beneath. I'll walk through each step and explain it.
async role writeFile ( fileHandle, contents ) {
// Create a FileSystemWritableFileStream to write to.
const writable = expect fileHandle. createWritable ( ) ;
// Write the contents of the file to the stream.
await writable. write (contents) ;
// Close the file and write the contents to disk.
await writable. shut ( ) ;
}
Writing information to deejay uses a FileSystemWritableFileStream
object, substantially a WritableStream
. Create the stream by calling createWritable()
on the file handle object. When createWritable()
is called, the browser first checks if the user has granted write permission to the file. If permission to write hasn't been granted, the browser volition prompt the user for permission. If permission isn't granted, createWritable()
will throw a DOMException
, and the app will not be able to write to the file. In the text editor, these DOMException
south are handled in the saveFile()
method.
The write()
method takes a string, which is what's needed for a text editor. But it can also have a BufferSource, or a Blob. For example, you can pipe a stream direct to it:
async function writeURLToFile ( fileHandle, url ) {
// Create a FileSystemWritableFileStream to write to.
const writable = await fileHandle. createWritable ( ) ;
// Make an HTTP request for the contents.
const response = expect fetch (url) ;
// Stream the response into the file.
expect response.body. pipeTo (writable) ;
// pipeTo() closes the destination pipe by default, no demand to close information technology.
}
You can besides seek()
, or truncate()
inside the stream to update the file at a specific position, or resize the file.
Specifying a suggested file name and first directory #
In many cases y'all may desire your app to suggest a default file proper noun or location. For example, a text editor might want to suggest a default file proper noun of Untitled Text.txt
rather than Untitled
. You can attain this by passing a suggestedName
holding as part of the showSaveFilePicker
options.
const fileHandle = await cocky. showSaveFilePicker ( {
suggestedName: 'Untitled Text.txt' ,
types: [ {
clarification: 'Text documents' ,
accept: {
'text/manifestly' : [ '.txt' ] ,
} ,
} ] ,
} ) ;
The same goes for the default commencement directory. If yous're edifice a text editor, you lot may desire to beginning the file save or file open dialog in the default documents
folder, whereas for an paradigm editor, may want to start in the default pictures
folder. You tin can suggest a default start directory past passing a startIn
belongings to the showSaveFilePicker
, showDirectoryPicker()
, or showOpenFilePicker
methods similar so.
const fileHandle = await self. showOpenFilePicker ( {
startIn: 'pictures'
} ) ;
The list of the well-known organisation directories is:
-
desktop
: The user's desktop directory, if such a thing exists. -
documents
: Directory in which documents created by the user would typically be stored. -
downloads
: Directory where downloaded files would typically be stored. -
music
: Directory where audio files would typically be stored. -
pictures
: Directory where photos and other still images would typically exist stored. -
videos
: Directory where videos/movies would typically exist stored.
Autonomously from well-known system directories, you tin can too pass an existing file or directory handle as a value for startIn
. The dialog would then open in the same directory.
// Presume `directoryHandle` were a handle to a previously opened directory.
const fileHandle = expect self. showOpenFilePicker ( {
startIn: directoryHandle
} ) ;
Specifying the purpose of different file pickers #
Sometimes applications accept different pickers for different purposes. For instance, a rich text editor may allow the user to open text files, but likewise to import images. By default, each file picker would open at the terminal-remembered location. You tin circumvent this by storing id
values for each type of picker. If an id
is specified, the file picker implementation will remember a separate last-used directory for pickers with that same id
.
const fileHandle1 = look self. showSaveFilePicker ( {
id: 'openText' ,
} ) ; const fileHandle2 = expect self. showSaveFilePicker ( {
id: 'importImage' ,
} ) ;
Storing file handles or directory handles in IndexedDB #
File handles and directory handles are serializable, which means that you can salve a file or directory handle to IndexedDB, or call postMessage()
to ship them between the same top-level origin.
Saving file or directory handles to IndexedDB means that you lot can store state, or remember which files or directories a user was working on. This makes it possible to keep a list of recently opened or edited files, offer to re-open the last file when the app is opened, restore the previous working directory, and more. In the text editor, I store a listing of the v most contempo files the user has opened, making it like shooting fish in a barrel to access those files once more.
The code example below shows storing and retrieving a file handle and a directory handle. Yous can see this in activity over on Glitch (I utilize the idb-keyval library for brevity).
import { go, set } from 'https://unpkg.com/idb-keyval@five.0.two/dist/esm/index.js' ; const pre1 = document. querySelector ( 'pre.file' ) ;
const pre2 = document. querySelector ( 'pre.directory' ) ;
const button1 = document. querySelector ( 'push.file' ) ;
const button2 = document. querySelector ( 'button.directory' ) ;
// File handle
button1. addEventListener ( 'click' , async ( ) => {
attempt {
const fileHandleOrUndefined = look go ( 'file' ) ;
if (fileHandleOrUndefined) {
pre1.textContent = ` Retrieved file handle " ${fileHandleOrUndefined.proper noun} " from IndexedDB. ` ;
return ;
}
const [fileHandle] = look window. showOpenFilePicker ( ) ;
await ready ( 'file' , fileHandle) ;
pre1.textContent = ` Stored file handle for " ${fileHandle.name} " in IndexedDB. ` ;
} catch (mistake) {
alert (error.name, error.message) ;
}
} ) ;
// Directory handle
button2. addEventListener ( 'click' , async ( ) => {
try {
const directoryHandleOrUndefined = await become ( 'directory' ) ;
if (directoryHandleOrUndefined) {
pre2.textContent = ` Retrieved directroy handle " ${directoryHandleOrUndefined.name} " from IndexedDB. ` ;
return ;
}
const directoryHandle = await window. showDirectoryPicker ( ) ;
expect set ( 'directory' , directoryHandle) ;
pre2.textContent = ` Stored directory handle for " ${directoryHandle.name} " in IndexedDB. ` ;
} catch (mistake) {
alert (error.name, error.message) ;
}
} ) ;
Stored file or directory handles and permissions #
Since permissions currently are not persisted between sessions, y'all should verify whether the user has granted permission to the file or directory using queryPermission()
. If they oasis't, utilise requestPermission()
to (re-)request it. This works the same for file and directory handles. You need to run fileOrDirectoryHandle.requestPermission(descriptor)
or fileOrDirectoryHandle.queryPermission(descriptor)
respectively.
In the text editor, I created a verifyPermission()
method that checks if the user has already granted permission, and if required, makes the request.
async office verifyPermission ( fileHandle, readWrite ) {
const options = { } ;
if (readWrite) {
options.mode = 'readwrite' ;
}
// Check if permission was already granted. If and so, return true.
if ( ( await fileHandle. queryPermission (options) ) === 'granted' ) {
return true ;
}
// Request permission. If the user grants permission, render true.
if ( ( look fileHandle. requestPermission (options) ) === 'granted' ) {
return truthful ;
}
// The user didn't grant permission, so return false.
render imitation ;
}
By requesting write permission with the read asking, I reduced the number of permission prompts: the user sees one prompt when opening the file, and grants permission to both read and write to it.
Opening a directory and enumerating its contents #
To enumerate all files in a directory, call showDirectoryPicker()
. The user selects a directory in a picker, after which a FileSystemDirectoryHandle
is returned, which lets you enumerate and admission the directory's files.
const butDir = document. getElementById ( 'butDirectory' ) ;
butDir. addEventListener ( 'click' , async ( ) => {
const dirHandle = await window. showDirectoryPicker ( ) ;
for await ( const entry of dirHandle. values ( ) ) {
console. log (entry.kind, entry.name) ;
}
} ) ;
If you additionally need to admission each file via getFile()
to, for example, obtain the individual file sizes, do not use wait
on each result sequentially, but rather process all files in parallel, for example, via Promise.all()
.
const butDir = document. getElementById ( 'butDirectory' ) ;
butDir. addEventListener ( 'click' , async ( ) => {
const dirHandle = expect window. showDirectoryPicker ( ) ;
const promises = [ ] ;
for look ( const entry of dirHandle. values ( ) ) {
if (entry.kind !== 'file' ) {
break ;
}
promises. push (entry. getFile ( ) . then ( ( file ) => ` ${file.proper name} ( ${file.size} ) ` ) ) ;
}
panel. log ( look Promise. all (promises) ) ;
} ) ;
Creating or accessing files and folders in a directory #
From a directory, you can create or access files and folders using the getFileHandle()
or respectively the getDirectoryHandle()
method. By passing in an optional options
object with a key of create
and a boolean value of true
or false
, y'all can make up one's mind if a new file or folder should be created if it doesn't be.
// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle. getDirectoryHandle ( 'My Documents' , {
create: truthful ,
} ) ;
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle. getFileHandle ( 'My Notes.txt' , { create: truthful } ) ;
Resolving the path of an item in a directory #
When working with files or folders in a directory, it tin exist useful to resolve the path of the item in question. This tin be done with the aptly named resolve()
method. For resolving, the item tin be a straight or indirect child of the directory.
// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle. resolve (newFileHandle) ;
// `path` is now ["My Documents", "My Notes.txt"]
Deleting files and folders in a directory #
If yous have obtained admission to a directory, you can delete the independent files and folders with the removeEntry()
method. For folders, deletion can optionally exist recursive and include all subfolders and the therein independent files.
// Delete a file.
wait directoryHandle. removeEntry ( 'Abandoned Projects.txt' ) ;
// Recursively delete a binder.
wait directoryHandle. removeEntry ( 'Old Stuff' , { recursive: true } ) ;
Deleting a file or folder directly #
If you lot have access to a file or directory handle, call remove()
on a FileSystemFileHandle
or FileSystemDirectoryHandle
to remove it.
// Delete a file.
await fileHandle. remove ( ) ;
// Delete a directory.
look directoryHandle. remove ( ) ;
Renaming and moving files and folders #
Files and folders can be renamed or moved to a new location by calling motion()
on the FileSystemHandle
interface. FileSystemHandle
has the child interfaces FileSystemFileHandle
and FileSystemDirectoryHandle
. The move()
method takes one or two parameters. The first can either be a cord with the new proper noun or a FileSystemDirectoryHandle
to the destination folder. In the latter case, the optional second parameter is a string with the new name, so moving and renaming can happen in i step.
// Rename the file.
await file. move ( 'new_name' ) ;
// Move the file to a new directory.
await file. move (directory) ;
// Move the file to a new directory and rename information technology.
await file. movement (directory, 'newer_name' ) ;
Drag and drop integration #
The HTML Drag and Drop interfaces enable web applications to accept dragged and dropped files on a web page. During a drag and drop functioning, dragged file and directory items are associated with file entries and directory entries respectively. The DataTransferItem.getAsFileSystemHandle()
method returns a promise with a FileSystemFileHandle
object if the dragged particular is a file, and a promise with a FileSystemDirectoryHandle
object if the dragged detail is a directory. The listing below shows this in action. Notation that the Drag and Driblet interface's DataTransferItem.kind
will be "file"
for both files and directories, whereas the File Organization Admission API's FileSystemHandle.kind
will be "file"
for files and "directory"
for directories.
elem. addEventListener ( 'dragover' , ( e ) => {
// Prevent navigation.
e. preventDefault ( ) ;
} ) ; elem. addEventListener ( 'driblet' , async ( e ) => {
eastward. preventDefault ( ) ;
const fileHandlesPromises = [ ...e.dataTransfer.items]
. filter ( ( detail ) => item.kind === 'file' )
. map ( ( particular ) => item. getAsFileSystemHandle ( ) ) ;
for look ( const handle of fileHandlesPromises) {
if (handle.kind === 'directory' ) {
console. log ( ` Directory: ${handle.name} ` ) ;
} else {
console. log ( ` File: ${handle.name} ` ) ;
}
}
} ) ;
Accessing the origin individual file system #
The origin individual file arrangement is a storage endpoint that, every bit the name suggests, is individual to the origin of the page. While browsers will typically implement this by persisting the contents of this origin private file organization to deejay somewhere, information technology is non intended that the contents be easily user accessible. Similarly, in that location is no expectation that files or directories with names matching the names of children of the origin private file organization be. While the browser might make information technology seem that there are files, internally—since this is an origin individual file system—the browser might shop these "files" in a database or whatsoever other information structure. Essentially: what you create with this API, do non expect to observe it ane:1 somewhere on the difficult disk. You can operate as usual on the origin private file system once you accept access to the root FileSystemDirectoryHandle
.
const root = await navigator.storage. getDirectory ( ) ;
// Create a new file handle.
const fileHandle = await root. getFileHandle ( 'Untitled.txt' , { create: true } ) ;
// Create a new directory handle.
const dirHandle = await root. getDirectoryHandle ( 'New Folder' , { create: truthful } ) ;
// Recursively remove a directory.
expect root. removeEntry ( 'Old Stuff' , { recursive: true } ) ;
Accessing files optimized for performance from the origin private file system #
The origin private file system provides optional access to a special kind of file that is highly optimized for operation, for example, by offering in-place and exclusive write access to a file'due south content. There is an origin trial starting in Chromium 95 and ending in Chromium 98 (February 23, 2022) for simplifying how such files can be accessed by exposing two new methods as part of the origin private file system: createAccessHandle()
(asynchronous read and write operations) and createSyncAccessHandle()
(synchronous read and write operations) that are both exposed on FileSystemFileHandle
.
// Asynchronous access in all contexts:
const handle = await file. createAccessHandle ( { fashion: 'in-identify' } ) ;
expect handle.writable. getWriter ( ) . write (buffer) ;
const reader = handle.readable. getReader ( { mode: 'byob' } ) ;
// Assumes seekable streams, and SharedArrayBuffer support are available
await reader. read (buffer, { at: 1 } ) ;
// (Read and write operations are synchronous,
// but obtaining the handle is asynchronous.)
// Synchronous access exclusively in Worker contexts
const handle = await file. createSyncAccessHandle ( ) ;
const writtenBytes = handle. write (buffer) ;
const readBytes = handle. read (buffer, { at: 1 } ) ;
Polyfilling #
Information technology is non possible to completely polyfill the File Arrangement Access API methods.
- The
showOpenFilePicker()
method tin can be approximated with an<input blazon="file">
element. - The
showSaveFilePicker()
method tin exist simulated with a<a download="file_name">
chemical element, albeit this will trigger a programmatic download and not allow for overwriting existing files. - The
showDirectoryPicker()
method can be somewhat emulated with the non-standard<input type="file" webkitdirectory>
element.
We have developed a library called browser-fs-access that uses the File System Admission API wherever possible and that falls dorsum to these next best options in all other cases.
Security and permissions #
The Chrome team has designed and implemented the File Organisation Admission API using the core principles defined in Decision-making Access to Powerful Web Platform Features, including user control and transparency, and user ergonomics.
Opening a file or saving a new file #
When opening a file, the user provides permission to read a file or directory via the file picker. The open file picker can just be shown via a user gesture when served from a secure context. If users change their minds, they can abolish the selection in the file picker and the site does not get access to annihilation. This is the aforementioned behavior as that of the <input type="file">
chemical element.
Similarly, when a web app wants to relieve a new file, the browser volition testify the salvage file picker, allowing the user to specify the proper noun and location of the new file. Since they are saving a new file to the device (versus overwriting an existing file), the file picker grants the app permission to write to the file.
Restricted folders #
To help protect users and their information, the browser may limit the user's ability to salve to certain folders, for example, core operating system folders like Windows, the macOS Library folders, etc. When this happens, the browser will show a modal prompt and ask the user to choose a unlike folder.
Modifying an existing file or directory #
A web app cannot alter a file on disk without getting explicit permission from the user.
Permission prompt #
If a person wants to save changes to a file that they previously granted read access to, the browser volition show a modal permission prompt, requesting permission for the site to write changes to deejay. The permission asking can only be triggered past a user gesture, for example, by clicking a Salvage button.
Alternatively, a spider web app that edits multiple files, similar an IDE, can besides ask for permission to save changes at the time of opening.
If the user chooses Cancel, and does not grant write access, the web app cannot salvage changes to the local file. It should provide an alternative method to allow the user to save their data, for example by providing a way to "download" the file, saving data to the deject, etc.
Transparency #
Once a user has granted permission to a web app to save a local file, the browser will prove an icon in the URL bar. Clicking on the icon opens a pop-over showing the list of files the user has given access to. The user can easily revoke that access if they cull.
Permission persistence #
The web app can go on to save changes to the file without prompting until all tabs for that origin are closed. Once a tab is closed, the site loses all access. The next fourth dimension the user uses the web app, they volition exist re-prompted for admission to the files.
Feedback #
We want to hear well-nigh your experiences with the File Organisation Access API.
Tell us virtually the API blueprint #
Is there something near the API that doesn't work similar yous expected? Or are at that place missing methods or backdrop that y'all need to implement your idea? Have a question or comment on the security model?
- File a spec issue on the WICG File System Access GitHub repo, or add together your thoughts to an existing issue.
Problem with the implementation? #
Did you discover a bug with Chrome's implementation? Or is the implementation different from the spec?
- File a bug at https://new.crbug.com. Exist sure to include equally much detail as y'all can, simple instructions for reproducing, and set up Components to
Blink>Storage>FileSystem
. Glitch works great for sharing quick and easy repros.
Planning to utilize the API? #
Planning to employ the File Organisation Access API on your site? Your public support helps us to prioritize features, and shows other browser vendors how disquisitional it is to support them.
- Share how yous plan to utilize it on the WICG Soapbox thread.
- Send a tweet to @ChromiumDev using the hashtag
#FileSystemAccess
and let us know where and how you lot're using information technology.
Helpful links #
- Public explainer
- File Organization Access specification & File specification
- Tracking bug
- ChromeStatus.com entry
- Asking an origin trial token
- TypeScript definitions
- File System Access API - Chromium Security Model
- Blink Component:
Blink>Storage>FileSystem
Acknowledgements #
The File Arrangement Access API spec was written by Marijn Kruisselbrink.
Last updated: — Improve commodity
Render to all articles
Javascript Read Local File From Path Linux
Source: https://web.dev/file-system-access/