I.4
Store files outside the response
You need this: Any form with a file question.
By default a file question embeds the file in the response as base64, which bloats every response. Set storeDataAsText to false and upload files to your own endpoint; the response keeps a reference instead of the content. If files are private, which is usual for documents, IDs and scans, store an id and add a second endpoint that checks access and returns the content. The form requests it through onDownloadFile when it shows a preview.
Try this
Add a file to compare the response sizes.
Code that just ran
These regions are read from the files that served this page, the same lines the Server Integration page shows.
shared/client/store-files.js · sjs:I.4.client
View on GitHub
// Upload: your server stores the files and returns an id (or a URL) for each
survey.onUploadFiles.add(async (_, options) => {
const body = new FormData();
options.files.forEach(file => body.append("files[]", file)); // "files[]": PHP keeps only the last of several "files"
const ids = await fetch("/api/files", { method: "POST", body }).then(r => r.json());
// The response stores these ids, not the file contents
options.callback(options.files.map((file, i) => ({ file, content: ids[i] })));
});
// Preview: only when the stored value is not a public URL the browser can open
survey.onDownloadFile.add(async (_, options) => {
const res = await fetch(`/api/files/${encodeURIComponent(options.content)}`);
if (!res.ok) return options.callback("error");
const reader = new FileReader();
reader.onload = () => options.callback("success", reader.result); // base64 data the form shows
reader.readAsDataURL(await res.blob());
});
routes/examples/store-files.php · sjs:I.4.server
View on GitHub
// POST /api/files — store uploads, return their ids in the same order
Route::post('/api/files', function (Request $request) {
$ids = [];
foreach (Arr::wrap($request->file('files')) as $file) { // field "files[]": PHP keeps only the last of several "files"
abort_unless($file->isValid(), 400, $file->getErrorMessage());
$id = (string) Str::uuid();
$file->storeAs('', $id, 'uploads'); // the "uploads" disk: local, S3…; named by id, never by the client
DB::table('files')->insert(['id' => $id, 'name' => $file->getClientOriginalName(), 'type' => $file->getMimeType()]);
$ids[] = $id;
}
return response()->json($ids);
});
// GET /api/files/{id} — the content, for private files only; check access first
Route::get('/api/files/{id}', function (string $id) {
$file = DB::table('files')->find($id);
abort_if(! $file || Gate::denies('read-file', $file), 404); // 404, not 403: don't confirm the file exists
return Storage::disk('uploads')->response($id, $file->name, [ // nosniff and sandbox: an uploaded HTML or SVG never runs as this site
'Content-Type' => $file->type, 'X-Content-Type-Options' => 'nosniff', 'Content-Security-Policy' => 'sandbox',
]);
});
Definition: shared/definitions/store-files.json