{"record":{"id":"19509a4e2f2621b4","repo":"transloadit/uppy","slug":"onbeforesnapshot-message","errorCode":null,"errorMessage":"onBeforeSnapshot: ${message}","messagePattern":"onBeforeSnapshot: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@uppy/webcam/src/Webcam.tsx","lineNumber":588,"sourceCode":"          clearInterval(countDown)\n          this.uppy.info(this.i18n('smile'), 'success', 1500)\n          setTimeout(() => resolve(), 1500)\n        }\n      }, 1000)\n    })\n  }\n\n  async takeSnapshot(): Promise<void> {\n    if (this.captureInProgress) return\n\n    this.captureInProgress = true\n\n    try {\n      await this.opts.onBeforeSnapshot()\n    } catch (err) {\n      const message = typeof err === 'object' ? err.message : err\n      this.uppy.info(message, 'error', 5000)\n      throw new Error(`onBeforeSnapshot: ${message}`)\n    }\n\n    try {\n      const file = await this.getImage()\n      this.capturedMediaFile = file\n\n      if (file.data == null) throw new Error('File data is empty')\n      // Create object URL for preview\n      const capturedSnapshotUrl = URL.createObjectURL(file.data)\n      this.setPluginState({ capturedSnapshot: capturedSnapshotUrl })\n      this.captureInProgress = false\n    } catch (error) {\n      // Logging the error, except restrictions, which is handled in Core\n      this.captureInProgress = false\n      if (!error.isRestriction) {\n        this.uppy.log(error)\n      }\n    }","sourceCodeStart":570,"sourceCodeEnd":606,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/webcam/src/Webcam.tsx#L570-L606","documentation":"Webcam's takeSnapshot first runs the user-supplied onBeforeSnapshot hook; if that hook throws or rejects, the error message is prefixed and rethrown as `onBeforeSnapshot: <message>`. The original message is also shown to the user via uppy.info.","triggerScenarios":"Passing an onBeforeSnapshot option that throws or returns a rejected promise — e.g. a countdown routine that rejects, a permission check that fails, or a bug in the callback.","commonSituations":"Custom '3-2-1 countdown' or 'prepare camera' hooks; hooks doing await on user gesture/permission that can reject; throwing strings instead of Error objects in the callback (message becomes the string).","solutions":["Debug your onBeforeSnapshot callback — the suffixed text is your own error's message","Make the hook resolve instead of reject for recoverable conditions, and catch expected failures inside it","If using a countdown, resolve it (e.g. Promise.resolve) instead of rejecting on cancel"],"exampleFix":"// before\nonBeforeSnapshot: () => Promise.reject(new Error('camera not ready'))\n\n// after\nonBeforeSnapshot: () => {\n  if (!cameraReady) return Promise.resolve() // skip prep gracefully\n  return prepareCamera()\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await webcam.takeSnapshot()\n} catch (e) {\n  if (e.message.startsWith('onBeforeSnapshot:')) {\n    console.error('hook failed:', e.message.slice('onBeforeSnapshot:'.length))\n  }\n}","preventionTips":["Never let onBeforeSnapshot reject for expected conditions — resolve instead","Wrap risky logic inside your hook with its own try/catch","Throw Error objects, not strings, so message extraction is reliable"],"tags":["webcam","user-callback","hook","snapshot"],"backgroundTag":"callback-hook-threw","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}