Documentation

Handle opened files, folders, and permissions in your HTML app.

HTML to App can register your generated macOS app as a Finder handler for selected file extensions or folders, preselect app permissions from HTML metadata, and pass opened items into your page JavaScript with scoped read and write access where appropriate.

What the feature does

Use this when you want the generated app to behave like an image viewer, video player, audio player, folder browser, or another file-driven tool.

Open With registration

  • Choose whether the app should open files, folders, or both.
  • Set the role to Viewer or Editor.
  • Enter file extensions such as jpg, png, or mp4.
  • Export the app bundle with those document types included in the generated Info.plist.

Runtime delivery

  • When the user opens a matching file from Finder, macOS launches your generated app.
  • The launcher receives the opened file or folder URLs on the native side.
  • HTML to App forwards those items into the page after the web content is ready.
  • Editor-role apps can also write back inside the opened file or folder scope.
  • The same page can also receive later open events while the app is already running.

App permissions

  • Advanced Settings can enable generated-app entitlements for camera, microphone, or location services.
  • The same permissions can be preselected automatically from HTML metadata when the source is chosen.
  • This is useful for camera viewers, audio capture tools, voice utilities, mapping tools, or mixed-media apps.

Auto-detect Open With and permissions

If your main HTML file already knows what kind of app it is, you can declare that in the document head and HTML to App will prefill the Open With controls and Advanced Settings permission checkboxes when the source is selected.

Recommended meta tag

<meta
  name="htmltoapp:open-with"
  content="role=editor; files=jpg,jpeg,png; folders=true; permissions=camera,microphone"
>

Supported values

  • role=viewer or role=editor
  • files=jpg,jpeg,png for file extensions
  • folders=true or folders=false
  • permissions=camera,microphone,location for generated-app entitlements
  • camera=true, microphone=true, and location=true are also accepted
  • mic, audio, geolocation, and gps are accepted aliases
  • The same metadata can also be written as an HTML comment if you prefer.

Example app types

  • Image viewer: role=viewer; files=jpg,jpeg,png,webp; folders=true
  • Video player: role=viewer; files=mp4,mov,m4v; folders=true
  • Folder browser: role=viewer; folders=true
  • Drawing app: role=editor; files=jpg,jpeg,png,webp
  • Camera viewer: permissions=camera

JavaScript API

Generated apps expose launch items, runtime capability info, and a scoped file bridge for document-style workflows.

Initial launch items

Read the initial selection from window.HTMLToApp.launchItems.

const launchItems = (window.HTMLToApp && window.HTMLToApp.launchItems) || [];

Later open events

Listen for htmltoapp-open when the user opens more files or folders while the app is already running.

window.addEventListener("htmltoapp-open", (event) => {
  const detail = event.detail || {};
  const items = detail.items || [];
  const replaceExisting = !!detail.replaceExisting;
  handleLaunchItems(items, replaceExisting);
});

Runtime capabilities

Check whether Open With is enabled, whether the build is a viewer or editor, and whether write-back is available through window.HTMLToApp.runtime.

const runtime = (window.HTMLToApp && window.HTMLToApp.runtime) || {
  enabled: false,
  role: "viewer",
  allowFiles: false,
  allowFolders: false,
  canWriteBack: false
};

window.addEventListener("htmltoapp-runtime", (event) => {
  console.log("runtime", event.detail);
});

Launch item shape

Files and folders share a common shape, with a few extra fields depending on item type.

Common fields

  • id: opened-item identifier on root launch items
  • itemId: same identifier, also included on folder listing entries
  • name: display name of the file or folder
  • path: original macOS path as a string
  • relativePath: path inside the opened folder, or an empty string for the root item
  • isDirectory: true for folders, false for files
  • kind: file or directory on root launch items

File and folder extras

  • url: loadable URL for a file item, usable in img, video, audio, or fetch()
  • baseURL: base URL for a folder item
  • listingURL: JSON endpoint for the folder contents

Example folder listing

Folder listing entries use the same basic shape and can be passed back into window.HTMLToApp.fs through entry.itemId plus entry.relativePath.

