Documentation

Build your project. Connect it to macOS.

Use the project workspace to create or add a folder, edit its source, preview the result, and export an app for your own Mac. The JavaScript bridge adds Finder file handling, notifications, badges, menu bar controls, and other native features when your project needs them.

Requirements for the builder and exported apps

HTML to App is for personal tools and local testing. The builder and the launcher it packages have separate compatibility requirements.

The HTML to App builder

The current builder requires macOS 12 or newer. Export includes an ad hoc signing step that also requires macOS 12 or newer. On macOS 11 or earlier, that step fails before the exported app is saved to your chosen destination.

The exported launcher

The current bundled launcher also targets macOS 12 or newer. It displays your HTML using the system WebKit engine, so test your project's web features and native integrations on the macOS version you use. Export does not add support for older macOS versions.

Create or add a project folder

Open the + menu beside Projects in the main window. Choose the starting point that matches your work, then select the project in the sidebar to see its source files.

New Project

Choose a location for a new folder. HTML to App creates an editable notes app with index.html, style.css, app.js, and a README so you can start changing a working project immediately.

Add Project Folder

Choose an existing website folder with index.html at its root. The workspace uses that folder in place. If you use a build tool, select its generated website folder with the HTML, CSS, JavaScript, and assets needed to run the site.

Example Gallery

Browse the bundled drawing tools, text editor, media players, and native integration examples. Choose Create Project to save your own editable copy in a new folder. The gallery includes instructions for trying each example.

Edit, run, and check your work

The main window brings the project list, source editor, and Checks & Output together. Your project settings are kept for later previews and exports.

Edit the original source files

Select a file in the Files list to edit supported UTF-8 text, including HTML, CSS, JavaScript, JSON, and Markdown. Save or Command-S writes to the original project folder. Use Command-F to find text. Use Project's Reload Files command after external changes.

Run a preview

Choose Run to open the project in a preview window. Save your edits and run it again to see the changes. Checks & Output collects JavaScript errors and console messages while you try the project.

Check local resource paths

Check Project inspects the saved entry page and static HTML and CSS references. It reports missing resources, paths outside the project folder, and external URLs. JavaScript-generated paths and application behavior still need testing in the preview and exported app.

Keep control of your folders

Duplicate Project makes a separate copy for a new version or experiment. Remove from List only removes the workspace entry. If a folder becomes unavailable, use Reconnect Folder to restore access. Show in Finder opens the source location.

Test native permissions after export

The workspace preview is useful for layout, navigation, local content, and supported bridge features. Camera, microphone, location, notifications, and Dock badges must be tested in the exported app. Preview storage and exported app storage are separate.

Export an app for your own Mac

Choose Export App for the selected project. To package an existing HTML file or folder without adding a project, use Convert HTML to App at the top of the main window.

Choose the content mode

HTML Source and Packaging lets you set the window title and choose how content is loaded. Embed it to include the website inside the app, or use its original location to keep loading from the source folder. That folder must remain available for a reference app to work.

Set the identity and save the app

Choose the app name, icon, optional Dock label and effects, Finder file handling, and permissions. On Ready to Export, choose Save HTML App and a destination for the .app bundle. Then follow the first-launch guidance. Return to the project whenever you want to make another change.

What is in an export

Export copies the bundled, precompiled launcher, configures its name, icon, resources, and settings, and embeds your HTML content or references its original location. It does not compile new native code. The resulting bundle receives an ad hoc signature for personal use and local testing, not a Developer ID distribution signature.

Exports are not prepared for distribution. HTML to App does not notarize them, submit them to App Review, or replace the Apple Developer Program. Any public release is a separate development and distribution process subject to Apple's applicable requirements. See Apple's Developer ID guidance.

Open your export on your Mac

Saving an export and allowing it to launch are separate steps. Only proceed with your own export when you trust the HTML, scripts, and other content you put into it.

First launch and manual handling

  1. Choose Open App on the export completion screen, or use Show in Finder to locate the saved app.
  2. If macOS blocks an unidentified or unnotarized app, use Open Privacy & Security. After trying to open your trusted export, choose Open Anyway if macOS offers it, then confirm Open.
  3. On macOS 12, the corresponding controls are in System Preferences under Security & Privacy. Later versions use System Settings and Privacy & Security.

If the completion screen offers Copy Commands and Open Terminal, review the generated commands for that specific export before choosing to run them. They are a manual local-launch option, not a distribution step.

