Skip to content

Files, Assets, HTML

<input type="file"> is a page-initiated picker, not loadFile. Android uses the built-in Photo Picker / content chooser / camera path. iOS uses WKWebView’s system picker. macOS uses NSOpenPanel. Windows, Linux, and Web use the engine or browser dialog. See Permissions for host-app manifest and Info.plist keys.

Manual checks after a page load:

  • <input type="file" accept="image/*" multiple> opens the Photo Picker or system picker, allows more than one image, and canceling lets you tap the control again.
  • <input type="file"> without accept can pick an arbitrary file such as a PDF.
  • <input type="file" accept="image/*" capture> asks for camera permission and then opens the camera. Denying permission or backing out of the camera must not freeze the input.
await controller.loadFile('/Users/me/Documents/page.html');

Platform behavior:

Platform Behavior
Android Uses file URL loading. AndroidLoadFileParams can add headers.
iOS/macOS Uses WebKit local file loading. WebKitLoadFileParams controls read access scope.
Windows Maps the file’s directory to a virtual HTTPS host.
Linux Loads the absolute file through WebKitGTK.
Enables file access and loads the file URL.
Unsupported; browsers do not allow arbitrary file reads from a hosted app.
await controller.platform.loadFileWithParams(
AndroidLoadFileParams(
absoluteFilePath: '/sdcard/Download/page.html',
headers: const <String, String>{'X-Source': 'app'},
),
);
await controller.platform.loadFileWithParams(
WebKitLoadFileParams(
absoluteFilePath: '/Users/me/site/index.html',
readAccessPath: '/Users/me/site',
),
);

readAccessPath must include any images, scripts, and styles referenced by the HTML file.

Declare the asset:

flutter:
assets:
- assets/help/index.html

Load it:

await controller.loadFlutterAsset('assets/help/index.html');

On Windows, each controller maps the canonical asset directory to a private randomized HTTPS host with cross-origin access denied. Mappings are replaced or cleared as navigation changes. Linux requires absolute file paths and canonicalizes them before loading. Both implementations reject traversal and symlink escapes outside the Flutter asset bundle. On web, assets resolve under the app’s generated assets/ path.

await controller.loadHtmlString(
'<html><body><a href="details.html">Details</a></body></html>',
baseUrl: 'https://docs.example.com/help/',
);

Use baseUrl when the document contains relative URLs.

await controller.loadRequest(
Uri.parse('https://api.example.com/form'),
method: LoadRequestMethod.post,
headers: const <String, String>{'Content-Type': 'application/json'},
body: Uint8List.fromList(utf8.encode('{"ok":true}')),
);

Check platform support before relying on custom requests:

Platform Notes
Android POST with custom headers is not supported by Android WebView postUrl.
Web Non-simple requests use fetch, require CORS approval, preserve binary response bytes, and use the final redirect URL as the logical URL.
Other native platforms Support method, headers, and body through native request APIs.

Recommended Fallback for Unsupported POST Headers

Section titled “Recommended Fallback for Unsupported POST Headers”

When a platform cannot submit a POST with custom headers directly, send the request with your app’s HTTP client and load the resulting HTML:

final response = await http.post(
Uri.parse('https://api.example.com/form'),
headers: const <String, String>{'Authorization': 'Bearer token'},
body: '{"ok":true}',
);
await controller.loadHtmlString(
response.body,
baseUrl: 'https://api.example.com/form',
);

This fallback is appropriate for HTML responses. It is not equivalent to browser navigation for cookies, redirects, service workers, or streaming responses.