SurveyJS PHP Backend Integration with Laravel /

Store Files Separately

Store Files Separately

Use when: Your form allows respondents to upload files.

Read this step in the docs

Try this

  1. Add one or two images or PDFs to Receipts. Requests shows one POST /api/files; the size line at the top of Requests compares the response size with Base64 against the size with IDs.
  2. Data lists the files by ID, name, and type. Press Complete: the stored response holds IDs, not contents.
  3. The preview of each file comes through GET /api/files/<id> in Requests.
  4. Switch the Demo user to Signed out, then upload a file: the upload still answers 200, but the preview's GET /api/files/<id> answers 404 (Gate::denies('read-file')).
  5. Press Start over to fill out another expense report.

Add a file to compare the response sizes.

    No requests yet.

      What the server stored

      Source on GitHub Definition: shared/definitions/store-files.json

      Client · shared/client/store-files.js · sjs:store-files.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());
      });
      Server · routes/examples/store-files.php · sjs:store-files.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',
          ]);
      });