Overview
Mobile apps host File Storage inside a WebView. You can either load our hosted shell (cdn.filestorage.io/widget.html) with query parameters, or load the CDN JS/CSS yourself and call FileStorageSDK.mountWidget(). Storage credentials never go in the app binary - they stay on your proxy or Managed backend, same as the web.
Widget modes
widget | UI | Typical use |
|---|---|---|
full | Complete file manager (sidebar + browse) | In-app files screen |
picker | Browse + select (read-oriented) | Attach / insert a file |
upload | Upload-focused chrome | Import from camera roll / documents |
media | Photos-first media library | Gallery / brand assets |
Hosted widget URL
Fastest path for native apps - no local HTML bundle required:
https://cdn.filestorage.io/widget.html\
?apiKey=fs_live_…\
&widget=picker\
&proxyBaseUrl=https://api.example.com/fm\
&publicObjectBaseUrl=https://cdn.example.comUse httpBaseUrl instead of proxyBaseUrl when you implement the HTTP contract. Register your app’s WebView origin / website in the dashboard so license checks succeed.
mountWidget() (JavaScript)
<!-- In a WebView HTML shell you control, or any mobile webview page -->
<link rel="stylesheet" href="https://cdn.filestorage.io/file-storage.css" />
<div id="fm" style="height:100dvh"></div>
<script src="https://cdn.filestorage.io/file-storage.min.js"></script>
<script>
FileStorageSDK.mountWidget({
el: '#fm',
apiKey: 'fs_live_…',
widget: 'picker', // 'full' | 'picker' | 'upload' | 'media'
s3: {
proxyBaseUrl: 'https://api.example.com/fm',
publicObjectBaseUrl: 'https://cdn.example.com',
headers: () => ({ Authorization: 'Bearer ' + sessionToken }),
},
})
window.addEventListener('filestorage:pick', (e) => {
// Bridge to native if needed
console.log(e.detail)
})
</script>mountWidget is equivalent to mount({ …, widget }) with embedded chrome that fills the host - ideal for phone viewports. Same store options as web: s3, http, or store.
Native hosts
iOS (WKWebView)
import WebKit
final class FileStorageWidgetController: UIViewController, WKScriptMessageHandler {
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "filestorage")
webView = WKWebView(frame: view.bounds, configuration: config)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(webView)
var comps = URLComponents(string: "https://cdn.filestorage.io/widget.html")!
comps.queryItems = [
URLQueryItem(name: "apiKey", value: "fs_live_…"),
URLQueryItem(name: "widget", value: "picker"),
URLQueryItem(name: "proxyBaseUrl", value: "https://api.example.com/fm"),
URLQueryItem(name: "publicObjectBaseUrl", value: "https://cdn.example.com"),
]
webView.load(URLRequest(url: comps.url!))
}
func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
guard message.name == "filestorage",
let body = message.body as? String,
let data = body.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let file = json["file"] as? [String: Any] else { return }
// file["path"], file["name"], file["url"], file["mimeType"]
print(file)
}
}Android (WebView)
class FileStorageWidgetFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
val web = WebView(requireContext())
web.settings.javaScriptEnabled = true
web.settings.domStorageEnabled = true
web.addJavascriptInterface(object {
@JavascriptInterface
fun postMessage(payload: String) {
// Parse JSON: { type: "filestorage:pick", file: { path, name, url, mimeType } }
}
}, "FileStorageAndroid")
val url = Uri.parse("https://cdn.filestorage.io/widget.html").buildUpon()
.appendQueryParameter("apiKey", "fs_live_…")
.appendQueryParameter("widget", "full")
.appendQueryParameter("proxyBaseUrl", "https://api.example.com/fm")
.appendQueryParameter("publicObjectBaseUrl", "https://cdn.example.com")
.build()
web.loadUrl(url.toString())
return web
}
}React Native
import { useRef } from 'react'
import { WebView } from 'react-native-webview'
const WIDGET =
'https://cdn.filestorage.io/widget.html' +
'?apiKey=fs_live_…' +
'&widget=media' +
'&proxyBaseUrl=https://api.example.com/fm' +
'&publicObjectBaseUrl=https://cdn.example.com'
export function FileStorageWidget() {
const ref = useRef(null)
return (
<WebView
ref={ref}
source={{ uri: WIDGET }}
onMessage={(e) => {
const msg = JSON.parse(e.nativeEvent.data)
if (msg.type === 'filestorage:pick') {
// msg.file.path / name / url / mimeType
}
}}
javaScriptEnabled
domStorageEnabled
style={{ flex: 1 }}
/>
)
}Flutter
import 'dart:convert';
import 'package:webview_flutter/webview_flutter.dart';
final uri = Uri.https('cdn.filestorage.io', '/widget.html', {
'apiKey': 'fs_live_…',
'widget': 'upload',
'proxyBaseUrl': 'https://api.example.com/fm',
'publicObjectBaseUrl': 'https://cdn.example.com',
});
final controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'FileStorageAndroid', // also receives ReactNative-style posts via bridge in widget.html
onMessageReceived: (JavaScriptMessage message) {
final data = jsonDecode(message.message) as Map<String, dynamic>;
if (data['type'] == 'filestorage:pick') {
// data['file']
}
},
)
..loadRequest(uri);Pick events
When the user selects a file (picker flows / double-click insert), the SDK dispatches filestorage:pick on window. The hosted widget.html forwards that payload to:
webkit.messageHandlers.filestorage(iOS)FileStorageAndroid.postMessage(Android / Flutter channel name)ReactNativeWebView.postMessage(React Native)
{
"type": "filestorage:pick",
"file": {
"path": "/campaigns/hero.jpg",
"name": "hero.jpg",
"url": "https://cdn.example.com/campaigns/hero.jpg",
"mimeType": "image/jpeg"
}
}License & pricing
- Public gallery at /widgets - free to view.
- Production
mountWidget/ hosted shell - Pro+ API key (from $29/mo). Free keys unlock the web file manager only. - White label (no “Powered by” badge) is included on the same Pro+ plans.
- Same website registration rules as the web embed.
- Do not ship long-lived cloud secrets inside the mobile binary.
Troubleshooting
| Symptom | Fix |
|---|---|
| Blank WebView / license gate | Pro+ apiKey; register the WebView host under Dashboard → Websites. Free keys return MOBILE_WIDGET_LOCKED. |
| CORS / network errors | Allow the WebView origin on your S3 proxy or HTTP API. |
| No pick callbacks | Use widget=picker (or handle pick in your shell); confirm the message handler name matches. |
| Uploads fail | Proxy must accept multipart; pass auth via headers on the store config in a custom HTML shell. |