How Zakirullin Files Web App Uses OPFS for Offline File Handling

The Zakirullin Files application stores user files in the browser's Origin-Private File System (OPFS) to ensure data persists across page reloads and remains accessible without network connectivity, falling back to an in-memory file system when OPFS methods are unavailable.

The zakirullin/files.md repository implements a markdown editor that leverages the Origin-Private File System (OPFS) to provide a robust offline experience. By storing documents directly in the browser's private file sandbox and pairing this with strategic IndexedDB handle persistence, the application maintains full functionality during network outages while ensuring user data survives browser sessions.

Detecting OPFS Capability and Fallback Mechanisms

Before executing any file operations, the application verifies that the current browser fully supports the required OPFS API surface. This detection occurs in web/welcome.js through the opfsIsFullyUsable() function.

Browser Capability Checks

The function checks for the existence of both FileSystemFileHandle.createWritable and FileSystemFileHandle.remove methods. If either method is missing—common in Safari, older Chromium versions, or non-secure HTTP contexts—the application sets an internal flag and instantiates MemFile and MemDir classes to create a volatile in-memory filesystem.

if (!opfsIsFullyUsable()) {
  console.warn('OPFS missing methods – using in‑memory FS');
  isMemFS = true;
  root = await getMemFSRoot();          // in‑memory fallback
} else {
  root = await navigator.storage.getDirectory(); // real OPFS root
}

Graceful Degradation Strategy

Every OPFS-dependent function guards against missing API methods. When navigator.storage.getDirectory() throws an error or returns undefined, the code automatically delegates to getMemFSRoot(), ensuring the UI remains responsive even on unsupported browsers.

Initializing the Origin-Private File System

On first load, when no user-selected folder exists, the application creates a temporary OPFS workspace seeded with default content.

Temporary Storage Setup

The getTemporaryStorageDirHandle() function in web/welcome.js obtains the OPFS root handle via navigator.storage.getDirectory() and populates it with markdown files defined in the WELCOME_FILES constant. This provides users with immediate, editable content while demonstrating the app's capabilities.

Error Handling and HTTPS Requirements

If the browser lacks HTTPS or restricts storage access, the function catches these permission errors and triggers the in-memory fallback. This prevents initialization failures from crashing the application on incompatible environments.

Persisting Directory Handles with IndexedDB

While OPFS provides the storage backend, IndexedDB handles the persistence of directory handles across browser sessions, eliminating the need for repeated permission prompts.

Saving User Folder Selections

When a user opens a local folder through the file picker, the resulting FileSystemDirectoryHandle is serialized and stored in IndexedDB using saveDirectoryHandle() in web/app.js. This allows the application to maintain access to the same folder after page reloads.

Retrieval Logic in getRootDirHandle

The getRootDirHandle() function orchestrates handle retrieval by first attempting to fetch the saved handle via getSavedRootDirHandle(). If the handle is unavailable or OPFS detection fails, the function delegates to getTemporaryStorageDirHandle() to create a fresh temporary workspace.

async function getRootDirHandle() {
  // Try to get saved handle from IndexedDB first
  const saved = await getSavedRootDirHandle();
  if (saved && await verifyPermission(saved)) {
    return saved;
  }
  // Fall back to temporary OPFS storage
  return getTemporaryStorageDirHandle();
}

Performing File Operations on OPFS

All high-level file system operations reside in web/lib/fs.js, providing a unified API that abstracts whether the backend is OPFS or in-memory.

Core File System APIs

Functions like read(), write(), createDir(), and remove() obtain a directory handle through getRootDirHandle() and then use native OPFS methods including getFileHandle(), createWritable(), and removeEntry(). The createDir() function, for instance, creates a directory on OPFS while simultaneously mirroring the change in the in-memory tree used for UI rendering.

async function savePage(path, content) {
  // `write` from lib/fs.js resolves the appropriate handle
  await write(path, content);   // writes via OPFS or memFS transparently
}

Atomic Write Operations

The write() and writeAtEnd() functions utilize FileSystemWritableFileStream to perform atomic file updates. This prevents data corruption during write operations and ensures consistency between the OPFS storage and the application's internal state.

await createDir('/projects/notes');   // OPFS directory + in‑memory entry
await renderSidebar();                // UI reflects new folder

Caching Static Assets with Service Workers

While OPFS handles user-generated content, static application assets are managed separately by a Service Worker in web/offline.js.

Pre-caching Application Shell

During the install event, the Service Worker caches the HTML, CSS, JavaScript, and font files required to render the interface. This ensures the application shell loads instantly from cache regardless of network status.

self.addEventListener('install', e => {
  e.waitUntil(caches.open('files-md-v1')
    .then(cache => cache.addAll(['/','/app.js','/lib/hypermd.js'])));
});

Separation of Concerns

This architecture isolates user data (stored in OPFS) from application code (cached by the Service Worker). The OPFS provides writable, persistent storage for markdown files, while the Cache API delivers the static resources necessary to boot the application in offline scenarios.

Summary

  • Capability Detection: web/welcome.js checks for createWritable and remove methods via opfsIsFullyUsable() before utilizing OPFS features.
  • Temporary Storage: getTemporaryStorageDirHandle() initializes OPFS with welcome files when no user folder is selected.
  • Handle Persistence: IndexedDB stores directory handles via functions in web/app.js to maintain access across sessions without repeated permission prompts.
  • File Operations: web/lib/fs.js provides a unified API that writes to OPFS while maintaining an in-memory mirror for UI responsiveness.
  • Graceful Fallback: Unsupported browsers automatically use MemFile and MemDir classes instead of native OPFS.
  • Asset Caching: The Service Worker in web/offline.js caches static resources independently of user file storage.

Frequently Asked Questions

What browsers support the OPFS implementation in Zakirullin Files?

The application requires browsers implementing both FileSystemFileHandle.createWritable and FileSystemFileHandle.remove methods. This includes modern Chromium-based browsers and recent Firefox versions supporting the File System Access API. Safari and legacy browsers automatically fall back to the in-memory file system implemented in web/welcome.js.

How does the app prevent data loss when OPFS is unavailable?

When opfsIsFullyUsable() returns false, the application instantiates MemFile and MemDir classes to create a volatile in-memory filesystem. While this data disappears on page reload, it ensures the application remains functional and allows users to export their work via download prompts before closing the browser.

Why does the app use IndexedDB alongside OPFS?

IndexedDB stores FileSystemDirectoryHandle objects via saveDirectoryHandle() because OPFS handles themselves are not automatically persisted across browser sessions. This storage mechanism enables the app to reopen previously selected folders in web/app.js without requiring users to re-grant permission through file picker dialogs.

What is the difference between OPFS storage and the Service Worker cache?

OPFS stores user-generated markdown files and media content in a private, origin-specific directory using navigator.storage.getDirectory(), while the Service Worker in web/offline.js caches the application's static assets using the Cache API. OPFS provides writable, persistent file storage that survives browser restarts, whereas the Service Worker cache is read-only and stores only the application shell required for offline booting.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →