About b2c-forms
A Salesforce B2C Commerce skill for developers building and validating forms with SFRA patterns. It covers XML definitions, server-side controller handling, ISML templates, localization, and submission safeguards.
Key features
- 01Define fields and validation in XML
- 02Handle forms in SFRA controllers
- 03Render forms with ISML templates
- 04Localize labels and error messages
- 05Apply CSRF and server-side validation
Use cases
- 01Create profile or contact forms
- 02Validate submitted form data
- 03Build AJAX form submissions
- 04Support localized form labels and errors
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ salesforcecommercecloud/ b2c-developer-tooling --skill b2c-formsAfter installing, type/b2c-formsin Claude Code to confirm it loaded.
Bundled filesevals/trigger-evals.jsonreferences/FORM-XML.md
Details
From the project READMEForms Skill
This skill guides you through creating forms with validation in Salesforce B2C Commerce using the SFRA patterns.
Overview
B2C Commerce forms consist of three parts:
- Form Definition - XML file defining fields, validation, and actions
- Controller Logic - Server-side form handling and processing
- Template - ISML template rendering the HTML form
File Location
Forms are defined in the cartridge's forms directory:
/my-cartridge
/cartridge
/forms
/default # Default locale
profile.xml
contact.xml
/de_DE # German-specific (optional)
address.xml
Form Definition (XML)
Basic Structure
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.demandware.com/xml/form/2008-04-19">
<field formid="email" label="form.email.label" type="string"
mandatory="true" max-length="50"
regexp="^[\w.%+-]+@[\w.-]+\.\w{2,6}$"
parse-error="form.email.invalid"/>
<field formid="password" label="form.password.label" type="string"
mandatory="true" min-length="8" max-length="255"
missing-error="form.password.required"/>
<field formid="rememberMe" label="form.remember.label" type="boolean"/>
<action formid="submit" valid-form="true"/>
<action formid="cancel" valid-form="false"/>
</form>
Field Types
| Type | Description | HTML Input |
|---|---|---|
string | Text input | <input type="text"> |
integer | Whole number | <input type="number"> |
number | Decimal number | <input type="number"> |
boolean | Checkbox | <input type="checkbox"> |
date | Date value | <input type="date"> |
Key Field Attributes
| Attribute | Purpose | Example |
|---|---|---|
formid | Field identifier (required) | formid="email" |
label | Resource key for label | label="form.email.label" |
type | Data type (required) | type="string" |
mandatory | Required field | mandatory="true" |
max-length | Max string length | max-length="100" |
min-length | Min string length | min-length="8" |
regexp | Validation pattern | regexp="^\d{5}$" |
Validation Error Messages
| Attribute | When Triggered |
|---|---|
missing-error | Mandatory field is empty |
parse-error | Value doesn't match regexp or type |
range-error | Value outside min/max range |
value-error | General validation failure |
See Form XML Reference for complete field attributes, groups, lists, and validation patterns.
Controller Logic (SFRA)
Rendering a Form
'use strict';
var server = require('server');
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');
server.get('Show',
csrfProtection.generateToken,
function (req, res, next) {
var form = server.forms.getForm('profile');
form.clear(); // Reset previous values
res.render('account/profile', {
profileForm: form
});
next();
}
);
module.exports = server.exports();
Processing Form Submission
server.post('Submit',
server.middleware.https,
csrfProtection.validateAjaxRequest,
function (req, res, next) {
var form = server.forms.getForm('profile');
if (!form.valid) {
res.json({
success: false,
fields: getFormErrors(form)
});
return next();
}
// Access form values
var email = form.email.value;
var firstName = form.firstName.value;
// Process and save data
this.on('route:BeforeComplete', function () {
var Transaction = require('dw/system/Transaction');
Transaction.wrap(function () {
customer.profile.email = email;
customer.profile.firstName = firstName;
});
});
res.json({ success: true });
next();
}
);
function getFormErrors(form) {
var errors = {};
Object.keys(form).forEach(function (key) {
if (form[key] && form[key].error) {
errors[key] = form[key].error;
}
});
return errors;
}
Prepopulating Forms
server.get('Edit', function (req, res, next) {
var form = server.forms.getForm('profile');
form.clear();
var profile = req.currentCustomer.profile;
form.firstName.value = profile.firstName;
form.lastName.value = profile.lastName;
form.email.value = profile.email;
res.render('account/editProfile', { profileForm: form });
next();
});
Template (ISML)
Basic Form Template
<form action="${pdict.actionUrl}" method="POST" name="profile-form"
class="form-horizontal" data-action="${URLUtils.url('Profile-Submit')}">
<!-- CSRF Token -->
<input type="hidden" name="${pdict.csrf.tokenName}" value="${pdict.csrf.token}"/>
<div class="form-group ${pdict.profileForm.email.mandatory ? 'required' : ''}">
<label for="email" class="form-control-label">
${Resource.msg('form.email.label', 'forms', null)}
</label>
<input type="email"
id="email"
name="email"
class="form-control ${pdict.profileForm.email.error ? 'is-invalid' : ''}"
value="${pdict.profileForm.email.value || ''}"
<isif condition="${pdict.profileForm.email.mandatory}">required</isif>
maxlength="${pdict.profileForm.email.maxLength || 50}"/>
<isif condition="${pdict.profileForm.email.error}">
<div class="invalid-feedback">${pdict.profileForm.email.error}</div>
</isif>
</div>
<button type="submit" class="btn btn-primary">
${Resource.msg('button.submit', 'forms', null)}
</button>
</form>
Localization
Form labels and errors use resource bundles:
forms.properties:
form.email.label=Email Address
form.email.required=Email is required
form.email.invalid=Please enter a valid email address
form.password.label=Password
button.submit=Submit
forms_de_DE.properties:
form.email.label=E-Mail-Adresse
form.email.required=E-Mail ist erforderlich
Best Practices
- Always use CSRF protection for form submissions
- Clear forms before displaying to reset state
- Use resource keys for labels and errors (localization)
- Validate server-side even with client-side validation
- Use
route:BeforeCompletefor database operations - Return JSON for AJAX form submissions
Detailed Reference
For comprehensive form patterns:
- Form XML Reference - Complete XML schema, validation patterns, and examples