Files and payments
Most real apps need users to attach something (a photo of the broken part, a signed PDF, a profile picture) and many need to take money. Both are built in. Uploads are private to the person who made them, and payments never block an order: card when Stripe is connected, pay offline otherwise.
File uploads
What your users get
- Upload from any form or screen. Image and attachment fields in generated forms already have an upload button.
- Private by default.Each file belongs to the user who uploaded it. Asking for someone else's file returns "not found", checked in the database before storage is touched.
- Common types, sensible limits. Images, PDFs, text, audio, video, JSON, ZIP and Office documents, up to 25 MB each by default.
In your own screens
app.upload(file) returns { url, name }. Store the url in an image or attachment field of a row.
async function onPick(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
try {
const { url } = await app.upload(file);
await app.update("job", jobId, { photo: url });
} catch (err) {
setError(err instanceof Error ? err.message : "Upload failed");
}
}
<input type="file" accept="image/*" onChange={onPick} />In an offline preview there is no server to store files, so the call rejects with a message. Catch it and show it.
Where files are stored
On Appbricx Cloud, files go to the platform's object store. When you run Appbricx yourself, you choose local disk or any S3-compatible bucket. File records live in your app's own database, so they are covered by backups along with the rest of your data. Uploads appear in the editor under Backend → Logs → Files.
Reference
POST /__appbricx/files/signwith{ mime, size }returns an upload URL;app.uploaddoes this for you.GET /__appbricx/files/:idredirects the owner to a short-lived signed URL.DELETE /__appbricx/files/:idremoves a file.
Payments
How it works
- Pay offline always works. Pay at pickup or on delivery is the default, so an app is never blocked on a payment provider.
- Card once you connect Stripe. Add your Stripe credentials in the integrations vault and checkout offers card. Your users pay on Stripe's own checkout page and come back to your app, which confirms with Stripe before the payment counts as paid. No key reaches the browser.
- The server sets the amount.It recomputes the total from the order's lines and your catalogue prices, so a user cannot change what they pay.
- Orders are never lost. If Stripe cannot be reached, the order is recorded as an offline payment with a note instead of failing.
Apps with a basket get a checkout step automatically. Payments appear in the editor under Backend → Logs → Payments.
In your own code
const app = useAppHandle();
const methods = await app.pay.methods(); // ["offline"] or ["offline", "card"]
await app.pay.checkout("order", orderId, "card"); // goes to Stripe Checkout
// Back on the page after paying:
useEffect(() => {
const back = app.pay.returned(); // { status, orderId } or null
if (back?.status === "success") {
void app.pay.confirm("order", back.orderId).then(setStatus); // "paid"
}
}, [app]);fallback is set when a card payment could not go through and the order was recorded as offline instead. Show the note to the user. See the SDK.
Related: Frontend SDK · Integrations · Webhooks and schedules