Integrations
Notifications through the Triggers service, and uploads plus image transforms through ImageKit.
Two external services are wired into Thunder UI out of the box. Both are opt-in - leave their variables unset and the corresponding UI never appears.
Triggers - Notifications
Notifications are served by Triggers - a separate notification service, itself built with Thunder Framework but deployed independently and not part of the framework core. It is not your main API, and it is configured with its own base URL and tenant:
VITE_TRIGGERS_BASE_URL=https://triggers.example.com
VITE_TRIGGERS_TENANT_ID=...
VITE_UNREAD_COUNT_INTERVAL=30000 # millisecondsBoth the base URL and the tenant id are required for any notification UI to appear, and the whole feature is additionally gated by VITE_ENABLE_NOTIFICATIONS.
What Ships
- A navbar popover
- A mobile side sheet
- A full notifications page
- A polled unread count
| Module | Role |
|---|---|
src/core/endpoints/notification.ts | The Triggers client |
src/core/hooks/useUnreadCount.ts | useUnreadCount(userId) - the polling hook |
These requests send no credentials.
Push notifications on native are registered through @capacitor/push-notifications, and the device token is posted to the backend with ThunderSDK.users.addFcmToken.
ImageKit - Uploads and Image Transforms
File uploads go to ImageKit. The browser never holds an ImageKit key: the SDK mints a short-lived signature with ThunderSDK.imageKit.auth(), and @imagekit/react uploads directly from the browser.
import { handleUpload, transformImage } from "@/core/lib/utils"
// Upload a file. Returns the ImageKit response, including `url`.
const res = await handleUpload(file, {
folder: "invoices", // defaults to VITE_UPLOAD_FOLDER
path: ["2026", "q1"], // optional path prefix
filename: "custom-name.pdf", // defaults to file.name
signal: controller.signal, // cancellable
onProgress: (pct) => setProgress(pct),
})
// Request a resized variant via ImageKit's `tr` query parameter.
const thumb = transformImage(res.url, { width: 64, height: 64 })VITE_UPLOAD_FOLDER= # ImageKit folder; empty uses the account defaultSchema fields with fieldHint: "avatar" or "upload" use this automatically - the avatar uploader and the file-table uploader both call handleUpload and store the returned URL. See Schema to Form Fields.
The signing endpoint is provided by thunder-core's upload auth, which is why the plugin is a prerequisite for uploads to work at all.