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.
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.
HTML to App is for personal tools and local testing. The builder and the launcher it packages have separate compatibility requirements.
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 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.
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.
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.
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.
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.
The main window brings the project list, source editor, and Checks & Output together. Your project settings are kept for later previews and exports.
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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.
HTMLtoAppApproval shown in the installation dialog.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.
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.
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.
Viewer or Editor.jpg, png, or mp4.Info.plist.Notification API to show native macOS notifications from local HTML.window.HTMLtoApp.setBadge(value) to set a Dock tile badge string.window.HTMLtoApp.clearBadge() when there is no active count or status to show.window.HTMLtoApp.window.HTMLtoApp.menuBar.setIcon().window.HTMLtoApp.menuBar.setMenu(() => items).submenu arrays for recursive menus and checked plus toggle for stateful items.closeToMenuBarOnWindowClose so closing the window keeps the app alive in the menu bar.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.
<meta
name="htmltoapp:open-with"
content="role=editor; files=jpg,jpeg,png; folders=true; permissions=camera,microphone"
> role=viewer or role=editorfiles=jpg,jpeg,png for file extensionsfolders=true or folders=falsepermissions=camera,microphone,location for generated-app entitlementscamera=true, microphone=true, and location=true are also acceptedmic, audio, geolocation, and gps are accepted aliasesrole=viewer; files=jpg,jpeg,png,webp; folders=truerole=viewer; files=mp4,mov,m4v; folders=truerole=viewer; folders=truerole=editor; files=jpg,jpeg,png,webppermissions=cameraGenerated 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.
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.
Read the initial selection from window.HTMLtoApp.launchItems.
const launchItems = (window.HTMLtoApp && window.HTMLtoApp.launchItems) || [];
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);
});
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);
}); 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.
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;
} 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();
}
} body option become the main native notification content.clearBadge() or set an empty value.Files and folders share a common shape, with a few extra fields depending on item type.
id: opened-item identifier on root launch itemsitemId: same identifier, also included on folder listing entriesname: display name of the file or folderpath: original macOS path as a stringrelativePath: path inside the opened folder, or an empty string for the root itemisDirectory: true for folders, false for fileskind: file or directory on root launch itemsurl: loadable URL for a file item, usable in img, video, audio, or fetch()baseURL: base URL for a folder itemlistingURL: JSON endpoint for the folder contents
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 : [];
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.
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); 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
}); 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.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.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;
} 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 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.Viewer for galleries, players, and browsers.Editor when the app should overwrite the opened file or manage files inside the opened folder.fetch().window.HTMLtoApp.fs.list() or the provided listing URL.Start from working demos instead of building the bridge integration from scratch.
Image viewer, geometry pad, video player, audio player, folder browser, and text editor demos built to work with the generated app bridge, plus a notifications and Dock badge demo.
Ask project-fit questions, check bridge setup, and get current guidance for packaging local HTML projects as macOS apps.
If your project needs a more specific setup, contact support with your expected file types and how the page should react when content is opened.