Agent Skill · Salesforce Commerce Cloud

b2c-forms

Build forms with validation in B2C Commerce using SFRA patterns. Use this skill whenever the user needs to create a storefront form (checkout, registration, profile edit, address, contact), define form fields in XML, handle form submission in a controller, add field validation rules, or render forms in ISML templates. Also use when they mention form XML definitions, server.forms.getForm, form groups/actions/lists, or CSRF protection on form posts — even if they just say "I need a registration form" or "add validation to checkout".

Provider: Salesforce Commerce Cloud Path in repo: skills/b2c/skills/b2c-forms/SKILL.md

Skill body

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
            /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

  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:

Work with this as data

Every skill here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for agent skills

4 MCP tools reach this
  • find_skillsBrowse and filter every skill in the catalog.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools

Call it yourself

curl for this page
This skill
curl "https://apis.io/api/v1/skills/b2c-forms"
All agent skills
curl "https://apis.io/api/v1/skills?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no email required.

A second provider on the same verified email joins the account you already have.