A Salesforce app that lets you create public web forms that submit Cases with file attachments. Built for the AppExchange.
Status: Partner Community case filed (2026-02-15), awaiting Salesforce response.
The package contains a deactivated RemoteSiteSetting (Google_reCAPTCHA) that triggers an "Approve Third-Party Access" prompt on every install, even though isActive=false. We can't remove the component from the 2GP managed package without Salesforce granting removal permission.
Once Salesforce responds:
- Delete
force-app/main/default/remoteSiteSettings/Google_reCAPTCHA.remoteSite-meta.xml - Create new package version, promote, install — verify prompt is gone
- Update install URL in this README
What we tried that didn't work:
- Changing the URL to
login.salesforce.com(first-party) — prompt still fires for any RemoteSiteSetting regardless of URL - Setting
isActive=false— prompt still fires
The package v1.9.0.1 has been installed in the dev hub org (devhub / dietrich-dev-ed).
- Log in to the dev hub:
https://dietrich-dev-ed.develop.my.salesforce.com - Assign the Web-to-Case Admin permission set to your user (Setup → Permission Sets → Web-to-Case Admin → Manage Assignments)
- Open the Web-to-Case Forms app from the App Launcher
- Run through the Setup Wizard (select/create a Site, configure Guest User permissions)
- Create a test form in Form Manager and verify end-to-end submission
- Test the embeddable widget on an external page (add domain to Allowed Domains, use embed snippet)
- Submit for AppExchange security review via the Salesforce Partner Community
- Address any security review feedback
- Multi-file upload (drag & drop)
- Custom field types (picklist, date, checkbox)
- Form analytics / submission tracking
- Email notifications on submission
- Re-enable reCAPTCHA (uncomment code across 12+ files — see Re-enabling reCAPTCHA for v2)
- reCAPTCHA v3 testing (create v3 keys, test score-based blocking, test type switching)
- Raise image compression target from 0.7MB to ~3MB (chunked uploads now support up to 4MB — no need to crush image quality to avoid chunking)
- Increase test coverage for
WebToCaseNonceService(51% → 81%) - Increase test coverage for
SetupWizardController(50% → 86%) - Register namespace prefix →
caseform - Create managed package — Package ID
0Hod20000000XlZCAU - Create package version —
04td2000000KAPpAAO(v1.7.0.1, all classes pass) - Promote package version — Released (v1.7.0.1)
- Test install in dev hub — Successfully installed/upgraded in
dietrich-dev-ed
- reCAPTCHA code removed — all captcha logic commented out with
/* v2: reCAPTCHA ... */markers; Remote Site Setting deactivated (eliminates "Approve Third-Party Access" install prompt) - File upload fixes —
submitForm()usesFirstPublishLocationIdfor Guest User access;SYNC_CHUNK_LIMITreduced 3 → 2 to avoid 6MB heap limit;FileAssemblyQueueableusesFirstPublishLocationId; removed invalid CRUD checks on Content objects - Setup banner real-time updates —
formAdminApp.jsuses imperative Apex + LMS subscription so banner updates without page refresh after wizard completion - Package version —
04td2000000KDAnAAO(v1.9.0.1), promoted, installed in devhub
- Security fixes deployed (IDOR on
uploadFileChunk, nonce replay, SOQL injection, chunk validation) -
checkUploadStatusIDOR check added (authorization required before status polling) -
authorizeCaseForUploadmade conditional onEnable_File_Upload__c - SOQL injection false positives resolved (converted dynamic SOQL to static queries)
- Empty catch blocks fixed (added
System.debuglogging) - ESLint violations fixed in
caseFormScript.jsandcaseFormWidget.js - Org-specific metadata removed (CORS origins, CSP trusted sites, Site config)
-
.forceignorecreated to prevent re-pulling org-specific metadata -
WebToCaseNonceServicetest coverage: 51% → 81% (14 new tests) -
SetupWizardControllertest coverage: 50% → 86% (12 new tests) - 257/257 tests passing
- FlexiPage deployment blocker fixed (namespace
caseform:component references) - DOM XSS sinks eliminated (
innerHTML→textContentfor error messages) - Server error message leakage fixed (generic messages to client)
- Explicit sharing declarations added (
ErrorLogger,WebToCaseRateLimiter) - AggregateResult namespace robustness fix (
FormAdminController)
- 0 Critical/High violations on custom code (Apex SOQL injection + empty catch blocks fixed, JS ESLint clean)
- 59
pmd:ApexCRUDViolationremaining — all false positives (PMD can't detect manualassertAccessible/assertCreateable/stripInaccessibleenforcement). Won't fail security review. - 19 violations in
imageCompression.js— third-party minified library, not our code
The following items need manual verification in the dev org:
- Create a standalone HTML page with a custom form layout
- Include the widget script and call
WebToCaseForm.connect()with correct apiBase/formName - Verify form submission creates a Case with correct field values
- Verify file upload works via
fileInputSelectoroption - Verify CAPTCHA renders in the user-provided container (if enabled)
- Verify validation errors appear in the user's error container and use
aria-invalid - Verify success state hides the form and shows the success container with case number in
[data-wtc-case-number] - Verify
destroy()cleans up event listeners (test re-initialization in SPA-like scenario) - Verify console warnings appear for missing
nameattributes on required fields
- Embed the widget on a test page and set
--wtc-container-max-width,--wtc-container-padding— verify they take effect - Test title variables:
--wtc-title-font-size,--wtc-title-font-weight,--wtc-title-margin - Test description variables:
--wtc-description-color,--wtc-description-font-size - Test label variables:
--wtc-label-font-size,--wtc-label-font-weight,--wtc-label-color - Test input variables:
--wtc-input-padding,--wtc-input-font-size - Test field gap:
--wtc-field-gap - Test submit button:
--wtc-submit-color,--wtc-submit-background,--wtc-submit-padding,--wtc-submit-font-size,--wtc-submit-border-radius - Test success/error:
--wtc-success-background,--wtc-success-border,--wtc-error-background,--wtc-error-border
- Verify scoped tabs (Widget / Custom HTML / iframe) render with light blue active background
- Verify "Generate Embed Code" button is required before tabs appear
- Verify tabs show immediately when loading a saved form with existing allowed domains
- Verify Custom HTML tab generates correct HTML snippet with actual form fields
- Verify Custom HTML tab generates correct CSS snippet scoped to form ID
- Verify Custom HTML tab generates correct JS snippet with
connect()call - Verify all Copy buttons work in all three tabs
- Verify existing
WebToCaseForm.render()widget still works identically after the mixin refactor - Verify iframe embed still works
Note: reCAPTCHA UI is hidden for v1 MVP (v0.7.0). These tests apply only after re-enabling for v2.
- Create reCAPTCHA v3 keys at Google Admin Console (select "v3")
- Configure v3 in Setup Wizard (select "v3 Score-based" type)
- Test form submission with v3 - verify invisible badge appears
- Verify low-score submissions are blocked (use VPN/incognito to simulate bot-like behavior)
- Test switching between v2 Checkbox, v2 Invisible, and v3 Score-based
| Namespace | caseform |
| Version | v1.9.0.1 (Released) |
| Phases Complete | 0-4 (MVP, Admin UI, Setup Wizard, reCAPTCHA, Embeddable Widget) |
| Next Up | Manual test install verification → AppExchange security review submission |
| Dev Org | devorg (tilman.dietrich@gmail.com.dev) |
| GitHub | https://github.com/tilman-d/salesforce-webtocase |
| Dev Hub | devhub (tilman.dietrich@gmail.com.freelance) |
| Package ID | 0Hod20000000XlZCAU |
| Package Version | 04td2000000KDAnAAO (v1.9.0.1) |
| Install URL | https://login.salesforce.com/packaging/installPackage.apexp?p0=04td2000000KDAnAAO |
| Last Change | v1.9.0 — reCAPTCHA removal, file upload fixes, setup banner real-time updates |
Salesforce's native Web-to-Case doesn't support file attachments. This app solves that problem with:
- Configurable forms stored as custom objects
- Public Visualforce page for form rendering
- File attachments via ContentVersion/ContentDocumentLink
- LWC Admin UI for managing forms without using Setup
- Google reCAPTCHA v2/v3 for spam protection (hidden in v1 MVP, re-enable for v2)
- Error logging for debugging
| Phase | Description | Status |
|---|---|---|
| Phase 0 | MVP - Core form submission | ✅ Complete |
| Phase 1 | Admin UI (LWC form builder) | ✅ Complete |
| Phase 2 | Post-Install Setup Wizard | ✅ Complete |
| Phase 3 | reCAPTCHA integration (code commented out for v1, re-enable for v2) | ✅ Complete |
| Phase 4 | Embeddable widget for external websites | ✅ Complete |
| Phase 5 | Multi-file upload, custom field types | 🔜 Next up |
- Form__c - Form configuration (name, title, description, file upload settings)
- Form_Field__c - Field definitions (label, type, Case field mapping, required, sort order)
- Error_Log__c - Error logging for debugging
- CaseFormController - Main controller for form rendering and submission
- ErrorLogger - Utility for logging errors
- CaseFormPage - Public form page that renders dynamically based on Form__c configuration
- caseFormStyles.css - Clean, responsive form styling
- caseFormScript.js - Client-side validation and form submission
Access via the Form Manager tab in Salesforce.
Features:
- List all forms with field counts and created date
- Sortable columns - click column headers to sort (Form Name, Title, Fields, Active, Created)
- Create new forms
- Edit existing forms (click form name to edit)
- Delete forms with confirmation modal
- Toggle form active/inactive status
- Add, edit, delete, and reorder fields
- Preview forms with "View Live" button
- URL hash routing for bookmarkable links (
#new,#edit/{formId}) - Form name validation (unique, URL-safe)
- Toast notifications for success/error feedback
- FormAdminController - CRUD operations for Form__c and Form_Field__c with wrapper classes
- FormAdminControllerTest - 33 unit tests, 94%+ code coverage
- formAdminApp - Main container with list view, routing, and delete modal
- formDetail - Form settings editor with inline field management
- Form_Manager.flexipage-meta.xml - Lightning App Page
- Form_Manager.tab-meta.xml - Navigation tab
- Added FormAdminController class access
- Added Form_Manager tab visibility
Access via the Setup Wizard tab or the Web-to-Case Forms app in the App Launcher.
5-Step Wizard Flow (v1 MVP):
- Welcome - Precondition checks (My Domain, admin permissions, Sites enabled)
- Select Site - Choose from existing Sites or get instructions to create one
- Configure - Auto-configure Guest User object/field permissions with security acknowledgment
- Verify - Manual configuration instructions for Apex class and VF page access with validation
- Complete - Success message + "What's Next" box with link to Form Manager
- "Test Your Setup" box hidden for v1 (re-enable via
showTestSetupBoxgetter)
- "Test Your Setup" box hidden for v1 (re-enable via
Note: The reCAPTCHA step (previously step 5) is hidden for v1 MVP. All reCAPTCHA code is commented out (v0.8.2) with
/* v2: reCAPTCHA ... */markers — see Re-enabling reCAPTCHA for v2 to restore.
Features:
- Automatic detection of active Salesforce Sites
- One-click permission configuration for Guest User profile
- Support for both Enhanced Profile UI and Classic Profile UI instructions
- Validation to ensure all permissions are correctly configured
- reCAPTCHA configuration UI - Enter Site Key and Secret Key without Anonymous Apex (hidden in v1 MVP)
- Sample "Contact Support" form creation
- Dynamic public URL generation (works with Enhanced Domains)
- SetupWizardController - Site detection, permission configuration, validation, sample form creation, reCAPTCHA settings management
- SetupWizardControllerTest - Unit tests with coverage (41 tests)
- setupWizard - Multi-step wizard with progress indicator
- setupStatus - Configuration Status Dashboard (embedded in Setup Wizard page, hidden when unconfigured, auto-refreshes via LMS when wizard completes)
- Setup_Wizard.flexipage-meta.xml - Lightning App Page
- Setup_Wizard.tab-meta.xml - Navigation tab
- Web_to_Case_Forms.app-meta.xml - Lightning App (consolidates all functionality)
- SetupStatusRefresh.messageChannel-meta.xml - LMS channel for wizard→dashboard communication
- Added SetupWizardController class access
- Added Setup_Wizard tab visibility
- Added Web_to_Case_Forms app visibility
v1 MVP Note: All reCAPTCHA executable code has been commented out (v0.8.2) for the v1 AppExchange release. This eliminates the "Approve Third-Party Access" install prompt (caused by the Remote Site Setting), removes HTTP callouts to external services (simplifies security review), and reduces onboarding friction. Metadata fields (
Enable_Captcha__c,reCAPTCHA_Settings__c) are kept for v2 re-enablement. All commented code is marked with/* v2: reCAPTCHA ... */. See Re-enabling reCAPTCHA for v2 below.
Protects public forms from spam and bot submissions with "I'm not a robot" checkbox verification.
Features:
- Per-form CAPTCHA toggle (
Enable_Captcha__cfield on Form__c) - Protected Custom Setting for API keys (
reCAPTCHA_Settings__c) - Client-side reCAPTCHA widget rendering
- Server-side token verification via Google API
- Graceful error handling with user-friendly messages
- Admin UI toggle in Form Manager
- reCAPTCHA_Settings__c - Protected hierarchy Custom Setting
Site_Key__c- Public key for widget renderingSecret_Key__c- Private key for server-side verification
- Enable_Captcha__c - Checkbox to enable/disable CAPTCHA per form
- Google_reCAPTCHA -
Allows callouts toDeleted in v0.8.2 (caused "Approve Third-Party Access" install prompt)https://www.google.comfor token verification
- CaseFormController.cls -
verifyCaptcha(),getCaptchaSiteKey(),getCaptchaEnabled()(commented out in v0.8.2, return stubs) - CaseFormPage.page - reCAPTCHA script loading and widget rendering (commented out in v0.8.2)
- caseFormScript.js - CAPTCHA token extraction and submission (commented out in v0.8.2)
- caseFormWidget.js - CAPTCHA widget rendering and token handling (commented out in v0.8.2)
- caseFormStyles.css - Styling for CAPTCHA widget and error messages
- formDetail LWC - Added "Enable CAPTCHA" toggle in form settings (hidden in v0.7.0)
- setupWizard.js - reCAPTCHA imports and methods (commented out in v0.8.2)
- setupStatus.js - CAPTCHA_TYPE_LABELS constant (commented out in v0.8.2)
- WebToCaseRestAPI.cls - Captcha config in REST response (commented out in v0.8.2)
- SetupWizardController.cls - reCAPTCHA settings methods (commented out in v0.8.2)
Enable_Captcha__cfield permission removed from permission set in v0.8.2
- CaseFormControllerTest - CAPTCHA-specific tests and HTTP mocks commented out in v0.8.2
- SetupWizardControllerTest - ~25 reCAPTCHA test methods commented out in v0.8.2
- 88% code coverage on CaseFormController
Allows users to embed Web-to-Case forms on external websites using a <script> tag.
Three Embed Modes:
- Uses Shadow DOM for CSS isolation
- Customizable via 28 CSS variables (container, title, description, labels, inputs, submit button, success/error)
- Form rendered directly in the host page
WebToCaseForm.connect()binds submission logic to user's own HTML form- No Shadow DOM, no rendered HTML — full design control
- Validates against form config using
aria-invalidattributes - Collects only config-defined fields by
nameattribute - Supports file upload, CAPTCHA, chunked uploads, and all existing features
destroy()method for SPA re-initialization- Init-time console warnings for missing required field inputs
- Full DOM isolation
- Automatic height resizing via postMessage
- Best for sites with strict CSP policies
| Method | Path | Description |
|---|---|---|
| GET | /webtocase/v1/form/{formName} |
Get form configuration + nonce |
| POST | /webtocase/v1/submit |
Submit form data |
| POST | /webtocase/v1/upload-chunk |
Upload file chunk |
| OPTIONS | /* |
CORS preflight |
- Origin validation: Strict domain allowlist per form
- One-time nonce: Prevents replay attacks (15-min TTL)
- Rate limiting: 100 submissions/hour per origin
- Field allowlist: Server ignores unknown fields
Apex Classes:
WebToCaseRestAPI.cls- REST API with CORS supportWebToCaseNonceService.cls- Nonce generation/validationWebToCaseRateLimiter.cls- Rate limiting logicWebToCaseRestAPITest.cls- Unit tests
Static Resources:
caseFormWidget.js- Embeddable widget script
Custom Objects:
Rate_Limit_Counter__c- Tracks rate limits per origin
New Fields:
Form__c.Allowed_Domains__c- Newline-separated domain allowlist
- Embed Code section in Form Detail with:
- Allowed Domains textarea with "Generate Embed Code" confirmation button
- SLDS scoped tabs: Widget | Custom HTML | iframe
- Widget tab: Inline script snippet + CSS variables snippet
- Custom HTML tab: Generated HTML/CSS/JS snippets from actual form fields
- iframe tab: iframe embed snippet
- Copy buttons for all code snippets
- reCAPTCHA domain warning (above tabs, applies to all methods)
#support-form {
/* Base */
--wtc-primary-color: #0176d3;
--wtc-font-family: system-ui, -apple-system, sans-serif;
--wtc-text-color: #181818;
--wtc-border-radius: 4px;
--wtc-error-color: #c23934;
--wtc-success-color: #2e844a;
/* Container */
--wtc-container-max-width: 100%;
--wtc-container-padding: 0;
/* Title */
--wtc-title-font-size: 1.5rem;
--wtc-title-font-weight: 600;
--wtc-title-margin: 0 0 8px 0;
/* Description */
--wtc-description-color: #666;
--wtc-description-font-size: 0.875rem;
/* Labels */
--wtc-label-font-size: 0.875rem;
--wtc-label-font-weight: 500;
--wtc-label-color: var(--wtc-text-color);
/* Inputs */
--wtc-input-border: 1px solid #c9c9c9;
--wtc-input-background: #ffffff;
--wtc-input-padding: 10px 12px;
--wtc-input-font-size: 1rem;
/* Field layout */
--wtc-field-gap: 20px;
/* Submit button */
--wtc-submit-color: #fff;
--wtc-submit-background: var(--wtc-primary-color);
--wtc-submit-padding: 12px 24px;
--wtc-submit-font-size: 1rem;
--wtc-submit-border-radius: var(--wtc-border-radius);
/* Success/Error */
--wtc-success-background: #d4edda;
--wtc-success-border: 1px solid #c3e6cb;
--wtc-error-background: #f8d7da;
--wtc-error-border: 1px solid #f5c6cb;
}-
Configure allowed domains in Form Manager:
- Edit your form
- Go to "Embed Code" section
- Add your website domain (e.g.,
example.com) - Save the form
-
Add embed code to your website:
<div id="support-form"></div>
<script src="https://yoursite.salesforce-sites.com/support/resource/caseFormWidget"></script>
<script>
WebToCaseForm.render({
formName: 'support',
containerId: 'support-form',
apiBase: 'https://yoursite.salesforce-sites.com/support/services/apexrest',
onSuccess: function(caseNumber) {
console.log('Case created:', caseNumber);
},
onError: function(error) {
console.error('Error:', error);
}
});
</script>Use WebToCaseForm.connect() for full control over form HTML and styling:
<form id="wtc-support">
<div>
<label for="SuppliedName">Your Name *</label>
<input type="text" id="SuppliedName" name="SuppliedName" required />
</div>
<div>
<label for="SuppliedEmail">Email *</label>
<input type="email" id="SuppliedEmail" name="SuppliedEmail" required />
</div>
<div>
<label for="Subject">Subject *</label>
<input type="text" id="Subject" name="Subject" required />
</div>
<div>
<label for="Description">Message *</label>
<textarea id="Description" name="Description" required></textarea>
</div>
<div id="wtc-error" hidden></div>
<button type="submit">Submit</button>
</form>
<div id="wtc-success" hidden>
<p>Your request has been submitted successfully.</p>
<p>Reference: <span data-wtc-case-number></span></p>
</div>
<script src="https://yoursite.salesforce-sites.com/support/resource/caseFormWidget"></script>
<script>
WebToCaseForm.connect({
formName: 'support',
formSelector: '#wtc-support',
apiBase: 'https://yoursite.salesforce-sites.com/support/services/apexrest',
errorContainerId: 'wtc-error',
successContainerId: 'wtc-success',
onSuccess: function(caseNumber) {
console.log('Case created:', caseNumber);
},
onError: function(error) {
console.error('Error:', error);
}
});
</script>Input name attributes must match Case field API names from your form config. You can rearrange elements, add classes, and style freely — the script only touches elements it needs.
Use iframe if you need full DOM isolation:
<iframe
src="https://yoursite.salesforce-sites.com/support/apex/CaseFormPage?form=support&embed=1"
style="width:100%; border:none; min-height:500px;"
id="wtc-frame">
</iframe>
<script>
window.addEventListener('message', function(e) {
if (e.data && e.data.type === 'wtc:resize') {
document.getElementById('wtc-frame').style.height = e.data.height + 'px';
}
});
</script>Add these to your Content-Security-Policy if using the inline widget:
script-src: https://yoursite.salesforce-sites.com
connect-src: https://yoursite.salesforce-sites.com
frame-src: https://www.google.com https://yoursite.salesforce-sites.com
If your form uses CAPTCHA, add your embedding domain to Google reCAPTCHA:
- Go to Google reCAPTCHA Admin Console
- Select your reCAPTCHA site
- Add your embedding domain (e.g.,
example.com) to the allowed domains list
| Issue | Solution |
|---|---|
| "Origin not allowed" error | Add your domain to Allowed Domains in Form Manager |
| CORS errors | Verify the apiBase URL is correct |
| CAPTCHA not loading | Add your domain to Google reCAPTCHA allowed domains |
| Form not rendering | Check browser console for JavaScript errors |
| Rate limit exceeded | Wait 1 hour or contact the form administrator |
v1 MVP Note: reCAPTCHA admin UI is hidden in v0.7.0. These tests apply only after re-enabling for v2 (see changelog).
Use this checklist to verify reCAPTCHA integration in your org:
- Go to Google reCAPTCHA Admin Console
- Create a new site with reCAPTCHA v2 "I'm not a robot" checkbox
- Add your Salesforce Site domain (e.g.,
yourorg.my.salesforce-sites.com) - Copy the Site Key and Secret Key
- Go to Setup Wizard tab (or run the wizard from App Launcher → "Web-to-Case Forms")
- If you already completed setup, navigate to Step 5 (reCAPTCHA)
- Enter your Site Key and Secret Key
- Click Save reCAPTCHA Settings
- Proceed to Complete step
Alternative (Anonymous Apex):
reCAPTCHA_Settings__c settings = reCAPTCHA_Settings__c.getOrgDefaults();
settings.Site_Key__c = 'YOUR_SITE_KEY_HERE';
settings.Secret_Key__c = 'YOUR_SECRET_KEY_HERE';
upsert settings;- Go to Form Manager tab
- Click New Form or edit an existing form
- Check the Enable CAPTCHA checkbox
- Add at least one required field (e.g., Name, Email, Subject)
- Check Active
- Click Save
- Click View Live to open the form in a new tab
- Verify the reCAPTCHA widget ("I'm not a robot") appears below the form fields
- Test 1: Submit without CAPTCHA
- Fill in all required fields
- Do NOT click the CAPTCHA checkbox
- Click Submit
- Verify error message: "Please complete the CAPTCHA verification."
- Test 2: Submit with CAPTCHA
- Fill in all required fields
- Click the reCAPTCHA checkbox (wait for green checkmark)
- Click Submit
- Verify success message with case number
- Go to Cases tab in Salesforce
- Find the newly created case
- Verify the case fields match your form submission
- Create or edit a form with Enable CAPTCHA unchecked
- View the form publicly
- Verify NO reCAPTCHA widget appears
- Submit the form and verify it works without CAPTCHA
- Go to Form Manager tab
- Verify the Enable CAPTCHA checkbox appears in form settings
- Verify the help text explains reCAPTCHA requirements
- Toggle CAPTCHA on/off and save - verify it persists
| Issue | Solution |
|---|---|
| reCAPTCHA widget doesn't appear | Check that reCAPTCHA_Settings__c has Site Key configured |
| "CAPTCHA verification failed" on submit | Verify Secret Key is correct in Custom Settings |
| Form hangs on submit | Check browser console for errors; verify Remote Site Setting exists |
| Error: "Unauthorized endpoint" | Add Remote Site Setting for https://www.google.com |
- Salesforce CLI (
sf) installed - Access to a Salesforce org (Developer Edition or Sandbox)
sf org login web --alias myorg
sf project deploy start --target-org myorg
sf apex run test --target-org myorg --test-level RunLocalTestsRun this anonymous Apex to create a sample form:
Form__c form = new Form__c(
Form_Name__c = 'support',
Title__c = 'Contact Support',
Description__c = 'We\'ll get back to you within 24 hours.',
Success_Message__c = 'Thank you! Your case has been submitted.',
Active__c = true,
Enable_File_Upload__c = true,
Enable_Captcha__c = true // Enable CAPTCHA
// Note: File limits are fixed (Images: 25MB auto-compressed, Documents: 4MB)
);
insert form;
List<Form_Field__c> fields = new List<Form_Field__c>{
new Form_Field__c(Form__c = form.Id, Field_Label__c = 'Your Name', Field_Type__c = 'Text', Case_Field__c = 'SuppliedName', Required__c = true, Sort_Order__c = 1),
new Form_Field__c(Form__c = form.Id, Field_Label__c = 'Email Address', Field_Type__c = 'Email', Case_Field__c = 'SuppliedEmail', Required__c = true, Sort_Order__c = 2),
new Form_Field__c(Form__c = form.Id, Field_Label__c = 'Subject', Field_Type__c = 'Text', Case_Field__c = 'Subject', Required__c = true, Sort_Order__c = 3),
new Form_Field__c(Form__c = form.Id, Field_Label__c = 'Message', Field_Type__c = 'Textarea', Case_Field__c = 'Description', Required__c = true, Sort_Order__c = 4)
};
insert fields;Or use the Form Manager tab to create forms via the UI (Phase 1).
- Setup → Sites → Register your domain
- Create new site:
- Site Label:
Support - Site Name:
support(orsfor shorter URL) - Active Site Home Page:
CaseFormPage
- Site Label:
- Activate the site
On the Site detail page → Public Access Settings:
Object Permissions:
| Object | Read | Create |
|---|---|---|
| Form__c | ✓ | |
| Form_Field__c | ✓ | |
| Case | ✓ | |
| ContentVersion | ✓ | |
| ContentDocumentLink | ✓ | |
| Error_Log__c | ✓ |
Field Permissions (Form__c): Read access to Description__c, Enable_File_Upload__c, Success_Message__c, Enable_Captcha__c
Field Permissions (Form_Field__c): Read access to Required__c
Apex Class Access: CaseFormController, ErrorLogger
VF Page Access: CaseFormPage
reCAPTCHA admin UI is hidden in v0.7.0. To enable, see Re-enabling reCAPTCHA for v2 in the changelog.
- Get API keys from Google reCAPTCHA Admin
- Setup → Custom Settings → reCAPTCHA Settings → Manage → New
- Enter Site Key and Secret Key
Access: https://[your-domain].my.salesforce-sites.com/support/CaseFormPage?name=support
- Navigate to the Form Manager tab in Salesforce
- Click New Form to create a form
- Fill in form settings:
- Form Name: URL-safe identifier (lowercase, hyphens only)
- Title: Display title shown on the form
- Description: Instructions shown at the top
- Success Message: Shown after submission
- Active: Toggle to enable/disable the form
- Enable File Upload: Allow file attachments
- Enable CAPTCHA: Require reCAPTCHA verification (Phase 3, hidden in v1 MVP)
- Add fields in the Form Fields section
- Click Save
- Use View Live to preview the form
- Multi-file upload (drag & drop)
- Custom field types (picklist, date, checkbox)
- Form analytics/submission tracking
- Email notifications on submission
force-app/main/default/
├── applications/
│ └── Web_to_Case_Forms.app-meta.xml # Phase 2 - Lightning App
├── cachePartitions/
│ └── WebToCase.cachePartition-meta.xml # Platform Cache for nonces
├── # corsWhitelistOrigins/ — removed (org-specific, excluded via .forceignore)
├── # cspTrustedSites/ — removed (org-specific, excluded via .forceignore)
├── objects/
│ ├── Form__c/ # Form configuration
│ │ └── fields/
│ │ ├── Enable_Captcha__c # Phase 3
│ │ ├── Site_Id__c # URL Display Feature
│ │ ├── Allowed_Domains__c # Phase 4 - Embed allowlist
│ │ └── Default_Case_Values__c # JSON defaults for hidden Case fields
│ ├── Form_Field__c/ # Field definitions
│ ├── Error_Log__c/ # Error logging
│ ├── Rate_Limit_Counter__c/ # Phase 4 - Rate limiting
│ └── reCAPTCHA_Settings__c/ # Phase 3 - API keys + Site settings
│ └── fields/
│ ├── Site_Key__c
│ ├── Secret_Key__c
│ ├── Captcha_Type__c
│ ├── Score_Threshold__c
│ ├── Default_Site_Id__c # URL Display Feature
│ └── Default_Site_Base_Url__c # URL Display Feature
├── classes/
│ ├── CaseDefaultFieldConfig.cls # Shared allowlist for default Case fields
│ ├── CaseFormController.cls
│ ├── CaseFormControllerTest.cls
│ ├── ErrorLogger.cls
│ ├── ErrorLoggerTest.cls
│ ├── FileAssemblyQueueable.cls # Async chunk assembly (4MB support)
│ ├── FileAssemblyQueueableTest.cls
│ ├── FormAdminController.cls # Phase 1
│ ├── FormAdminControllerTest.cls # Phase 1
│ ├── SetupWizardController.cls # Phase 2
│ ├── SetupWizardControllerTest.cls # Phase 2
│ ├── WebToCaseRestAPI.cls # Phase 4 - REST endpoints
│ ├── WebToCaseNonceService.cls # Phase 4 - Nonce management
│ ├── WebToCaseRateLimiter.cls # Phase 4 - Rate limiting
│ └── WebToCaseRestAPITest.cls # Phase 4 - Tests
├── messageChannels/
│ └── SetupStatusRefresh.messageChannel-meta.xml # LMS channel
├── lwc/
│ ├── formAdminApp/ # Phase 1 - Main admin container
│ ├── formDetail/ # Phase 1 - Form editor (+ CAPTCHA toggle)
│ ├── setupStatus/ # Configuration Status Dashboard (+ LMS subscriber)
│ └── setupWizard/ # Phase 2 - Setup wizard (+ LMS publisher)
├── flexipages/
│ ├── Form_Manager.flexipage-meta.xml # Phase 1
│ └── Setup_Wizard.flexipage-meta.xml # Phase 2
├── tabs/
│ ├── Form_Manager.tab-meta.xml # Phase 1
│ └── Setup_Wizard.tab-meta.xml # Phase 2
├── pages/
│ └── CaseFormPage.page # + reCAPTCHA widget (Phase 3)
├── staticresources/
│ ├── caseFormStyles.css # + CAPTCHA styles (Phase 3)
│ ├── caseFormScript.js # + compression, validation, postMessage
│ ├── caseFormWidget.js # Phase 4 - Embeddable widget
│ └── imageCompression.js # browser-image-compression library
├── # remoteSiteSettings/ — Google_reCAPTCHA deleted in v0.8.2 (re-create for v2)
├── # sites/ — removed (org-specific, excluded via .forceignore)
└── permissionsets/
└── Web_to_Case_Admin.permissionset-meta.xml
- Verify Form__c record exists with matching
Form_Name__c - Verify
Active__c = true - Check Guest User has Read access to Form__c
- For preview mode, add
?preview=trueto bypass Active check
- Verify
Enable_File_Upload__c = trueon the Form__c record - Check Guest User has Read access to
Enable_File_Upload__cfield
- Check Error_Log__c for error details
- Verify Guest User has Create access to Case, ContentVersion, ContentDocumentLink
- Enable debug logs for the Guest User
- Assign the Web_to_Case_Admin permission set to your user
- Verify Form_Manager tab is set to Visible in the permission set
- Verify
Enable_Captcha__c = trueon the Form__c record - Check that
reCAPTCHA_Settings__chas a valid Site Key - Guest User needs Read access to
Enable_Captcha__cfield
- Verify Secret Key is correct in
reCAPTCHA_Settings__c - Check Remote Site Setting
Google_reCAPTCHAis active - Review Error_Log__c for detailed error messages
Fixed three bugs in the chunked file upload pipeline discovered during end-to-end upload limit testing:
- Heap limit crash on sync assembly:
SYNC_CHUNK_LIMITreduced from 3 → 2. Synchronous assembly of 3 chunks (~2MB file) caused ~8MB peak heap during base64 concatenation + blob decode, exceeding the 6MB synchronous limit (LimitException, uncatchable). Files with 3+ chunks now route through the Queueable path (12MB async heap). - "Private library" error in FileAssemblyQueueable: Final
ContentVersionwas created withoutFirstPublishLocationId, causingINVALID_STATUS: Documents in a user's private library must always be owned by that userwhen the Queueable runs as Automated Process User. Fixed by usingFirstPublishLocationId = caseId(same pattern as chunk uploads) and removing manualContentDocumentLinkcreation. Same fix applied toassembleSynchronousfor consistency. - Overly strict CRUD checks for Guest Users: Removed
assertDeletable(ContentDocument)andassertCreateable(ContentDocumentLink)checks from assembly methods. Guest Users get implicit Content object access viaFirstPublishLocationId; explicitObjectPermissionscannot be granted for Content standard objects (INVALID_OR_NULL_FOR_RESTRICTED_PICKLISTon DML).
Files changed: CaseFormController.cls, FileAssemblyQueueable.cls
Status: Deployed to devorg, all tests pass. Needs 2GP package workflow (v1.9.0) to reach devhub.
Commented out all reCAPTCHA/captcha executable code across 12+ files for the v1 AppExchange release. This eliminates the "Approve Third-Party Access" install prompt (caused by the Google_reCAPTCHA Remote Site Setting) and removes HTTP callouts to external services (simplifies security review). All commented code is marked with /* v2: reCAPTCHA ... */ for easy re-enablement.
Deleted:
Google_reCAPTCHA.remoteSite-meta.xml— Remote Site Setting that caused the install prompt
Apex changes (methods return stubs):
CaseFormController.cls— 6 methods:getCaptchaSiteKey()returns'',getCaptchaType()returns'V2_Checkbox',getCaptchaEnabled()returnsfalse, captcha verification block insubmitForm()commented out,verifyCaptchaWithDetails()returns empty map,verifyCaptcha()returnsfalseWebToCaseRestAPI.cls— hardcodedenableCaptcha=falsein response, captcha config block commented outSetupWizardController.cls—getReCaptchaSettings()returns empty result,saveReCaptchaSettings()returns error "reCAPTCHA is disabled in v1",clearReCaptchaSettings()returns empty result, reCAPTCHA block ingetFullStatus()commented out
Frontend changes:
CaseFormPage.page— reCAPTCHA script loading (v2+v3), widget rendering (3 types), and formConfig captcha vars commented outcaseFormScript.js—onCaptchaSuccesscallback,captchaResolve, token extraction,grecaptcharefs commented outcaseFormWidget.js— captcha rendering in FormWidget, token handling in SubmissionMixin, captcha rendering in ConnectedForm commented outsetupWizard.js—getReCaptchaSettings/saveReCaptchaSettingsimports, tracked properties, 8 handler methods commented outsetupStatus.js—CAPTCHA_TYPE_LABELSconstant commented out
Permission set:
Web_to_Case_Admin.permissionset-meta.xml— removedEnable_Captcha__cFLS entry
Tests commented out:
CaseFormControllerTest.cls— ~16 captcha test methods and 5 HTTP mock classesSetupWizardControllerTest.cls— ~25 reCAPTCHA test methods across 5 sections
Not modified (kept for v2 re-enablement):
Enable_Captcha__cfield onForm__c(defaults tofalse)reCAPTCHA_Settings__cobject and fields (also stores non-captchaDefault_Site_Id__c,Default_Site_Base_Url__c)formDetail.js—showCaptchaTogglealready returnsfalse(v0.7.0)
Status: Deployed to devorg, all tests pass. Needs 2GP package workflow (v1.9.0) to reach devhub.
- Fixed: Configuration Status dashboard no longer shows prematurely when only Step 2 (site selection) is completed — now requires all permissions to pass before appearing
- Fixed: Form Manager "setup incomplete" banner now checks permissions, not just site selection —
isSetupComplete()callsgetSetupStatus()to verifyallPassed - Both surfaces now correctly hide until the wizard is fully completed (all steps including Verify)
Created 2GP managed package for AppExchange submission. Package ID: 0Hod20000000XlZCAU.
Org setup:
- Authenticated Dev Hub org (
devhub) and linkedcaseformnamespace via Namespace Registries (App Launcher, not Setup) - Created package with
sf package create
2GP test compatibility fixes (5 retries to resolve all issues):
- stripInaccessible removal: Removed
Security.stripInaccessible()fromFormAdminControllerandWebToCaseRestAPIfor package-owned objects. In 2GP packaging scratch orgs, the admin profile lacks FLS on namespace-prefixed custom fields, causingstripInaccessibleto silently remove fields and crash downstream code. CRUD checks remain; FLS controlled by package permission set. - Permission set completeness: Added all missing non-required field FLS entries, removed required fields (Salesforce rejects them), fixed
Error_Log__c.allowCreate. - Test setup PS assignment: Added
PermissionSetAssignmentforWeb_to_Case_Adminin@TestSetupof 3 test classes. - Profile query resilience: Changed
SetupWizardControllerTestfrom hardcoded'Standard User'profile to flexible query with graceful skip. - Cache partition: Set
WebToCasepartitionisDefaultPartition=false(can't package default partitions).
Blocked: Daily package version create limit (6/day) exhausted. Version create command ready to run next session.
Fixes three blockers identified during pre-submission security audit.
1. FlexiPage deployment failure (hard blocker):
- Root cause: Org has registered namespace
caseform, but FlexiPage XML referenced components with defaultc:prefix - Fixed
Form_Manager.flexipage-meta.xml→caseform:formAdminApp - Fixed
Setup_Wizard.flexipage-meta.xml→caseform:setupStatus,caseform:setupWizard - Added
targetConfigswithsupportedFormFactorstoformAdminApp.js-meta.xmlfor consistency
2. DOM XSS sink elimination (security review risk):
- Replaced
innerHTMLwithtextContentfor all error message rendering:caseFormScript.js(VF Remoting error display)caseFormWidget.js(widget + connect mode error display)
- Replaced raw
e.getMessage()inCaseFormController.uploadFileChunkcatch block with generic message — exception details now logged server-side only viaErrorLogger
3. Explicit sharing declarations (security review risk):
ErrorLogger→public without sharing class(must always insertError_Log__cregardless of caller context; has CRUD checks)WebToCaseRateLimiter→public without sharing class(must always accessRate_Limit_Counter__cfor accurate rate limiting; has CRUD checks)
4. AggregateResult namespace robustness:
- Added field alias in
FormAdminControlleraggregate query (SELECT Form__c formId, COUNT(Id) cnt) to avoid namespace-dependent key resolution inAggregateResult.get()
Test results: 255/257 tests passing. 2 failures are LeadConversionServiceTest (non-package class, broken org Flow — pre-existing). All package tests pass.
Fixed "INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST" error when clicking "Configure Permissions" in the Setup Wizard (Step 3).
Root cause: Error_Log__c.Timestamp__c is a required field (<required>true</required>). Salesforce automatically grants FLS (Read/Edit) on required fields to all profiles, so they don't appear in the FieldPermissions restricted picklist. The wizard was trying to upsert a FieldPermissions record for this field, which Salesforce rejected.
Fix: Removed Timestamp__c from the REQUIRED_FIELD_READ map in SetupWizardController — no FLS configuration needed for required fields.
Increased test coverage for the two classes that were below the 75% AppExchange threshold.
WebToCaseNonceService: 51% → 81% (14 new tests in WebToCaseRestAPITest):
- Nonce generate + validate/consume round-trip, one-time use enforcement
- Validation failures: formId mismatch, origin mismatch, expired nonce
- Blank origin matching (nonce with blank origin accepts any requester)
- Multiple independent nonces, empty allowedFields edge case
- Case upload authorization: full round-trip, blank/null IDs, non-authorized case
- Platform Cache code paths (generate, validate, authorize, check via cache)
isCacheAvailable()cached result path,NonceDatawrapper defaults
SetupWizardController: 50% → 86% (12 new tests in SetupWizardControllerTest):
- Real Site tests covering private helpers:
checkObjectPermission,checkFieldPermission,checkApexAccess,checkVfPageAccess,configureObjectPermission,configureFieldPermission,getSiteBaseUrl getSetupStatus,configurePermissions,validateConfiguration,getPublicUrl,getConfigSummary,getFullStatus,saveDefaultSite— all with real active Site- CRUD denial for
saveDefaultSiteandconfigurePermissions - No Guest User error paths for
getSetupStatusandconfigurePermissions getFullStatuswith reCAPTCHA configured + real Site (end-to-end dashboard)
Test results: 257/257 Apex tests passed (100%). Org-wide coverage: 64%.
Hides all reCAPTCHA admin UI surfaces for the v1 AppExchange submission. reCAPTCHA adds onboarding friction (customers must get Google API keys), introduces an external HTTP dependency (complicates security review), and increases support burden. The feature is well-built but not essential for v1. All code remains intact and conditional — Enable_Captcha__c defaults to false and all frontend/backend logic is gated on it.
Changes:
- Setup Wizard (
setupWizard.js/html): Reduced from 6 steps to 5 — reCAPTCHA step commented out, Complete step renumbered from 6 to 5. reCAPTCHA status messages hidden on Complete step. - Form Editor (
formDetail.js/html): "Enable CAPTCHA" checkbox hidden viashowCaptchaTogglegetter (returnsfalse) - Setup Status Dashboard (
setupStatus.js/html): reCAPTCHA panel hidden viashowRecaptchaPanelgetter (returnsfalse). Dashboard now shows 3 panels: Site, Permissions, URL. - Remote Site Setting (
Google_reCAPTCHA.remoteSite-meta.xml): Deactivated (isActive=false)
Not modified (all remain intact):
CaseFormController.cls— captcha logic conditional onEnable_Captcha__cWebToCaseRestAPI.cls— API gates captcha config on the same flagCaseFormPage.page— VF rendering conditional oncaptchaEnabledcaseFormScript.js/caseFormWidget.js— JS conditional onenableCaptchaCaseFormControllerTest.cls— all captcha tests pass (use mocks + explicitEnable_Captcha__c = true)reCAPTCHA_Settings__candForm__c.Enable_Captcha__c— metadata stays in package
Re-enabling for v2 — 4 toggles in 4 files, each marked with v1 MVP / v2 comments:
setupWizard.js— Uncomment reCAPTCHA step, renumber back to 6 stepsformDetail.js—showCaptchaTogglereturnstruesetupStatus.js—showRecaptchaPanelreturnstrueGoogle_reCAPTCHA.remoteSite-meta.xml—isActiveback totrue
Test results: 227/227 Apex tests passed (100%). Playwright verified: 5 wizard steps, no CAPTCHA toggle in form editor, no reCAPTCHA panel in dashboard, form submission works without captcha (Case 00001114 created).
- 6 CSS bug fixes in
caseFormWidget.js:- Added
box-sizing: border-boxto inputs and submit button (prevents overflow with padding) - Focus ring now respects
--wtc-primary-color(was hardcoded Salesforce blue) - Case number color uses
--wtc-success-color(was hardcoded green) - Disabled button uses
opacity: 0.6instead of hardcoded gray (respects custom--wtc-submit-background) - Removed self-referencing CSS variable declarations from
:host(dead code per CSS spec) - Error focus ring respects
--wtc-error-color(was hardcoded red)
- Added
- Added explicit fallbacks to all bare
var(--wtc-*)references throughout widget CSS - Transparent nonce retry: On expired nonce errors, widget auto-fetches fresh config and resubmits once (no user action needed)
- iframe embed support: Changed Site clickjack protection from
SameOriginOnlytoAllowAllFramingto allow iframe embedding on external domains - CORS headers before errors: Moved CORS header setting before error responses in all REST API endpoints so browsers can read error messages
- Nonce service fix: Fixed Platform Cache API to use partition-qualified calls; reduced nonce key from 256-bit to 128-bit to fit 50-char cache key limit
- Snippet ID collisions fix: Custom HTML tab in Admin UI now uses form-scoped IDs (
{formId}-file,{formId}-error) instead of generic IDs - New metadata:
WebToCase.cachePartition— Platform Cache partition for noncesdietrich_ai.corsWhitelistOrigin/dietrich_ai_bare.corsWhitelistOrigin— CORS whitelist for dietrich.aidietrich_ai.cspTrustedSite— CSP trusted site for iframe embeddingSupport.site— Salesforce Site metadata withAllowAllFraming
- setupStatus hidden when unconfigured: Dashboard renders nothing when the org has no configuration, eliminating the redundant "Setup Not Complete" message
- Auto-refresh via LMS: When the Setup Wizard completes (Step 6),
setupStatusautomatically refreshes and appears without a page reload - New Lightning Message Channel:
SetupStatusRefreshfor sibling component communication betweensetupWizardandsetupStatus - Immediate card display: Card shows loading spinner immediately on page load (no 2-second lag on configured orgs)
- New LWC:
setupStatus- Configuration Status Dashboard showing real-time setup health- Displays Site configuration, Guest User permissions, reCAPTCHA status, and public form URL
- Embedded in the Setup Wizard Lightning page alongside the wizard
- Uses
SetupWizardController.getFullStatus()for live status checks - Copy-to-clipboard for public form URL
- Collapsible permission details section
- Setup Wizard progress indicator: Reverted from
type="path"(chevron/arrow) totype="base"(standard dot stepper)
- Connect mode (
WebToCaseForm.connect()): Bind submission logic to user's own HTML form — no Shadow DOM, full design control- Validates against form config using
aria-invalidattributes - Collects only config-defined fields by
nameattribute - Supports file upload, CAPTCHA, chunked uploads, polling — all existing features
destroy()method for clean SPA re-initialization- Init-time console warnings for missing required field inputs
- Validates against form config using
- Extended CSS variables: Expanded from 8 to 28 variables covering container, title, description, labels, inputs, field gap, submit button, success, and error styling
- SubmissionMixin: Internal refactor extracting shared submission-pipeline methods from FormWidget — no behavior change, enables code reuse by ConnectedForm
- Admin UI: Embed Code section redesigned:
- SLDS scoped tabs (Widget / Custom HTML / iframe) replacing sequential layout
- "Generate Embed Code" button — domains must be confirmed before snippets appear
- Custom HTML tab generates HTML, CSS, and JS snippets from the form's actual fields
- All tabs have Copy buttons for each snippet
- Updated files:
caseFormWidget.js(CSS variables, SubmissionMixin, ConnectedForm,connect()),formDetail.js/html/css(snippets, handlers, scoped tabs) - No backend (Apex) changes — connect mode uses the same REST endpoints
- Default Case Values: Admins can configure hidden Case field defaults per form (e.g., Priority=High, Type=Problem)
- JSON storage: Defaults stored as JSON on
Form__c.Default_Case_Values__c(LongTextArea) - Allowlisted fields: Priority, Status, Origin, Type, Reason — enforced server-side via
CaseDefaultFieldConfig - Config-time validation:
FormAdminController.saveForm()validates JSON structure, allowlist, and non-blank string values - Submit-time application:
CaseFormController.submitForm()applies defaults between hardcoded fallbacks and user fields - DML retry resilience: If invalid picklist values cause DML failure, system retries with a fresh Case (no defaults), logs error
- Admin UI: New "Default Case Values" accordion section in Form Detail with picklist-aware field/value selectors
- New Apex class:
CaseDefaultFieldConfig— shared allowlist constant used by both controllers - New field:
Form__c.Default_Case_Values__c— LongTextArea(32768) for JSON defaults - New method:
FormAdminController.getCaseFieldsForDefaults()— returns field metadata with active picklist values - Updated permission set:
Web_to_Case_Admin— added read/edit forDefault_Case_Values__c - Test coverage: 12 new test methods (7 in FormAdminControllerTest, 5 in CaseFormControllerTest)
- Increased document upload limit from 2MB to 4MB using async Queueable assembly
- New Apex class:
FileAssemblyQueueable- Assembles file chunks asynchronously using 12MB Queueable heap- Files <= 2MB (3 chunks): Assembled synchronously in CaseFormController (6MB heap)
- Files 2-4MB (4+ chunks): Assembled asynchronously via Queueable (12MB heap)
- Updated frontends: caseFormScript.js (VF Remoting) and caseFormWidget.js (REST API) both enforce 4MB limit
- Updated backend: CaseFormController and WebToCaseRestAPI enforce 4MB hard cap server-side
- Updated Admin UI: Form Manager help text now reads "Documents: up to 4MB"
- Chunk pattern:
__chunk__{uploadKey}__{chunkIndex}__{totalChunks}__{fileName}(750KB chunks)
- Embeddable widget for external websites with Shadow DOM isolation
- Two embed modes: Inline widget (recommended) and iframe embed
- REST API for cross-origin form operations (
/webtocase/v1/*) - Security features:
- Origin validation with per-form domain allowlist
- One-time nonces (15-min TTL) prevent replay attacks
- Rate limiting (100 submissions/hour per origin)
- Server-side field allowlist enforcement
- CSS variable theming for widget customization
- postMessage API for iframe height auto-resize
- New Apex classes:
WebToCaseRestAPI- REST endpoints with CORS supportWebToCaseNonceService- Nonce generation/validationWebToCaseRateLimiter- Rate limiting logic
- New static resource:
caseFormWidget.js- Embeddable widget script - New custom object:
Rate_Limit_Counter__cfor rate limit tracking - New field:
Form__c.Allowed_Domains__c- Domain allowlist - Admin UI: Embed Code section with code snippets and copy buttons
- Test coverage: WebToCaseRestAPITest with nonce, rate limit, and origin validation tests
- Fixed: Users can now change reCAPTCHA type (v2 Checkbox, v2 Invisible, v3 Score) without re-entering the secret key
- Previously, the Setup Wizard required both Site Key and Secret Key to save any changes
- Now, when reCAPTCHA is already configured, users can change just the type and click Save
- Backend already supported partial updates (null values preserve existing) - this fix updates frontend validation to match
- First-time setup still requires both keys (unchanged behavior)
- Image compression: Images up to 25MB are automatically compressed to ~0.7MB before upload
- Uses browser-image-compression library for client-side compression
- Converts HEIC/PNG/WebP/BMP to JPEG for optimal size
- Shows "Optimizing..." progress during compression
- Compression target (0.7MB) ensures single-request upload without chunking
- Fixed file limits: Removed user-configurable "Max File Size" setting
- Images: Up to 25MB (auto-compressed)
- Documents (PDF, Word, etc.): Up to 4MB
- Videos: Not supported (users prompted to email separately)
- Simplified UX: "Enable File Upload" checkbox now shows help text with limits
- "Images: up to 25MB (auto-compressed) | Documents: up to 4MB"
- New static resource:
imageCompression.js- browser-image-compression library (v2.0.2) - Removed: "Max Document Size (MB)" input field from Form Manager
- Fixed: Files larger than ~1.5MB could not be uploaded due to Salesforce Visualforce Remoting payload limits
- Solution: Implemented chunked file uploads for large files
- Files ≤750KB: Uploaded in single request (as before)
- Files >750KB: Automatically split into 750KB chunks and uploaded sequentially
- Chunks are stored temporarily as ContentVersions, then assembled into final file
- Chunk files are automatically cleaned up after assembly
- New Apex methods:
uploadFileChunk(caseId, fileName, chunkData, chunkIndex, totalChunks, uploadKey)- Handles chunk uploadsgetChunkSize()- Returns chunk size for JavaScript reference
- Updated JavaScript: Added chunking logic, progress indicator during multi-chunk uploads
- Test coverage: 6 new unit tests for chunked upload scenarios
- Note: The
Max_File_Size_MB__csetting now actually works for files up to the configured limit (previously limited to ~2MB)
- Site dropdown added to Form Detail editor (new/edit form views)
- Select per-form Site override or use org-wide default
- Sites loaded from active Salesforce Sites in the org
- Public URL display with copy button in Form Detail view
- Shows computed public URL based on selected Site
- Warning message when no default Site is configured
- "Copy URL" row action added to Form Manager list view
- Copies public form URL to clipboard with one click
- Toast feedback on success/failure
- Default Site configuration saved during Setup Wizard (Step 2 → Step 3)
- Stores both Site ID and resolved base URL in
reCAPTCHA_Settings__c
- Stores both Site ID and resolved base URL in
- "View Live" action now uses correct public Site URL (not internal Salesforce URL)
- New custom fields:
reCAPTCHA_Settings__c.Default_Site_Id__c- Default Site ID for orgreCAPTCHA_Settings__c.Default_Site_Base_Url__c- Cached base URLForm__c.Site_Id__c- Per-form Site override
- URL encoding for form names with special characters
- 143 Apex tests passing
- Setup Wizard Complete step: Consolidated from 4 boxes to 2 boxes for cleaner UX
- Box 1: "Test Your Setup" - combines Sample Form, Test Form, and Public URL sections
- Box 2: "What's Next" - links to Form Manager
- Test Form section now only appears after sample form is created (prevents confusing "Form Not Available" errors)
- Sample Form marked as "(Optional)" for clarity
- Form Manager: Added "Created" date column (shows when each form was created)
- Form Manager: Added column sorting - click any column header to sort ascending/descending
- Form Editor: Fixed dropdown overflow issue where "Maps to Case Field" dropdown was cut off at bottom of screen
- Bug fix: Fixed "SObject row was retrieved via SOQL without querying the requested field: Form__c.CreatedDate" error
- Fixed: Replaced custom CSS chevron path with standard
lightning-progress-indicatorcomponent - Eliminates persistent white gap issues between chevron steps
- Native Salesforce component handles complete/current/incomplete styling automatically
- Simplified codebase: removed ~245 lines of custom CSS and ~50 lines of JS
- Click navigation on steps still works via
data-stepattribute
- reCAPTCHA v3 Score-based support added alongside v2 Checkbox and v2 Invisible
- reCAPTCHA Type selector in Setup Wizard with clear descriptions for each type
- Simplified Score Threshold UX - removed confusing technical input, uses sensible default (0.3)
- Admins can still adjust threshold via Setup → Custom Metadata Types if needed
- Updated
reCAPTCHA_Settings__cwithCaptcha_Type__candScore_Threshold__cfields - Server-side v3 score verification with configurable threshold
- Client-side v3 integration with invisible badge
- Added Step 5: reCAPTCHA to Setup Wizard (wizard now has 6 steps)
- Admin-friendly UI for configuring reCAPTCHA API keys without Anonymous Apex
- Partial updates supported (update Site Key or Secret Key independently)
- Input validation (max 255 chars, whitespace trimming)
- "Skip for Now" option for users who don't need CAPTCHA
- Warning on Complete step if reCAPTCHA was skipped
- 11 new unit tests for reCAPTCHA settings (41 total in SetupWizardControllerTest)
- 114 total tests passing across all controllers
- Google reCAPTCHA v2 "I'm not a robot" checkbox integration
- Per-form CAPTCHA toggle (
Enable_Captcha__cfield) reCAPTCHA_Settings__cprotected Custom Setting for API keys- Server-side token verification via Google API
- Client-side CAPTCHA widget with error handling
- Admin UI "Enable CAPTCHA" toggle in Form Manager
- Remote Site Setting for Google API callouts
- 9 new unit tests for CAPTCHA scenarios (22 total in CaseFormControllerTest)
- 88% code coverage on CaseFormController
- 5-step post-install setup wizard (expanded to 6 steps in v0.4.1, reduced back to 5 in v0.7.0)
- Automatic Guest User permission configuration
- Site detection and selection
- Manual configuration instructions (Enhanced + Classic Profile UI)
- Configuration validation
- Sample form creation
- Dynamic public URL generation
- "Web-to-Case Forms" Lightning App in App Launcher
- SetupWizardController with unit tests
- Setup_Wizard Lightning tab
- LWC Form Manager for creating/editing forms without Setup
- FormAdminController with CRUD operations (33 tests, 94%+ coverage)
- formAdminApp component with list view and delete modal
- formDetail component with inline field editing
- Clickable form names for quick editing
- Field reordering with up/down arrows
- URL hash routing for bookmarkable links
- Preview mode support (
?preview=true) - Form_Manager Lightning tab
- Initial release
- Form__c, Form_Field__c, Error_Log__c custom objects
- CaseFormController with file attachment support
- CaseFormPage Visualforce page
- Basic styling and client-side validation
- Error logging utility
See the 🧪 MANUAL TESTING REQUIRED section at the top of this README for the current testing checklist.
The devhub runs the managed package. Direct sf project deploy does NOT update managed package components. The only way to update what the user sees is to create a new package version, promote it, and install it.
# 1. Commit your changes first (so git log = source of truth)
git add <files> && git commit -m "description"
# 2. Create a new package version
sf package version create --package "Web-to-Case Forms" --target-dev-hub devhub \
--code-coverage --wait 20 --definition-file config/project-scratch-def.json \
--installation-key-bypass
# 3. Promote (release) the version
sf package version promote --package <04t...> --target-dev-hub devhub --no-prompt
# 4. Install/upgrade in devhub
sf package install --package <04t...> --target-org devhub \
--upgrade-type Mixed --wait 10 --no-prompt
# 5. Commit the updated sfdx-project.json (new alias was added in step 2)
git add sfdx-project.json && git commit -m "Package version x.y.z"
# 6. Hard refresh browser (Ctrl+Shift+R) to see changes- Use minor increments for each change:
1.3.0,1.4.0,1.5.0, ... - 2GP managed packages do NOT support patch versioning (
1.2.1) unless enabled via Partner Community case sfdx-project.jsonusesMAJOR.MINOR.PATCH.NEXTformat — always useX.Y.0.NEXT
| Error | Fix |
|---|---|
ErrorAncestorNotHighestRelease |
Update ancestorVersion in sfdx-project.json to latest released minor (e.g., 1.2.0.LATEST) |
A released package version with version number X already exists |
Bump versionNumber patch or minor |
| Retrieved LWC shows correct content but org shows old | You retrieved the unpackaged copy, not the managed package. Create a new package version and install it. |
- Uncommitted changes are NOT reliably in the package. Always
git commitbeforesf package version create. - Retrieving
LightningComponentBundle:setupWizardreturns the unpackaged copy, not the managedcaseform__setupWizard. This is misleading — the managed component shows(hidden)in subscriber orgs. sf project deployto devhub creates an unpackaged copy alongside the managed package. The FlexiPages usecaseform:setupWizard, so the unpackagedc:setupWizardis never displayed.- Patch versioning (
1.2.1) is NOT supported for 2GP managed packages by default. Requires a Partner Community case. Use minor bumps (1.3.0,1.4.0) instead. ObjectPermissionsandFieldPermissionsstore namespace-prefixed API names (e.g.,caseform__Form__c). Code that queries these setup entities must qualify custom names at runtime.- Daily limit: 6 package version creates per day per dev hub.
Fix Configuration Status dashboard and Form Manager banner showing prematurely.
- Subscriber Package Version ID:
04td2000000KAWHAA4 - Ancestor: v1.7.0.1
- Install URL:
https://login.salesforce.com/packaging/installPackage.apexp?p0=04td2000000KAWHAA4 setupStatus.js:isConfiguredgetter now requiresallPermissionsPassed === trueSetupWizardController.isSetupComplete(): now callsgetSetupStatus()and checksvalidation.allPassedinstead of just checkingDefault_Site_Id__c- Both surfaces correctly hide until the wizard is fully completed (all steps including permission verify)
Auto-configure Apex/VF access, setup banner, tab reorder.
- Subscriber Package Version ID:
04td2000000KAPpAAO - Ancestor: v1.6.0.1
- Install URL:
https://login.salesforce.com/packaging/installPackage.apexp?p0=04td2000000KAPpAAO - Setup Wizard Step 3 now auto-grants
SetupEntityAccessfor Apex classes and VF pages - Form Manager tab is now the default/first tab in the app
- Setup banner in Form Manager warns when Setup Wizard hasn't been completed
- Namespace-aware fallback instructions in Step 4
Hide Configuration Status card on wizard steps 2-5 and fix scroll jump.
- Subscriber Package Version ID:
04td2000000KA4rAAG - Ancestor: v1.5.0.1
- Dashboard card only shows on Step 1; hides during wizard steps to reduce noise
Reorder step 4: validation results first, instructions when failures.
- Subscriber Package Version ID:
04td2000000K9yPAAS - Ancestor: v1.4.0.1
- Step 4 now shows validation results at the top, with manual instructions only when there are failures
Add "Open Site Settings" button to Setup Wizard step 4.
- Subscriber Package Version ID:
04td2000000K9wnAAC - Ancestor: v1.3.0.1
- Convenience link to Salesforce Site settings page from within the wizard
Fix: Namespace prefix in Guest User permission configuration.
- Subscriber Package Version ID:
04td2000000K9qLAAS - Ancestor: v1.2.0.1
- Code coverage: 78%
- Install URL:
https://login.salesforce.com/packaging/installPackage.apexp?p0=04td2000000K9qLAAS - Installed in dev hub org (
dietrich-dev-ed) — verified working - Fix:
ObjectPermissionsandFieldPermissionsnow use namespace-qualified API names (caseform__Form__cinstead ofForm__c) - Added
qualifyApiName()runtime namespace detection inSetupWizardController
Fix: Setup Wizard instructions (text + link).
- Subscriber Package Version ID:
04td2000000K9jtAAC - "Active Site Home Page" text fix, Sites Setup link fix
Did NOT contain the text fix (built from uncommitted working tree).
- Subscriber Package Version ID:
04td2000000K9TlAAK
Original release.
- Subscriber Package Version ID:
04td2000000K8xVAAS
Fixes applied during version creation:
SetupWizardControllerTest.testPreconditionsAsStandardUser: Changed profile query to filter byPermissionsCustomizeApplication = false AND PermissionsModifyAllData = false(packaging scratch org profiles can have admin perms even if not named "System Administrator")- Added
@TestVisibleto 7 private helper methods inSetupWizardControllerand 25 new direct unit tests that don't depend on active Sites (coverage: 48% → 68% in packaging scratch org)
| Alias | Username | Purpose |
|---|---|---|
devhub |
tilman.dietrich@gmail.com.freelance |
Dev Hub org (2GP packaging) |
devorg |
tilman.dietrich@gmail.com.dev |
Namespace org (caseform registered here) |
| Package Name | Web-to-Case Forms |
| Package ID | 0Hod20000000XlZCAU |
| Package Type | 2GP Managed |
| Namespace | caseform |
| Version Config | 1.9.0.NEXT in sfdx-project.json (minor increments — patch versioning not enabled) |
All reCAPTCHA code is commented out with /* v2: reCAPTCHA ... */ markers. To re-enable for v2:
1. Restore deleted file:
| File | Action |
|---|---|
Google_reCAPTCHA.remoteSite-meta.xml |
Re-create Remote Site Setting for https://www.google.com with <isActive>true</isActive> |
2. Uncomment Apex code (search for /* v2: reCAPTCHA):
| File | What to uncomment |
|---|---|
CaseFormController.cls |
6 methods: getCaptchaSiteKey, getCaptchaType, getCaptchaEnabled, captcha block in submitForm, verifyCaptchaWithDetails, verifyCaptcha |
WebToCaseRestAPI.cls |
enableCaptcha response value, captcha config block |
SetupWizardController.cls |
getReCaptchaSettings, saveReCaptchaSettings, clearReCaptchaSettings, reCAPTCHA block in getFullStatus |
3. Uncomment frontend code (search for /* v2: reCAPTCHA):
| File | What to uncomment |
|---|---|
CaseFormPage.page |
reCAPTCHA script loading, widget rendering, formConfig vars |
caseFormScript.js |
onCaptchaSuccess, captchaResolve, token extraction, grecaptcha refs |
caseFormWidget.js |
Captcha rendering in FormWidget, token handling in SubmissionMixin, ConnectedForm |
setupWizard.js |
Apex imports, tracked properties, 8 handler methods |
setupStatus.js |
CAPTCHA_TYPE_LABELS constant |
4. Re-enable UI toggles (search for v1 MVP):
| File | Change |
|---|---|
setupWizard.js |
Uncomment reCAPTCHA step in STEPS, renumber Complete back to '6' |
formDetail.js |
Change showCaptchaToggle to return true |
setupStatus.js |
Change showRecaptchaPanel to return true |
5. Restore permission set and tests:
| File | What to uncomment |
|---|---|
Web_to_Case_Admin.permissionset-meta.xml |
Enable_Captcha__c FLS entry |
CaseFormControllerTest.cls |
~16 test methods, 5 HTTP mock classes |
SetupWizardControllerTest.cls |
~25 reCAPTCHA test methods |
Search for v2: reCAPTCHA and v1 MVP across the codebase to find all change points.
Multi-file Upload:
- Allow multiple file attachments per submission
- Drag-and-drop file upload UI
- File type and size validation per file
Custom Field Types:
- Picklist fields with configurable options
- Date picker fields
- Checkbox fields
- Phone number formatting
Form Analytics:
- Track form views, submissions, abandonment
- Success/error rate metrics
- Time-to-submit analytics
When creating the managed/unlocked package for AppExchange, include the following metadata components:
| Component | API Name | Description |
|---|---|---|
| Custom Object | Form__c |
Form configuration |
| Custom Object | Form_Field__c |
Form field definitions |
| Custom Object | Error_Log__c |
Error logging |
| Custom Object | Rate_Limit_Counter__c |
Rate limit tracking (Phase 4) |
| Component | API Name | Description |
|---|---|---|
| Custom Setting (Hierarchy) | reCAPTCHA_Settings__c |
reCAPTCHA API keys and settings (Protected) |
| Field | API Name |
|---|---|
| Form Name | Form_Name__c |
| Title | Title__c |
| Description | Description__c |
| Success Message | Success_Message__c |
| Active | Active__c |
| Enable File Upload | Enable_File_Upload__c |
| Max File Size MB | Max_File_Size_MB__c |
| Enable Captcha | Enable_Captcha__c |
| Site Id | Site_Id__c |
| Allowed Domains | Allowed_Domains__c (Phase 4) |
| Default Case Values | Default_Case_Values__c - JSON defaults for hidden Case fields |
| Field | API Name |
|---|---|
| Form (Master-Detail) | Form__c |
| Field Label | Field_Label__c |
| Field Type | Field_Type__c |
| Case Field | Case_Field__c |
| Required | Required__c |
| Sort Order | Sort_Order__c |
| Field | API Name |
|---|---|
| Error Message | Error_Message__c |
| Stack Trace | Stack_Trace__c |
| Form Id | Form_Id__c |
| Timestamp | Timestamp__c |
| Field | API Name |
|---|---|
| Site Key | Site_Key__c |
| Secret Key | Secret_Key__c |
| Captcha Type | Captcha_Type__c |
| Score Threshold | Score_Threshold__c |
| Default Site Id | Default_Site_Id__c |
| Default Site Base Url | Default_Site_Base_Url__c |
| Field | API Name |
|---|---|
| Origin Key | Origin_Key__c |
| Origin Domain | Origin_Domain__c |
| Count | Count__c |
| Hour Bucket | Hour_Bucket__c |
| Class | Description |
|---|---|
CaseDefaultFieldConfig |
Shared allowlist of Case fields for default values |
CaseFormController |
Public form controller |
CaseFormControllerTest |
Test class |
ErrorLogger |
Error logging utility |
ErrorLoggerTest |
Test class |
FileAssemblyQueueable |
Async file chunk assembly for files >2MB (up to 4MB) |
FileAssemblyQueueableTest |
Test class |
FormAdminController |
Form Manager admin controller |
FormAdminControllerTest |
Test class |
SetupWizardController |
Setup Wizard controller |
SetupWizardControllerTest |
Test class |
WebToCaseRestAPI |
REST API for embed widget (Phase 4) |
WebToCaseNonceService |
Nonce management for security (Phase 4) |
WebToCaseRateLimiter |
Rate limiting logic (Phase 4) |
WebToCaseRestAPITest |
Test class (Phase 4) |
| Channel | API Name | Description |
|---|---|---|
| Setup Status Refresh | SetupStatusRefresh |
Notifies setupStatus to refresh after wizard completes |
| Component | Description |
|---|---|
formAdminApp |
Form Manager main container |
formDetail |
Form editor with field management |
setupStatus |
Configuration Status Dashboard |
setupWizard |
Post-install setup wizard |
| Page | Description |
|---|---|
CaseFormPage |
Public form page |
| Resource | Description |
|---|---|
caseFormStyles |
Form CSS styling |
caseFormScript |
Form JavaScript (validation, compression, submission) |
imageCompression |
browser-image-compression library for client-side image optimization |
caseFormWidget |
Embeddable widget script for external websites (Phase 4) |
| App | API Name |
|---|---|
| Web-to-Case Forms | Web_to_Case_Forms |
| Page | API Name |
|---|---|
| Form Manager | Form_Manager |
| Setup Wizard | Setup_Wizard |
| Tab | API Name |
|---|---|
| Form Manager | Form_Manager |
| Setup Wizard | Setup_Wizard |
| Permission Set | API Name |
|---|---|
| Web-to-Case Admin | Web_to_Case_Admin |
Google_reCAPTCHAwas deleted in v0.8.2 to eliminate the "Approve Third-Party Access" install prompt. Re-create for v2 reCAPTCHA re-enablement.
| Partition | API Name | Description |
|---|---|---|
| WebToCase | WebToCase |
Stores one-time nonces for replay attack prevention (15-min TTL) |
The following metadata types are org-specific and should NOT be included in the managed package. Customers configure their own via Salesforce Setup:
- CORS Whitelist Origins — customers add their own domains
- CSP Trusted Sites — customers add their own domains
- Custom Sites — customers create their own Site
These are excluded via .forceignore.
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>Form__c</members>
<members>Form_Field__c</members>
<members>Error_Log__c</members>
<members>Rate_Limit_Counter__c</members>
<name>CustomObject</name>
</types>
<types>
<members>reCAPTCHA_Settings__c</members>
<name>CustomObject</name>
</types>
<types>
<members>CaseDefaultFieldConfig</members>
<members>CaseFormController</members>
<members>CaseFormControllerTest</members>
<members>ErrorLogger</members>
<members>ErrorLoggerTest</members>
<members>FileAssemblyQueueable</members>
<members>FileAssemblyQueueableTest</members>
<members>FormAdminController</members>
<members>FormAdminControllerTest</members>
<members>SetupWizardController</members>
<members>SetupWizardControllerTest</members>
<members>WebToCaseRestAPI</members>
<members>WebToCaseNonceService</members>
<members>WebToCaseRateLimiter</members>
<members>WebToCaseRestAPITest</members>
<name>ApexClass</name>
</types>
<types>
<members>formAdminApp</members>
<members>formDetail</members>
<members>setupStatus</members>
<members>setupWizard</members>
<name>LightningComponentBundle</name>
</types>
<types>
<members>CaseFormPage</members>
<name>ApexPage</name>
</types>
<types>
<members>caseFormStyles</members>
<members>caseFormScript</members>
<members>imageCompression</members>
<members>caseFormWidget</members>
<name>StaticResource</name>
</types>
<types>
<members>Web_to_Case_Forms</members>
<name>CustomApplication</name>
</types>
<types>
<members>Form_Manager</members>
<members>Setup_Wizard</members>
<name>FlexiPage</name>
</types>
<types>
<members>Form_Manager</members>
<members>Setup_Wizard</members>
<name>CustomTab</name>
</types>
<types>
<members>Web_to_Case_Admin</members>
<name>PermissionSet</name>
</types>
<!-- Remote Site Setting removed in v0.8.2: Google_reCAPTCHA (re-add for v2) -->
<types>
<members>SetupStatusRefresh</members>
<name>LightningMessageChannel</name>
</types>
<types>
<members>WebToCase</members>
<name>PlatformCachePartition</name>
</types>
<!-- CORS, CSP, and Sites are org-specific — excluded from package -->
<version>59.0</version>
</Package>-
Custom Setting:
reCAPTCHA_Settings__cis a Protected Hierarchy Custom Setting. The records themselves (API keys) are NOT included - users configure these post-install via Setup Wizard. -
Remote Site Setting:
Google_reCAPTCHAwas deleted in v0.8.2 to eliminate the "Approve Third-Party Access" install prompt. Re-create when re-enabling reCAPTCHA for v2. -
Permission Set:
Web_to_Case_Admingrants access to all admin functionality. Assign to users who need to manage forms. -
Post-Install Configuration Required:
- Create/select a Salesforce Site
- Configure Guest User permissions (via Setup Wizard)
- Grant Apex class and VF page access to Guest User profile
- (v2 only) Add reCAPTCHA API keys via Setup Wizard
-
Test Coverage: All Apex classes have >75% code coverage. 257 tests passing, 64% org-wide coverage.
-
Platform Cache: The
WebToCasecache partition is required for nonce-based replay attack prevention. The org must have Platform Cache allocated (at least session cache). -
CORS, CSP & Sites: Org-specific metadata (CORS origins, CSP trusted sites, Site config) has been removed from the project and excluded via
.forceignore. Customers configure their own domains post-install. -
Site Metadata: Customers using iframe embed mode need
AllowAllFramingon their Site (documented in post-install Setup Wizard).