Custom protocols handle URL schemes such as app:// without starting a local HTTP server. Register each scheme before creating a webview.
import { readFile } from 'node:fs/promises';
import { extname, join } from 'node:path';
import { Application } from '@webviewjs/webview';
const MIME = {
'.html': 'text/html; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.css': 'text/css',
};
const app = new Application();
const win = app.createBrowserWindow();
win.registerProtocol('app', async (request) => {
const url = new URL(request.url);
const path = join(process.cwd(), 'dist', url.pathname);
try {
return new Response(await readFile(path), {
headers: { 'Content-Type': MIME[extname(path)] ?? 'application/octet-stream' },
});
} catch {
return new Response(`Not found: ${url.pathname}`, {
status: 404,
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
});
win.createWebview({ url: 'app://localhost/index.html' });
app.run();
The handler receives the standard global Fetch API Request. It may return a
Response, a Promise of a Response, or the legacy plain object shown below.
This makes Fetch-compatible routers such as Hono usable directly. Rejected
handlers and thrown errors become a 500 text/plain response.
Forward the request directly to a Hono application. Hono returns a standard
Fetch API Response, so no adapter or HTTP server is required:
import { Hono } from 'hono';
const router = new Hono();
router.get('/*', (context) => {
return context.html(`<h1>Current page: ${context.req.path}</h1>`);
});
win.registerProtocol('app', (request) => router.fetch(request));
win.createWebview({ url: 'app://localhost/' });
The runnable Hono custom protocol example includes dynamic pages, navigation links, pathname rendering, and application shutdown handling.
interface CustomProtocolResponse {
body: Buffer;
statusCode?: number; // default: 200
mimeType?: string; // default: application/octet-stream
headers?: HeaderData[];
}
Never resolve a request path without checking it remains inside the intended asset directory. Normalize and validate the path before passing it to the file system. The runnable custom protocol example includes this check.
Register multiple schemes before createWebview():
win.registerProtocol('app', appHandler);
win.registerProtocol('api', async (request) => {
const response = await fetch(`https://example.test${new URL(request.url).pathname}`);
return response;
});
Protocol registrations are fixed when the webview is created. Registering a scheme after createWebview() does not affect an existing webview.
Use response headers when the page needs them:
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'no-store',
},
});