If macOS reports damage or malicious software, stop and investigate instead of approving it. Consult Apple's guidance on safely opening apps for the warning you see.

Optional automation for later exports

  1. Choose Install Script on the export completion screen. Use View Script to review what it does before installing it.
  2. Keep the exact Application Scripts destination and the name HTMLtoAppApproval shown in the installation dialog.
  3. Installation handles the current export. Once the script is installed and up to date, HTML to App runs it automatically after subsequent exports.
  4. If Update Script is offered, review and install the current version to resume automation. If approval fails or is cancelled, the exported app remains saved. Use Retry Approval or the manual instructions.
  5. To disable automation, use Scripts Folder and remove HTMLtoAppApproval. Manual handling remains available.

The user-installed script runs outside the App Sandbox and removes the com.apple.quarantine attribute from the specific exported bundle. It does not change its signature or turn off Gatekeeper across your Mac.

Local approval is not Apple approval

The word "approval" in this workflow refers only to local launch handling on your Mac. The export already has an ad hoc signature. Neither manual launch handling nor the optional script supplies a Developer ID identity, notarization, an Apple malware review, or App Store approval. They do not grant camera, microphone, location, or other privacy permissions. They are not substitutes for Apple's distribution requirements.

HTML to App itself is coming soon to the Mac App Store and has not been approved yet. That planned listing concerns the builder, not the apps you export.

Connect your HTML to native Mac features

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.

Notifications and Dock badge

  • Use the standard Notification API to show native macOS notifications from local HTML.
  • Use window.HTMLtoApp.setBadge(value) to set a Dock tile badge string.
  • Use window.HTMLtoApp.clearBadge() when there is no active count or status to show.
  • The bridge namespace is exactly window.HTMLtoApp.

Menu bar apps

  • Create a menu bar icon from JavaScript with window.HTMLtoApp.menuBar.setIcon().
  • Regenerate the menu on every popup with window.HTMLtoApp.menuBar.setMenu(() => items).
  • Use nested submenu arrays for recursive menus and checked plus toggle for stateful items.
  • Enable closeToMenuBarOnWindowClose so closing the window keeps the app alive in the menu bar.

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, a scoped file bridge for document-style workflows, and native app-state controls for Dock badges, menu bar icons, and window behavior.

Bridge namespaces

  • window.HTMLtoApp: Finder launch items, runtime information, scoped file operations, Dock badge control, menu bar controls, and window controls.
  • Notification: standard browser-style notification constructor replaced by the generated app so local HTML can request native macOS notifications.

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

Notifications and Dock Badge

Use these APIs for local app status such as completed tasks, reminders, unread counts, queue lengths, or attention states. This is native notification passthrough from the generated Mac app; it does not configure APNs or server-delivered remote push notifications.

Send a macOS notification

Check for the API, request permission, then create a notification. On supported macOS releases, HTML to App forwards the request to the generated app's native notification handler.

async function sendAppNotification(title, body) {
  if (!("Notification" in window)) return false;

  const permission = await Notification.requestPermission();
  if (permission !== "granted") return false;

  new Notification(title, {
    body,
    tag: "html-to-app-status"
  });
  return true;
}

Set or clear the Dock badge

Badge values are strings. Numbers work well for counts, while short labels can represent states like paused, new, or sync pending. Request notification permission before enabling Dock badge controls, because macOS ties Dock badge visibility to notification authorization for the generated app.

const appBridge = window.HTMLtoApp;

async function setUnreadCount(count) {
  if (!appBridge || typeof appBridge.setBadge !== "function") return;
  if (!("Notification" in window)) return;

  const permission = await Notification.requestPermission();
  if (permission !== "granted") return;

  if (count > 0) {
    appBridge.setBadge(String(count));
  } else {
    appBridge.clearBadge();
  }
}

Behavior notes

  • Notifications are available through the generated app bridge on macOS versions that support native user notifications.
  • The exported app asks macOS for notification authorization when needed; if the user denies it, the native notification is not delivered.
  • Dock badge controls should use the same permission gate as native notifications, so users are not offered badge actions that macOS will keep hidden.
  • The notification title and body option become the main native notification content.
  • The Dock badge belongs to the running generated app and is cleared when you call clearBadge() or set an empty value.
  • Guard both APIs when the same HTML is also opened in a normal browser.

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.