Back to list
SalesforceCommerceCloud

b2c-forms

by SalesforceCommerceCloud

Salesforce Agentforce Commerce B2C DX Tools

4🍴 2📅 Jan 24, 2026

SKILL.md


name: b2c-forms description: Create forms with validation in SFRA patterns for B2C Commerce. Use when building checkout forms, account forms, or any form with field definitions, validation rules, and error handling. Covers form XML definitions, server-side validation, and template rendering.

Forms 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:

  1. Form Definition - XML file defining fields, validation, and actions
  2. Controller Logic - Server-side form handling and processing
  3. 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
                address.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

TypeDescriptionHTML Input
stringText input<input type="text">
integerWhole number<input type="number">
numberDecimal number<input type="number">
booleanCheckbox<input type="checkbox">
dateDate value<input type="date">

Field Attributes

AttributePurposeExample
formidField identifier (required)formid="email"
labelResource key for labellabel="form.email.label"
typeData type (required)type="string"
mandatoryRequired fieldmandatory="true"
max-lengthMax string lengthmax-length="100"
min-lengthMin string lengthmin-length="8"
regexpValidation patternregexp="^\d{5}$"
defaultDefault valuedefault="US"
minMin numeric valuemin="0"
maxMax numeric valuemax="100"
formatDate formatformat="yyyy-MM-dd"

Validation Error Messages

<field formid="email" type="string" mandatory="true"
       regexp="^[\w.%+-]+@[\w.-]+\.\w{2,6}$"
       missing-error="form.email.required"
       parse-error="form.email.invalid"
       range-error="form.email.toolong"
       value-error="form.email.error"/>
AttributeWhen Triggered
missing-errorMandatory field is empty
parse-errorValue doesn't match regexp or type
range-errorValue outside min/max range
value-errorGeneral validation failure

Grouped Fields

<group formid="address">
    <field formid="street" label="form.address.street" type="string" mandatory="true"/>
    <field formid="city" label="form.address.city" type="string" mandatory="true"/>
    <field formid="postalCode" label="form.address.zip" type="string" mandatory="true"/>
</group>

Access in controller: form.address.street.value

Actions

<!-- Only validate if valid-form="true" -->
<action formid="submit" valid-form="true"/>

<!-- Skip validation -->
<action formid="cancel" valid-form="false"/>
<action formid="back" valid-form="false"/>

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');

        // Check validation
        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();
    }
);

// Helper to extract form errors
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();

    // Prepopulate from existing data
    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();
});

Accessing Raw Form Data

server.post('Submit', function (req, res, next) {
    // Access raw POST data directly
    var email = req.form.email;
    var firstName = req.form.firstName;

    // Useful when not using form definitions
    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>

Form with Groups

<fieldset>
    <legend>${Resource.msg('form.address.title', 'forms', null)}</legend>

    <div class="form-group">
        <label for="street">${Resource.msg('form.address.street', 'forms', null)}</label>
        <input type="text" id="street" name="address_street"
               value="${pdict.addressForm.address.street.value || ''}"/>
    </div>

    <div class="form-group">
        <label for="city">${Resource.msg('form.address.city', 'forms', null)}</label>
        <input type="text" id="city" name="address_city"
               value="${pdict.addressForm.address.city.value || ''}"/>
    </div>
</fieldset>

AJAX Form Submission

<script>
$('form[name="profile-form"]').on('submit', function(e) {
    e.preventDefault();
    var $form = $(this);

    $.ajax({
        url: $form.data('action'),
        type: 'POST',
        data: $form.serialize(),
        success: function(response) {
            if (response.success) {
                window.location.href = response.redirectUrl;
            } else {
                displayErrors(response.fields);
            }
        }
    });
});

function displayErrors(fields) {
    Object.keys(fields).forEach(function(field) {
        var $field = $('[name="' + field + '"]');
        $field.addClass('is-invalid');
        $field.siblings('.invalid-feedback').text(fields[field]);
    });
}
</script>

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
form.password.required=Password is required

button.submit=Submit
button.cancel=Cancel

forms_de_DE.properties:

form.email.label=E-Mail-Adresse
form.email.required=E-Mail ist erforderlich

Custom Validation

In Form Definition

<field formid="password" type="string"
       validation="${require('*/cartridge/scripts/validation').validatePassword(formfield)}"
       range-error="form.password.weak"/>

Validation Script

// scripts/validation.js
exports.validatePassword = function (formfield) {
    var value = formfield.value;
    if (value && value.length < 8) {
        return false;  // Triggers range-error
    }
    if (!/[A-Z]/.test(value) || !/[0-9]/.test(value)) {
        return false;
    }
    return true;
};

Best Practices

  1. Always use CSRF protection for form submissions
  2. Clear forms before displaying to reset state
  3. Use resource keys for labels and errors (localization)
  4. Validate server-side even with client-side validation
  5. Use route:BeforeComplete for database operations
  6. Return JSON for AJAX form submissions

Detailed Reference

For comprehensive form patterns:

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon