Here are two LWCs that can be used in a flow to capture a user's photo using the computer's camera, as well as their signature, and upload those as images to any record.
Both LWCs have some configuration options to meet most needs.
Additionally there's an invocable Apex class that will take the base64 strings from the LWCs, convert them to blobs, and save the images with any file naming format you choose.
| Area | Entry points |
|---|---|
| Photo screen | lwc/photoCapture/ |
| Signature screen | lwc/signatureCapture/ |
| Upload to Files | classes/BiometricUploadService.cls |
Add the Photo Capture component to a Screen Flow element.
Flow property definitions live in photoCapture.js-meta.xml.
| Flow label (Screen component property) | Purpose |
|---|---|
Input: Photo Instructions Text (photoInstructions) |
Body copy on the screen. Text templates work well; the UI uses lightning-formatted-rich-text so plain or rich text is fine. |
Input: Step Heading Text (headingText) |
Heading at the top of the step. |
Wire these to Flow variables (for example a Boolean and a Text collection or long text variable for the payload).
| Flow label | Apex / Flow usage |
|---|---|
Output: Photo Captured Flag (photoCaptured) |
Use in a Decision to confirm the user captured a photo before calling upload. |
Output: Captured Photo Base64 (photoDataUrl) |
Raw Base64 (no data:image/jpeg;base64, prefix). Pass to Upload Biometric Files as photo input. |
Capture logic: photoCapture.js.
Same pattern as photo capture. Component: lwc/signatureCapture/. Metadata: signatureCapture.js-meta.xml.
| Flow label | Purpose |
|---|---|
Input: Signature Disclaimer Text (signatureDisclaimer) |
On-screen disclaimer; text templates recommended. |
Input: Step Heading Text (headingText) |
Step heading. |
| Flow label | Usage |
|---|---|
Output: Signature Complete Flag (signatureComplete) |
Boolean for Decision logic. |
Output: Captured Signature Base64 (signatureDataUrl) |
Raw Base64 for the invocable action. |
Canvas / export: signatureCapture.js.
In the Flow, add an Action and choose Upload Biometric Files (invocable method on BiometricUploadService.cls).
Map:
- Target Record ID (required) — record to link the files to.
- Photo Base64 Data / Signature Base64 Data — outputs from the LWCs (either can be left blank if you only use one of the LWCs).
- Photo File Title / Signature File Title — optional; omit to use defaults Applicant Photo and Applicant Signature. You do not need a file extension in the title; files are still stored as JPEG (
photo.jpeg/signature.jpegon the ContentVersion).
The service decodes Base64 and inserts ContentVersion and generates a ContentDocumentLink to the record. It does not resize or validate image dimensions.
| Output | Meaning |
|---|---|
| Success | true only if neither photo nor signature upload reported an error. |
| Error Message | Combined summary when something failed. |
| Photo Error Message / Signature Error Message | Per-artifact errors (decode/DML, etc.). |
Use a Decision after the action to branch on Success and show a fault path or toast as needed.
Tests: BiometricUploadServiceTest.cls.
Defaults live in photoCaptureCameraUtility.js.
Captured images are JPEG. Compression uses JPEG_QUALITY (0.8, i.e. 80%) via canvas.toDataURL('image/jpeg', quality). Lower quality → smaller files; higher → larger. Since this was meant for demos, it's set to 80%.
Both photo and signature share the same default quality, as they use that constant (signature imports JPEG_QUALITY from the same module). To change only the photo, pass an explicit quality third argument from photoCapture.js handleCapturePhoto into captureVideoFrameToJpeg(video, canvas, quality).
The camera is requested with DEFAULT_VIDEO_CONSTRAINTS (getUserMedia). On capture, the canvas is sized to video.videoWidth / video.videoHeight and one frame is drawn so the saved pixel dimensions match the camera track, not the on-screen CSS size.
To change requested resolution, edit DEFAULT_VIDEO_CONSTRAINTS or pass custom constraints from photoCapture.js into acquireUserMedia. For a fixed export size (scale down/up regardless of camera), extend captureVideoFrameToJpeg (or wrap it) instead of always using full video.videoWidth / video.videoHeight.
| Goal | Where |
|---|---|
| Default JPEG quality (both, unless overridden) | JPEG_QUALITY in photoCaptureCameraUtility.js |
| Photo-only quality | Third argument to captureVideoFrameToJpeg from photoCapture.js |
| Requested camera resolution | DEFAULT_VIDEO_CONSTRAINTS or custom constraints in photoCapture.js |
One-click deploy of this repo’s default branch: LWCBiometricCapture.
Source root: force-app/. Metadata under force-app/main/default/main/default/. Manifest: manifest/package.xml.
| Metadata type | Count | Members |
|---|---|---|
| ApexClass | 2 | BiometricUploadService, BiometricUploadServiceTest |
| LightningComponentBundle | 3 | photoCaptureCameraUtility, photoCapture, signatureCapture |
Total: 5 metadata components (2 Apex + 3 LWC bundles).
API versions: see each component’s *-meta.xml; project sourceApiVersion is 63.0 in sfdx-project.json.
- Salesforce CLI (
sf) - Permission to deploy Apex and LWCs to the target org
git clone https://github.com/erikboderek/LWCBiometricCapture.git
cd LWCBiometricCapture
sf org login web --alias my-org
sf project deploy start --source-dir force-app --target-org my-org