const folder = launchItems.find((item) => item.isDirectory);
const listing = await window.HTMLToApp.fs.list(folder.id);
const entries = Array.isArray(listing.entries) ? listing.entries : [];

Scoped file bridge

The generated app can read opened content in both viewer and editor mode. Mutating methods are only available when Open With is enabled and the role is Editor.

Read and overwrite the opened file

If the launched item is a file, omit relativePath and write directly back to that same file.

const fileItem = launchItems.find((item) => !item.isDirectory);
const opened = await window.HTMLToApp.fs.readText(fileItem.id);
editor.value = opened.text;

await window.HTMLToApp.fs.writeText(fileItem.id, editor.value);

Create, replace, move, and remove inside an opened folder

If the launched item is a folder, target descendants by relative path. The bridge never grants access outside that opened folder tree.

const folder = launchItems.find((item) => item.isDirectory);

await window.HTMLToApp.fs.createDirectory(folder.id, "drafts");
await window.HTMLToApp.fs.writeText(folder.id, "# Notes\n", "drafts/today.md");
await window.HTMLToApp.fs.move(folder.id, "drafts/today.md", "archive/today.md", {
  createIntermediates: true,
  overwrite: true
});

Available methods

  • stat(itemOrId, relativePath?) returns metadata for a file or folder.
  • list(itemOrId, relativePath?) returns folder metadata plus entries.
  • readText(itemOrId, relativePath?) returns decoded text plus detected encoding.
  • readData(itemOrId, relativePath?) returns Base64 data plus MIME type.
  • writeText(itemOrId, text, relativePath?, options?) creates or replaces a text file.
  • writeData(itemOrId, base64Data, relativePath?, options?) creates or replaces any file.
  • createDirectory(itemOrId, relativePath, options?) creates a folder inside an opened folder item.
  • remove(itemOrId, relativePath?, options?) removes a file or folder. Use recursive: true for non-empty folders.
  • move(itemOrId, fromRelativePath, toRelativePath, options?) renames or moves items inside an opened folder.

Write options

  • writeText supports encoding, atomic, and createIntermediates.
  • writeData supports atomic and createIntermediates.
  • createDirectory supports createIntermediates.
  • remove supports recursive for non-empty folders.
  • move supports createIntermediates and overwrite.

Recommended handling pattern

Use one shared function for startup items, later open events, and any drag-and-drop path in your page.

function extensionOf(name) {
  const dot = name.lastIndexOf(".");
  return dot === -1 ? "" : name.slice(dot + 1).toLowerCase();
}

function isSupportedFile(item) {
  return !item.isDirectory && SUPPORTED_EXTENSIONS.has(extensionOf(item.name || item.path || ""));
}

async function expandLaunchItems(items) {
  const results = [];

  for (const item of items) {
    if (!item) continue;

    if (isSupportedFile(item)) {
      results.push(item);
      continue;
    }

    if (item.isDirectory && window.HTMLToApp && window.HTMLToApp.fs) {
      const listing = await window.HTMLToApp.fs.list(item.id);
      const entries = Array.isArray(listing.entries) ? listing.entries : [];
      results.push(...entries.filter(isSupportedFile));
    }
  }

  return results;
}

Important behavior notes

The Open With role and file registration are scoped on purpose. They can support real editor workflows without turning the app into an unrestricted filesystem client.

Viewer vs editor

  • Viewer and Editor control how the app is registered with Finder and Launch Services.
  • Viewer keeps the bridge read-only.
  • Editor enables window.HTMLToApp.fs write methods for the opened item scope only.
  • Use Viewer for galleries, players, and browsers.
  • Use Editor when the app should overwrite the opened file or manage files inside the opened folder.

Source HTML vs opened content

  • HTML to App still exposes loadable item URLs for media tags, image previews, and fetch().
  • Folder items can be expanded through window.HTMLToApp.fs.list() or the provided listing URL.
  • When you export in external-source mode, the selected HTML source folder remains read-only.
  • Opened document files and folders are a separate scope, and editor-role apps can modify only that specific opened file or folder tree.

Example projects

Start from working demos instead of building the bridge integration from scratch.