The File System Standard introduces an origin private file system (OPFS) as a
storage endpoint private to the origin of the page and not visible to the user
that provides optional access to a special kind of file that is highly optimized
for performance.


![Thomas Steiner](https://web.dev/images/authors/thomassteiner.jpg) Thomas Steiner [GitHub](https://github.com/tomayac) [LinkedIn](https://www.linkedin.com/in/thomassteinerlinkedin) [Mastodon](https://toot.cafe/@tomayac) [Bluesky](https://bsky.app/profile/tomayac.com) [Homepage](https://blog.tomayac.com/)

<br />

> [!TIP]
> **Success:** The origin private file system allows web apps to store and manipulate files in their very own origin-specific virtual file system, including low-level file manipulation, byte-by-byte access, and file streaming. The origin private file system is supported across all major browsers.

## Browser support

The origin private file system is supported by modern browsers and is
standardized by the Web Hypertext Application Technology Working Group
([WHATWG](https://whatwg.org/)) in the
[File System Living Standard](https://fs.spec.whatwg.org/).


Browser Support

- ![Chrome: 86.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='-10 -10 276 276'%3E%3ClinearGradient id='a' x1='145' x2='34' y1='253' y2='61' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%231e8e3e'/%3E%3Cstop offset='1' stop-color='%2334a853'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='111' x2='222' y1='254' y2='62' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcc934'/%3E%3Cstop offset='1' stop-color='%23fbbc04'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' x1='17' x2='239' y1='80' y2='80' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23d93025'/%3E%3Cstop offset='1' stop-color='%23ea4335'/%3E%3C/linearGradient%3E%3Ccircle cx='128' cy='128' r='64' fill='%23fff'/%3E%3Cpath fill='url(%23a)' d='M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z'/%3E%3Cpath fill='url(%23b)' d='M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z'/%3E%3Ccircle cx='128' cy='128' r='52' fill='%231a73e8'/%3E%3Cpath fill='url(%23c)' d='M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z'/%3E%3C/svg%3E)
- ![Edge: 86.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 27600 27600'%3E%3ClinearGradient id='A' gradientUnits='userSpaceOnUse'/%3E%3ClinearGradient id='B' x1='6870' x2='24704' y1='18705' y2='18705' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%230c59a4'/%3E%3Cstop offset='1' stop-color='%23114a8b'/%3E%3C/linearGradient%3E%3ClinearGradient id='C' x1='16272' x2='5133' y1='10968' y2='23102' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%231b9de2'/%3E%3Cstop offset='.16' stop-color='%231595df'/%3E%3Cstop offset='.67' stop-color='%230680d7'/%3E%3Cstop offset='1' stop-color='%230078d4'/%3E%3C/linearGradient%3E%3CradialGradient id='D' cx='16720' cy='18747' r='9538' xlink:href='%23A'%3E%3Cstop offset='.72' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.53'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='E' cx='7130' cy='19866' r='14324' gradientTransform='matrix(.14843 -.98892 .79688 .1196 -8759 25542)' xlink:href='%23A'%3E%3Cstop offset='.76' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.5'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='F' cx='2523' cy='4680' r='20243' gradientTransform='matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2335c1f1'/%3E%3Cstop offset='.11' stop-color='%2334c1ed'/%3E%3Cstop offset='.23' stop-color='%232fc2df'/%3E%3Cstop offset='.31' stop-color='%232bc3d2'/%3E%3Cstop offset='.67' stop-color='%2336c752'/%3E%3C/radialGradient%3E%3CradialGradient id='G' cx='24247' cy='7758' r='9734' gradientTransform='matrix(.28109 .95968 -.78353 .22949 24510 -16292)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2366eb6e'/%3E%3Cstop offset='1' stop-color='%2366eb6e' stop-opacity='0'/%3E%3C/radialGradient%3E%3Cpath id='H' d='M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z'/%3E%3Cpath id='I' d='M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z'/%3E%3Cpath id='J' d='M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z'/%3E%3Cuse fill='url(%23B)' xlink:href='%23H'/%3E%3Cuse fill='url(%23D)' opacity='.35' xlink:href='%23H'/%3E%3Cuse fill='url(%23C)' xlink:href='%23I'/%3E%3Cuse fill='url(%23E)' opacity='.4' xlink:href='%23I'/%3E%3Cuse fill='url(%23F)' xlink:href='%23J'/%3E%3Cuse fill='url(%23G)' xlink:href='%23J'/%3E%3C/svg%3E)
- ![Firefox: 111.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 512 512'%3E%3Cdefs%3E%3CradialGradient id='ff-b' cx='428.5' cy='55.1' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23ffbd4f'/%3E%3Cstop offset='.2' stop-color='%23ffac31'/%3E%3Cstop offset='.3' stop-color='%23ff9d17'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff563b'/%3E%3Cstop offset='.5' stop-color='%23ff3750'/%3E%3Cstop offset='.7' stop-color='%23f5156c'/%3E%3Cstop offset='.8' stop-color='%23eb0878'/%3E%3Cstop offset='.9' stop-color='%23e50080'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-c' cx='245.4' cy='259.9' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%23960e18'/%3E%3Cstop offset='.3' stop-color='%23b11927' stop-opacity='.7'/%3E%3Cstop offset='.4' stop-color='%23db293d' stop-opacity='.3'/%3E%3Cstop offset='.5' stop-color='%23f5334b' stop-opacity='.1'/%3E%3Cstop offset='.5' stop-color='%23ff3750' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-d' cx='305.8' cy='-58.6' r='363' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.3' stop-color='%23ffdc3e'/%3E%3Cstop offset='.5' stop-color='%23ff9d12'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-e' cx='190' cy='390.8' r='238.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%233a8ee6'/%3E%3Cstop offset='.5' stop-color='%235c79f0'/%3E%3Cstop offset='.7' stop-color='%239059ff'/%3E%3Cstop offset='1' stop-color='%23c139e6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-f' cx='252.2' cy='201.3' r='126.5' gradientTransform='matrix(1 0 0 1 -48 31)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%239059ff' stop-opacity='0'/%3E%3Cstop offset='.3' stop-color='%238c4ff3' stop-opacity='.1'/%3E%3Cstop offset='.8' stop-color='%237716a8' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%236e008b' stop-opacity='.6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-g' cx='239.1' cy='34.6' r='171.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffe226'/%3E%3Cstop offset='.1' stop-color='%23ffdb27'/%3E%3Cstop offset='.3' stop-color='%23ffc82a'/%3E%3Cstop offset='.5' stop-color='%23ffa930'/%3E%3Cstop offset='.7' stop-color='%23ff7e37'/%3E%3Cstop offset='.8' stop-color='%23ff7139'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-h' cx='374' cy='-74.3' r='732.2' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-i' cx='304.6' cy='7.1' r='536.4' gradientTransform='rotate(84 303 4)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.6' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-j' cx='235' cy='98.1' r='457.1' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-k' cx='355.7' cy='124.9' r='500.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.2' stop-color='%23ffe141'/%3E%3Cstop offset='.5' stop-color='%23ffaf1e'/%3E%3Cstop offset='.6' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3ClinearGradient id='ff-a' x1='446.9' y1='76.8' x2='47.9' y2='461.8' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.4' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.5' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/linearGradient%3E%3ClinearGradient id='ff-l' x1='442.1' y1='74.8' x2='102.6' y2='414.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%23fff44f' stop-opacity='.8'/%3E%3Cstop offset='.3' stop-color='%23fff44f' stop-opacity='.6'/%3E%3Cstop offset='.5' stop-color='%23fff44f' stop-opacity='.2'/%3E%3Cstop offset='.6' stop-color='%23fff44f' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z' fill='url(%23ff-a)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-b)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-c)'/%3E%3Cpath d='m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z' fill='url(%23ff-d)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-e)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-f)'/%3E%3Cpath d='m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z' fill='url(%23ff-g)'/%3E%3Cpath d='M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z' fill='url(%23ff-h)'/%3E%3Cpath d='M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z' fill='url(%23ff-i)'/%3E%3Cpath d='M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z' fill='url(%23ff-j)'/%3E%3Cpath d='M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z' fill='url(%23ff-k)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z' fill='url(%23ff-l)'/%3E%3C/svg%3E)
- ![Safari: 15.2.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='195 190 135 135'%3E%3Cdefs%3E%3ClinearGradient id='s-a' x1='132.6' x2='134.4' y1='111.7' y2='-105.3' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23d2d2d2' /%3E%3Cstop offset='.5' stop-color='%23f2f2f2' /%3E%3Cstop offset='1' stop-color='%23fff' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-b' gradientUnits='userSpaceOnUse' /%3E%3ClinearGradient id='s-c' x1='65.4' x2='67.4' y1='115.7' y2='17.1' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23005ad5' /%3E%3Cstop offset='.2' stop-color='%230875f0' /%3E%3Cstop offset='.3' stop-color='%23218cee' /%3E%3Cstop offset='.6' stop-color='%2327a5f3' /%3E%3Cstop offset='.8' stop-color='%2325aaf2' /%3E%3Cstop offset='1' stop-color='%2321aaef' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-d' x1='158.7' x2='176.3' y1='96.7' y2='79.5' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23c72e24' /%3E%3Cstop offset='1' stop-color='%23fd3b2f' /%3E%3C/linearGradient%3E%3CradialGradient id='s-i' cx='-69.9' cy='69.3' r='54' gradientTransform='matrix(.9 -.01 .04 2.72 -9 -120)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%2324a5f3' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%231e8ceb' /%3E%3C/radialGradient%3E%3CradialGradient id='s-j' cx='109.3' cy='13.8' r='93.1' gradientTransform='matrix(-.02 1.1 -1.04 -.02 137 -115)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235488d6' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235d96eb' /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='220' height='220' x='22' y='-107' fill='url(%23s-a)' ry='49' transform='matrix(.57 0 0 .57 187 256)' /%3E%3Cg transform='translate(194 190)'%3E%3Ccircle cx='67.8' cy='67.7' fill='url(%23s-c)' paint-order='stroke fill markers' r='54' /%3E%3Ccircle cx='-69.9' cy='69.3' fill='url(%23s-i)' transform='translate(138 -2)' r='54' /%3E%3C/g%3E%3Cellipse cx='120' cy='14.2' fill='url(%23s-j)' rx='93.1' ry='93.7' transform='matrix(.58 0 0 .58 192 250)' /%3E%3Cg transform='matrix(.58 0 0 .57 197 182)'%3E%3Cpath fill='%23cac7c8' d='M46 192h1l72-48-7-9-66 57Z' /%3E%3Cpath fill='%23fbfffc' d='M46 191v1l66-57-7-9-59 65Z' /%3E%3Cpath fill='url(%23s-d)' d='m119 144-7-9 66-57-59 66Z' /%3E%3Cpath fill='%23fb645c' d='m105 126 7 9 66-57-1-1-72 49Z' /%3E%3C/g%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-miterlimit='1' stroke-width='1.3' d='m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8' transform='translate(-65 8)' /%3E%3C/svg%3E)

[Source](https://developer.mozilla.org/docs/Web/API/StorageManager/getDirectory)

<br />

## Motivation

When you think of files on your computer, you probably think about a file
hierarchy: files organized in folders that you can explore with your operating
system's file explorer. For example, on Windows, for a user called Tom, their To
Do list might live in `C:\Users\Tom\Documents\ToDo.txt`. In this example,
`ToDo.txt` is the filename, and `Users`, `Tom`, and `Documents` are folder
names. \`C:\` on Windows represents the root directory of the drive.

> [!NOTE]
> **Note:** In this document, I use the terms *folder* and *directory* interchangeably, disregarding the difference between the file system concept and the graphical interface representing it.

### Typical way of working with files on the web

To edit the To Do list in a web application, this is the usual flow:

1. The user *uploads* the file to a server or *opens* it on the client with [`<input type="file">`](https://developer.mozilla.org/docs/Web/HTML/Element/input/file).
2. The user makes their changes, and then *downloads* the resulting file with an injected [`<a download="ToDo.txt>`](https://developer.mozilla.org/docs/Web/API/HTMLAnchorElement/download) that you programmatically [`click()`](https://developer.mozilla.org/docs/Web/API/HTMLElement/click) with JavaScript.
3. For opening folders, you use a special attribute in [`<input type="file" webkitdirectory>`](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/webkitdirectory), which, despite its proprietary name, has practically universal browser support.

### Modern way of working with files on the web

This flow is not representative of how users think of editing files, and means
users end up with downloaded *copies* of their input files. Therefore, the File
System Access API introduced three picker methods:

- [`showOpenFilePicker()`](https://developer.mozilla.org/docs/Web/API/Window/showOpenFilePicker)
- [`showSaveFilePicker()`](https://developer.mozilla.org/docs/Web/API/Window/showSaveFilePicker)
- [`showDirectoryPicker()`](https://developer.mozilla.org/docs/Web/API/Window/showDirectoryPicker).

They enable a flow as follows:

1. Open `ToDo.txt` with `showOpenFilePicker()`, and get a [`FileSystemFileHandle`](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle) object.
2. From the `FileSystemFileHandle` object, get a [`File`](https://developer.mozilla.org/docs/Web/API/File) by calling the file handle's [`getFile()`](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/getFile) method.
3. Modify the file, then call [`requestPermission({mode: 'readwrite'})`](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/requestPermission) on the handle.
4. If the user accepts the permission request, save the changes back to the original file.
5. Alternatively, call `showSaveFilePicker()` and let the user pick a new file. (If the user picks a previously opened file, its contents will be overwritten.) For repeat saves, you can keep the file handle around, so you don't have to show the file save dialog again.

### Restrictions of working with files on the web

Files and folders that are accessible with these methods live in what can be
called the *user-visible* file system. Files saved from the web, and executable
files specifically, are marked with the
[mark of the web](https://textslashplain.com/2016/04/04/downloads-and-the-mark-of-the-web/),
so there's an additional warning the operating system can show before a
potentially dangerous file gets executed. As an additional security feature,
files obtained from the web are also protected by
[Safe Browsing](https://safebrowsing.google.com/), which, for the sake of
simplicity and in the context of this document, you can think of as a
cloud-based virus scan. When you write data to a file using the File System
Access API, writes are not in-place, but use a temporary file.
The file itself is not modified unless it passes all these security checks.

This work makes file operations relatively slow, despite improvements applied
where possible, for example, [on macOS](https://bugs.chromium.org/p/chromium/issues/detail?id=1413443).
Still every [`write()`](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/write)
call is self-contained, so under the hood it opens the file, seeks to the given
offset, and finally writes data.

### Files as the foundation of processing

At the same time, files are an excellent way to record data. For example,
[SQLite](https://www.sqlite.org/) stores entire databases in a single file.
Another example are [mipmaps](https://wikipedia.org/wiki/Mipmap) used in image
processing. Mipmaps are pre-calculated, optimized sequences of images, each of
which is a progressively lower resolution representation of the previous, which
makes many operations like zooming faster. So how can web applications get the
benefits of files, but without the performance costs of web-based file
processing? The answer is the *origin private file system*.

## The user-visible versus the origin private file system

Unlike the user-visible file system browsed using the operating system's file
explorer, with files and folders you can read, write, move, and rename, the
origin private file system is not meant to be seen by users. Files and folders
in the origin private file system, as the name suggests, are private, and more
concretely, private to the [origin](https://developer.mozilla.org/docs/Glossary/Origin)
of a site. Discover the origin of a page by typing
[`location.origin`](https://developer.mozilla.org/docs/Web/API/Location/origin)
in the DevTools Console. For example, the origin of the page
`https://developer.chrome.com/articles/` is `https://developer.chrome.com`.
Read more about the theory of origins in
[Understanding "same-site" and "same-origin"](https://web.dev/articles/same-site-same-origin#origin).

All pages that share the same origin can see the same origin private file system
data, so `https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/`
can see the same details as the previous example. Each origin has its own
independent origin private file system, which means the origin private file
system of `https://developer.chrome.com` is completely distinct from the one
of, say, [`https://web.dev`](). On Windows, the root directory of the
user-visible file system is `C:\\`.

The equivalent for the origin private file system is an initially empty root
directory per origin accessed by calling the asynchronous method
[`navigator.storage.getDirectory()`](https://developer.mozilla.org/docs/Web/API/StorageManager/getDirectory).

For a comparison of the user-visible file system and the origin private file
system, see the following diagram. The diagram shows that apart from the root
directory, everything else is conceptually the same, with a hierarchy of files
and folders to organize and arrange as needed for your data and storage needs.
![Diagram of the user-visible file system and the origin private file system with two exemplary file hierarchies.](https://web.dev/static/articles/origin-private-file-system/image/diagram-the-user-visible-7a2bde2e85a46.png) The entry point for the user-visible file system is a symbolic hard disk, the entry point for the origin private file system is calling of the method `navigator.storage.getDirectory`.

## Specifics of the origin private file system

Just like other storage mechanisms in the browser (for example,
[localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage)
or [IndexedDB](https://developer.mozilla.org/docs/Web/API/IndexedDB_API/Using_IndexedDB)),
the origin private file system is subject to browser quota restrictions. When a
user [clears all browsing data](https://support.google.com/chrome/answer/2392709)
or [all site data](https://developer.chrome.com/docs/devtools/storage/cache#deletecache),
the origin private file system will be deleted, too.

Call [`navigator.storage.estimate()`](https://developer.mozilla.org/docs/Web/API/StorageManager/estimate)
and in the resulting response object see the
[`usage`](https://developer.mozilla.org/docs/Web/API/StorageManager/estimate#usage)
entry to see how much storage your app already consumes, which is broken down by
storage mechanism in the [`usageDetails`](https://developer.mozilla.org/docs/Web/API/StorageManager/estimate#usagedetails)
object, where you want to look at the `fileSystem` entry specifically. Since the
origin private file system is not visible to the user, there are no permissions
prompts and no Safe Browsing checks.

## Getting access to the root directory

To get access to the root directory, run the following command. You end up with
an empty directory handle, more specifically, a
[`FileSystemDirectoryHandle`](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle).

    const opfsRoot = await navigator.storage.getDirectory();
    // A FileSystemDirectoryHandle whose type is "directory"
    // and whose name is "".
    console.log(opfsRoot);

## Main thread or Web Worker

There are two ways of using the origin private file system: on the
[main thread](https://developer.mozilla.org/docs/Glossary/Main_thread) or in a
[Web Worker](https://developer.mozilla.org/docs/Web/API/Worker). Web Workers
cannot block the main thread, which means in this context APIs can be
synchronous, a pattern generally disallowed on the main thread. Synchronous APIs
can be faster as they avoid having to deal with promises, and file operations
are typically synchronous in languages like C that can be compiled to WebAssembly.

    // This is synchronous C code.
    FILE *f;
    f = fopen("example.txt", "w+");
    fputs("Some text\n", f);
    fclose(f);

If you need the fastest possible file operations, or you deal with
[WebAssembly](https://developer.mozilla.org/es/docs/WebAssembly), skip down to
[Use the origin private file system in a Web Worker](https://web.dev/articles/origin-private-file-system#use_the_origin_private_file_system_in_a_web_worker).

## Use the origin private file system on the main thread

### Create new files and folders

Once you have a root folder, create files and folders using the
[`getFileHandle()`](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getFileHandle)
and the [`getDirectoryHandle()`](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getDirectoryHandle)
methods respectively. By passing
[`{create: true}`](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getFileHandle#create),
the file or folder will be created if it doesn't exist. Build up a hierarchy of
files by calling these functions using a newly created directory as the
starting point.

    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});

![The resulting file hierarchy from the earlier code sample.](https://web.dev/static/articles/origin-private-file-system/image/the-resulting-file-hierar-54931c1faae97.png)

### Access existing files and folders

If you know their name, access previously created files and folders by calling
the `getFileHandle()` or the `getDirectoryHandle()` methods, passing in the
name of the file or folder.

    const existingFileHandle = await opfsRoot.getFileHandle('my first file');
    const existingDirectoryHandle = await opfsRoot
        .getDirectoryHandle('my first folder');

### Getting the file associated with a file handle for reading

A `FileSystemFileHandle` represents a file on the file system. To obtain the
associated `File`, use the [`getFile()`](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/getFile)
method. A `File` object is a specific kind of
[`Blob`](https://developer.mozilla.org/es/docs/Web/API/Blob), and can be used in
any context that a `Blob` can.

In particular, [`FileReader`](https://developer.mozilla.org/es/docs/Web/API/FileReader),
[`URL.createObjectURL()`](https://developer.mozilla.org/es/docs/Web/API/URL/createObjectURL),
[`createImageBitmap()`](https://developer.mozilla.org/es/docs/Web/API/createImageBitmap),
and [`XMLHttpRequest.send()`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/send)
accept both `Blobs` and `Files`. Obtaining a `File` from a
`FileSystemFileHandle` "frees" the data, so you can access it and make it
available to the user-visible file system.

    const file = await fileHandle.getFile();
    console.log(await file.text());

### Write to a file by streaming

Stream data into a file by calling
[`createWritable()`](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/createWritable)
which creates a [`FileSystemWritableFileStream`](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream)
to that you then [`write()`](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/write)
the contents. At the end, you need to
[`close()`](https://developer.mozilla.org/docs/Web/API/WritableStream/close)
the stream.

    const contents = 'Some text';
    // Get a writable stream.
    const writable = await fileHandle.createWritable();
    // Write the contents of the file to the stream.
    await writable.write(contents);
    // Close the stream, which persists the contents.
    await writable.close();

### Delete files and folders

Delete files and folders by calling their file or directory handle's particular
[`remove()`](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/remove)
method. To delete a folder including all subfolders, pass the
[`{recursive: true}`](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/remove#recursive)
option.

    await fileHandle.remove();
    await directoryHandle.remove({recursive: true});

> [!NOTE]
> **Note:** The `remove()` method is implemented in Chrome. You can feature-detect support with `remove` in `FileSystemFileHandle.prototype`.

As an alternative, if you know the name of the to-be-deleted file or folder in a
directory, use the [`removeEntry()`](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/removeEntry)
method.

    directoryHandle.removeEntry('my first nested file');

> [!NOTE]
> **Note:** As a quick tip, `await (await navigator.storage.getDirectory()).remove({recursive: true})` is the fastest way to clear the entire origin private file system.

### Move and renaming files and folders

Rename and move files and folders using the
[`move()`](https://github.com/whatwg/fs/pull/10) method. Moving and renaming can
happen together or in isolation.

    // Rename a file.
    await fileHandle.move('my first renamed file');
    // Move a file to another directory.
    await fileHandle.move(nestedDirectoryHandle);
    // Move a file to another directory and rename it.
    await fileHandle
        .move(nestedDirectoryHandle, 'my first renamed and now nested file');

> [!NOTE]
> **Note:** Renaming and moving folders is not implemented yet in Chrome. You also can't move files from the origin private file system to the user-visible file system. You can [copy](https://web.dev/articles/origin-private-file-system#copy_a_file_from_the_origin_private_file_system_to_the_user-visible_file_system) them, though.

### Resolve the path of a file or folder

To learn where a given file or folder is located in relation to a reference
directory, use the [`resolve()`](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/resolve)
method, passing it a `FileSystemHandle` as the argument. To obtain the full path
of a file or folder in the origin private file system, use the root directory as
the reference directory obtained using `navigator.storage.getDirectory()`.

    const relativePath = await opfsRoot.resolve(nestedDirectoryHandle);
    // `relativePath` is `['my first folder', 'my first nested folder']`.

### Check if two file or folder handles point to the same file or folder

Sometimes you have two handles and don't know if they point at the same file or
folder. To check whether this is the case, use the
[`isSameEntry()`](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/isSameEntry)
method.

    fileHandle.isSameEntry(nestedFileHandle);
    // Returns `false`.

## List the contents of a folder

`FileSystemDirectoryHandle` is an
[asynchronous iterator](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols)
that you iterate over with a
[`for await... of`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/for-await...of)
loop. As an asynchronous iterator, it also supports the
[`entries()`](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/entries),
the [`values()`](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/values),
and the [`keys()`](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/keys)
methods, from which you can choose depending on what information you need:

    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()) {}

## Recursively list the contents of a folder and all subfolders

Dealing with asynchronous loops and functions paired with recursion is easy to
get wrong. The following function can serve as a starting point for listing the
contents of a folder and all its subfolders, including all files and their
sizes. You can simplify the function if you don't need the file sizes by, where
it says `directoryEntryPromises.push`, not pushing the `handle.getFile()`
promise, but the `handle` directly.

    const getDirectoryEntriesRecursive = async (
      directoryHandle,
      relativePath = '.',
    ) => {
      const fileHandles = [];
      const directoryHandles = [];
      const entries = {};
      // Get an iterator of the files and folders in the directory.
      const directoryIterator = directoryHandle.values();
      const directoryEntryPromises = [];
      for await (const handle of directoryIterator) {
        const nestedPath = `${relativePath}/${handle.name}`;
        if (handle.kind === 'file') {
          fileHandles.push({ handle, nestedPath });
          directoryEntryPromises.push(
            handle.getFile().then((file) => {
              return {
                name: handle.name,
                kind: handle.kind,
                size: file.size,
                type: file.type,
                lastModified: file.lastModified,
                relativePath: nestedPath,
                handle
              };
            }),
          );
        } else if (handle.kind === 'directory') {
          directoryHandles.push({ handle, nestedPath });
          directoryEntryPromises.push(
            (async () => {
              return {
                name: handle.name,
                kind: handle.kind,
                relativePath: nestedPath,
                entries:
                    await getDirectoryEntriesRecursive(handle, nestedPath),
                handle,
              };
            })(),
          );
        }
      }
      const directoryEntries = await Promise.all(directoryEntryPromises);
      directoryEntries.forEach((directoryEntry) => {
        entries[directoryEntry.name] = directoryEntry;
      });
      return entries;
    };

## Use the origin private file system in a Web Worker

As outlined before, Web Workers can't block the main thread, which is why in
this context synchronous methods are allowed.

### Getting a synchronous access handle

The entry point to the fastest possible file operations is a
[`FileSystemSyncAccessHandle`](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle),
obtained from a regular
[`FileSystemFileHandle`](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle)
by calling [`createSyncAccessHandle()`](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/createSyncAccessHandle).

    const fileHandle = await opfsRoot
        .getFileHandle('my highspeed file.txt', {create: true});
    const syncAccessHandle = await fileHandle.createSyncAccessHandle();

> [!NOTE]
> **Note:** It might seem confusing, but you actually get a *synchronous* `FileSystemSyncAccessHandle` from a regular `FileSystemFileHandle`. Also note that the `createSyncAccessHandle()` method is *asynchronous* , despite the `Sync` in its name.

### Synchronous in-place file methods

Once you have a synchronous access handle, you get access to fast in-place file
methods that are all synchronous.

- [`getSize()`](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/getSize): Returns the size of the file in bytes.
- [`write()`](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/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()`](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/read): Reads the contents of the file into a buffer, optionally at a given offset.
- [`truncate()`](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/truncate): Resizes the file to the given size.
- [`flush()`](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/flush): Ensures that the contents of the file contain all the modifications done through `write()`.
- [`close()`](https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/close): Closes the access handle.

Here is an example that uses each method.

    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();

    // Initialize this variable for the size of the file.
    let size;
    // The current size of the file, initially `0`.
    size = accessHandle.getSize();
    // Encode content to write to the file.
    const content = textEncoder.encode('Some text');
    // Write the content at the beginning of the file.
    accessHandle.write(content, {at: size});
    // Flush the changes.
    accessHandle.flush();
    // The current size of the file, now `9` (the length of "Some text").
    size = accessHandle.getSize();

    // Encode more content to write to the file.
    const moreContent = textEncoder.encode('More content');
    // Write the content at the end of the file.
    accessHandle.write(moreContent, {at: size});
    // Flush the changes.
    accessHandle.flush();
    // The current size of the file, now `21` (the length of
    // "Some textMore content").
    size = accessHandle.getSize();

    // Prepare a data view of the length of the file.
    const dataView = new DataView(new ArrayBuffer(size));

    // Read the entire file into the data view.
    accessHandle.read(dataView);
    // Logs `"Some textMore content"`.
    console.log(textDecoder.decode(dataView));

    // Read starting at offset 9 into the data view.
    accessHandle.read(dataView, {at: 9});
    // Logs `"More content"`.
    console.log(textDecoder.decode(dataView));

    // Truncate the file after 4 bytes.
    accessHandle.truncate(4);

> [!NOTE]
> **Note:** Note that the first parameter for `read()` and `write()` is an [`ArrayBuffer`](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) or an `ArrayBufferView` like a [`DataView`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/DataView). You cannot directly manipulate the contents of an `ArrayBuffer`. Instead, you create one of the [typed array objects](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) like an [`Int8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int8Array) or a `DataView` object which represents the buffer in a specific format, and use that to read and write the contents of the buffer.

## Copy a file from the origin private file system to the user-visible file system

As mentioned above, moving files from the origin private file system to the
user-visible file system isn't possible, but you can copy files. Since
`showSaveFilePicker()` is only exposed on the main thread, but not in the Worker
thread, be sure to run the code there.

    // On the main thread, not in the Worker. This assumes
    // `fileHandle` is the `FileSystemFileHandle` you obtained
    // the `FileSystemSyncAccessHandle` from in the Worker
    // thread. Be sure to close the file in the Worker thread first.
    const fileHandle = await opfsRoot.getFileHandle('fast');
    try {
      // Obtain a file handle to a new file in the user-visible file system
      // with the same name as the file in the origin private file system.
      const saveHandle = await showSaveFilePicker({
        suggestedName: fileHandle.name || ''
      });
      const writable = await saveHandle.createWritable();
      await writable.write(await fileHandle.getFile());
      await writable.close();
    } catch (err) {
      console.error(err.name, err.message);
    }

## Debug the origin private file system

Until built-in DevTools support is added (see [crbug/1284595](https://crbug.com/1284595)),
use the [OPFS Explorer](https://chrome.google.com/webstore/detail/opfs-explorer/acndjpgkpaclldomagafnognkcgjignd)
Chrome extension to debug the origin private file system. The screenshot from
the section [Creating new files and folders](https://web.dev/articles/origin-private-file-system#create_new_files_and_folders) is
taken straight from the extension by the way.

![The OPFS Explorer Chrome DevTools extension in the Chrome Web Store.](https://web.dev/static/articles/origin-private-file-system/image/the-opfs-explorer-chrome-676ec6bb62276.png)

After installing the extension, open the Chrome DevTools, select the
**OPFS Explorer** tab, and you're then ready to inspect the file hierarchy.
Save files from the origin private file system to the user-visible file system
by clicking the filename and delete files and folders by clicking the trash
can icon.

## Demo

See the origin private file system in action (if you install the OPFS Explorer
extension) in a [demo](https://googlechrome.github.io/samples/sqlite-wasm-opfs/)
that uses it as a backend for a SQLite database compiled to WebAssembly. Be sure
to check out the [source code on GitHub](https://github.com/GoogleChrome/samples/tree/gh-pages/sqlite-wasm-opfs).
Note how the embedded version does not use the origin private file system
backend (because the iframe is cross-origin), but when you open the demo in a
separate tab, it does.
<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi" loading="lazy" src="https://googlechrome.github.io/samples/sqlite-wasm-opfs/" style="height: 100%; width: 100%; border: 0;"></iframe>

## Conclusion

The origin private file system, as specified by the WHATWG, has shaped the way
we use and interact with files on the web. It has enabled new use cases that
were impossible to achieve with the user-visible file system. All major browser
vendors---Apple, Mozilla, and Google---are on-board and share a joint vision. The
development of the origin private file system is very much a collaborative
effort, and feedback from developers and users is essential to its progress.

As we continue to refine and improve the standard, feedback on the
[whatwg/fs repository](https://github.com/whatwg/fs) in the form of Issues or
Pull Requests is welcome.

### Related links

- [File System Standard spec](https://fs.spec.whatwg.org/)
- [File System Standard repo](https://github.com/whatwg/fs)
- [The File System API with Origin Private File System WebKit post](https://webkit.org/blog/12257/the-file-system-access-api-with-origin-private-file-system/)
- [OPFS Explorer extension](https://chrome.google.com/webstore/detail/opfs-explorer/acndjpgkpaclldomagafnognkcgjignd)

### Acknowledgements

This document was reviewed by [Austin Sully](https://github.com/a-sully),
[Etienne Noël](https://ca.linkedin.com/in/enoel19), and
[Rachel Andrew](https://rachelandrew.co.uk/).