SurveyJS PHP Backend Integration with Laravel /

Resume Progress

Resume Progress

Use when: Respondents need to complete a form across multiple sessions.

Read this step in the docs

Try this

  1. Type in a few fields. Requests shows one PUT /api/progress/onboarding per pause, not one per keystroke, and Progress at the top of Requests says when it saved.
  2. Go to the next page: the page change saves too.
  3. Type a letter, then switch to another browser tab and come back at once: the PUT was flushed when the tab was hidden.
  4. Press Reload page: you are back on the same page, in the same input, with your answers.
  5. Press Start over: the form is empty again, on its first page, and one PUT saves that, so Reload page no longer brings your answers back.

Progress:

    No requests yet.

      What the server stored

      Source on GitHub Definition: shared/definitions/resume-progress.json

      Client · shared/client/resume-progress.js · sjs:resume-progress.client View on GitHub
      import debounce from "lodash/debounce";
      
      // On return: restore both — the user continues on the same page, in the same input.
      // Restore before listening, so restoring the page doesn't count as a change to save.
      const saved = await fetch(`/api/progress/${formId}`).then(r => (r.ok ? r.json() : null));
      if (saved) {
        survey.data = saved.data;
        survey.uiState = saved.uiState;
      }
      
      // Save answers + UI state as the user works (uiState: see the Form Library API reference)
      const saveProgress = debounce(() => {
        fetch(`/api/progress/${formId}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ data: survey.data, uiState: survey.uiState }),
          keepalive: true                    // lets the last request finish while the tab closes
        });
      }, 800);
      survey.onValueChanged.add(saveProgress);
      survey.onCurrentPageChanged.add(saveProgress);
      document.addEventListener("visibilitychange", () => {
        if (document.visibilityState === "hidden") saveProgress.flush();
      });
      Server · routes/examples/resume-progress.php · sjs:resume-progress.server View on GitHub
      // GET /api/progress/{formId} — return the stored progress (answers + UI state) (404 if none yet)
      Route::get('/api/progress/{formId}', function (string $formId) {
          $json = DB::table('progress')->where('key', $formId)->value('json') ?? abort(404, 'No progress saved yet');
      
          return response($json)->header('Content-Type', 'application/json');
      });
      
      // PUT /api/progress/{formId} — store the progress (answers + UI state) as-is: it's one JSON document
      // Real code keys it by user and form, e.g. Auth::id().':'.$formId; the samples key by form for brevity
      Route::put('/api/progress/{formId}', function (Request $request, string $formId) {
          // The raw body, not $request->input(): PHP arrays would turn {} into [] in the stored JSON
          $progress = json_decode($request->getContent(), flags: JSON_THROW_ON_ERROR);
          StoredJson::put('progress', $formId, $progress);                         // stored as it arrived: 1.0 stays 1.0
      
          return response()->noContent();
      });