File Upload
The Lovina Chat SDK supports sending files (images, documents, audio, video) through the chat widget. Users can attach files via the paperclip button in the message composer.
Supported File Types and Size Limits
| Constraint | Limit |
|---|---|
| Max files per message | 5 |
| Max file size | 5 MB per file |
| Accepted types | image/*, application/pdf, text/*, audio/*, video/* |
Enabling / Disabling File Upload
File upload is enabled by default on web. To disable it:
window.lovinaSettings = {
features: { enable_file_upload: false },
};
File upload is disabled by default on native/hybrid platforms (Flutter, iOS, Android, React Native) — a browser file picker just works, but native platforms have real permission and setup requirements the widget can't verify on your behalf. To enable it, opt in explicitly once you've done the platform-specific setup described in How It Works on Each Platform:
// React Native
features: { enable_file_upload: true }
Flutter, iOS, and Android use the same enableFileUpload flag on their own LovinaFeatureFlags
type instead — see the Flutter,
iOS, or Android
configuration reference for the exact syntax.
An explicit value from the host always wins, except on Android, where true is only honored
if the native file picker actually registered.
How It Works
User Experience
- Attach files — User taps the paperclip icon in the composer to open the native file picker.
- Preview — Selected files appear in a horizontal preview strip below the composer. Images show thumbnails; other files show a file icon with extension.
- Remove — Each preview has an "x" button to remove a file before sending.
- Send — User taps send. Files are uploaded, then the message is sent with attachment references.
- Display — Images render inline in the message bubble. Other files show as download links with file icon and size.
Upload Flow
The SDK uses a two-step upload flow:
1. User picks file(s) via native file picker
2. Files validated client-side (size + count limits)
3. Files staged in preview strip with blob URL thumbnails
4. On send:
a. A chat session is established first, if one doesn't already exist —
needed before any file can be uploaded
b. Upload each file: POST /sdk/chat/upload
→ Returns { attachment_id, file_url }
c. Send message: POST /sdk/chat/message
→ Body includes { message, attachment_ids: ["att-1", "att-2"] }
d. AI responds via SSE stream
5. On any upload failure: the whole message is marked failed (never sent
partially or text-only) and shows a Retry button. Files that already
uploaded successfully are not re-uploaded on retry.
Upload API
Step 1: Upload File
POST /sdk/chat/upload
Authorization: Bearer <websiteToken>
X-Session-Token: <session_token>
Content-Type: multipart/form-data
Fields:
session_id string Active session ID
file File The file to upload (widget caps at 5MB; allow slightly more server-side for multipart overhead)
Response:
{
"attachment_id": "019078c3-d4e5-7f01-2345-678901abcdef",
"file_url": "https://storage.example.com/chat-attachments/.../document.pdf",
"file_name": "document.pdf",
"content_type": "application/pdf",
"size_in_bytes": 102400
}
Step 2: Send Message with Attachments
POST /sdk/chat/message
Authorization: Bearer <websiteToken>
Content-Type: application/json
{
"agent_config_id": "agent-xxx",
"message": "Here is the file",
"session_id": "session-xxx",
"attachment_ids": ["019078c3-d4e5-7f01-2345-678901abcdef"]
}
Attachment Object (in messages)
| Field | Type | Description |
|---|---|---|
id | string | Unique attachment ID |
fileType | string | Category: 'image', 'file', 'audio', 'video' |
dataUrl | string | URL to download/view the file |
thumbUrl | string | Thumbnail URL (images only) |
fileSize | number | File size in bytes |
Error Handling
| Scenario | What User Sees |
|---|---|
| File > 5MB | Toast: "filename exceeds 5.0 MB limit" — file rejected |
| Server rejects the upload as too large (413) | Toast: "File is too large to upload. Please choose a smaller file." |
| Unsupported file type | Toast: "filename is not a supported file type" — file rejected |
| > 5 files | Toast: "Maximum 5 files allowed" — extras rejected |
| Upload fails — network error | Toast: "Network error. Check your connection." |
| Upload fails — session/auth (401) | Toast: "File upload failed. Please refresh and try again. (401)" |
| Upload fails — server error (5xx) | Toast: "Server error. Please try again later. (5xx)" |
| Upload fails — any other status | Toast: "File upload failed. (status)" |
| Upload fails — no status determined | Toast: "File upload failed." |
| Any upload fails | The whole message (text and any other attachments) is marked failed, never sent — no partial or text-only fallback |
| Retry | Failed messages show red bubble with Retry button. Files that already uploaded successfully are preserved and not re-uploaded. |
Platform Support
| Platform | Status | Notes |
|---|---|---|
| Web (all browsers) | Supported | Native <input type="file"> with preview strip |
| Android WebView | Supported | onShowFileChooser + ActivityResultLauncher |
| iOS WKWebView | Supported | WKWebView presents a native action sheet internally (Photo Library, Take Photo or Video, Choose Files) |
| React Native | Supported | allowFileAccess props on WebView (v11+) |
| Flutter | Supported | Android: dedicated file-selector plugin · iOS: native WKWebView |
File upload is supported on all platforms — support isn't the same as enabled-by-default, though; see Enabling / Disabling File Upload above for each platform's default.
How It Works on Each Platform
When a user taps the paperclip button, the widget triggers <input type="file">. Each platform handles this differently:
| Platform | What Opens | Permissions Needed |
|---|---|---|
| Web | Browser file dialog | None |
| Android | System file picker (ACTION_GET_CONTENT) | None (system picker handles its own) |
| iOS | Native action sheet (Photo Library, Take Photo or Video, Choose Files) | None for Photo Library and Choose Files; the camera option has a requirement — see below |
| React Native | Native file picker via react-native-webview | None |
| Flutter | System file picker (ACTION_OPEN_DOCUMENT) | None (system picker handles its own) |
Android: Always call onReceiveValue() — even on cancel — or the WebView file input will hang permanently.
iOS: Always call completionHandler() — even on cancel — or WKWebView's file input silently breaks.
Permission Handling
File upload uses system file pickers on most platforms, which means no runtime permissions are required from the user — the OS handles file access internally. iOS is the exception: WKWebView's action sheet also offers a camera capture option alongside Photo Library and Choose Files, and that option does carry a permission requirement (see below).
However, there are edge cases:
| Scenario | What Happens | User Sees |
|---|---|---|
| Android: no file manager app | Intent.createChooser shows empty list | "No apps can perform this action" dialog |
| iOS: iCloud file not downloaded | OS downloads file first | iOS download progress indicator |
| iOS: camera / video capture | The WKWebView action sheet's "Take Photo or Video" option is reachable today, and needs NSCameraUsageDescription and NSMicrophoneUsageDescription in Info.plist -- omitting either one crashes the app on tap, it does not just deny the prompt. This is default WKWebView behavior the SDK doesn't control -- see iOS File Uploads for why. | iOS permission prompt (or a crash if the keys are missing) |
| Android < 13: restricted storage | System picker still works | Normal picker (system handles permissions) |
| Android: WebView not configured | <input type="file"> click does nothing | Attach button hidden (enable_file_upload resolves to false) |
Android: host context isn't a ComponentActivity | The SDK can't register a file picker at all; if enableFileUpload = true was requested, onEvent also receives ChatEvent.Error(code = "file_upload_unavailable") | Attach button hidden; logcat shows LovinaChatWidget: Host context is not a ComponentActivity... |
Android: enableFileUpload = true but the picker registered too late | The button downgrades to hidden and onEvent receives ChatEvent.Error(code = "file_upload_unavailable") | Attach button hidden; logcat shows LovinaChatWidget: Too late to register the file picker... |
| User cancels picker | onReceiveValue(null) / completionHandler(nil) called | Nothing happens, widget stays functional |
Picking a file from the system UI — Photo Library, Choose Files, the Android or web file picker — does not require any runtime permission prompts; the OS grants temporary read access to that specific file. The one exception is iOS's in-sheet camera capture option, which needs the Info.plist keys noted above.