Implement automated customer self-registration for Salesforce B2B Commerce storefronts using a custom Lightning Web Component and an Apex controller that orchestrates user provisioning, account creation, buyer access, and permission assignment.
The self-registration solution consists of two primary components:
- Custom web component — Provides the user-facing registration interface with form validation, user feedback, and storefront integration.
- Custom Apex controller — Orchestrates backend operations including user provisioning, account creation, buyer access configuration, and permission management.
When to Use This Implementation
Use this implementation when you want to:
- Enable customers to register for B2B Commerce stores without administrator intervention.
- Support password-based user authentication.
- Maintain control over user provisioning and account creation workflows.
- Enforce data validation and business rules during registration.
- Automate buyer access setup and permission assignment.
- Streamline the customer onboarding experience.
Customize the Self-Registration Page
Customize the store’s registration page using custom Lightning Web Components (LWC) and Apex code. For a sample Apex class, see the CommerceSelfRegistrationController.cls class.
Before you begin:
-
Add the new UI component to your store’s self-registration page.
a. In the navigation sidebar, select a store from the Store dropdown.
b. Select Website Design, and then click Experience Builder.
c. In Experience Builder, go to the Self-registration page.
d. To view the properties, click the page.
e. Drag and drop the component on the page.
f. Configure the component properties.
g. Publish your store.
-
Grant
CommerceSelfRegistrationController.clsaccess to guest users.
Note: Grant guest users access only to the Apex classes required for self-registration. Don't grant access to unrelated classes or classes that expose sensitive operations.
Don’t grant access to unrelated classes or classes that expose sensitive operations.
a. In the navigation sidebar, select a store from the Store dropdown.
b. Select Website Design, and then click Experience Builder.
c. In Experience Builder, go to Settings, and then click General.
d. Click the Guest User Profile.
e. In the Enabled Apex Class Access section, click Edit.
f. Add the Apex class to the list of enabled classes, and save your changes.
Sample Implementation of CommerceSelfRegistrationController
The CommerceSelfRegistrationController class is the core backend component that handles all server-side operations for customer self-registration.
The following sample shows a CommerceSelfRegistrationController class implementation.
/**
* Self-registration controller for Experience Cloud with B2B Commerce integration
*/
global without sharing class CommerceSelfRegistrationController {
private static final String LOG_PREFIX = '[CommerceSelfReg]';
public class RegistrationException extends Exception {}
global class RegistrationResult {
@AuraEnabled global Boolean success;
@AuraEnabled global String message;
@AuraEnabled global String redirectUrl;
}
/**
* Register a new user with BuyerAccount and auto-login
* Returns: RegistrationResult with success status, message, and redirect URL
*/
@AuraEnabled(cacheable=false)
global static RegistrationResult registerUserAndRedirectForLogin(
String firstName,
String lastName,
String email,
String phoneNumber,
String password,
String confirmPassword,
String startUrl
) {
RegistrationResult result = new RegistrationResult();
result.success = false;
Savepoint sp = Database.setSavepoint();
try {
String validationError = validateRegistrationInput(lastName, email, password, confirmPassword);
if (validationError != null) {
result.message = validationError;
return result;
}
String permissionSetGroupId = getPermissionSetGroupIdFromNetwork();
if (String.isBlank(startUrl)) {
startUrl = '';
}
String accountId = createBusinessAccount(firstName, lastName);
String userId = createUser(firstName, lastName, email, phoneNumber, password, accountId);
createBuyerAccount(accountId);
// Add buyer to all default buyer groups from config
List<String> defaultBuyerGroupIds = getDefaultBuyerGroupsFromConfig();
if (!defaultBuyerGroupIds.isEmpty()) {
addBuyerToDefaultGroups(accountId, defaultBuyerGroupIds);
}
assignPermissionSetToUserAsync(userId, permissionSetGroupId);
performAutoLogin(email, password, startUrl, result);
} catch (Exception ex) {
Database.rollback(sp);
result.success = false;
result.message = 'Registration error: ' + ex.getMessage();
System.debug(LOG_PREFIX + ' Registration exception: ' + ex.getMessage());
System.debug(LOG_PREFIX + ' Stack trace: ' + ex.getStackTraceString());
}
return result;
}
/**
* Validate registration input fields
* Returns: Error message if validation fails, null if valid
*/
private static String validateRegistrationInput(String lastName, String email, String password, String confirmPassword) {
if (String.isBlank(lastName)) {
return 'Last name is required';
}
if (String.isBlank(email)) {
return 'Email is required';
}
if (String.isBlank(password)) {
return 'Password is required';
}
if (password != confirmPassword) {
return 'Passwords do not match';
}
return null;
}
/**
* Create external user using Site.createExternalUser()
* Returns: User ID
*/
private static String createUser(String firstName, String lastName, String email,
String phoneNumber, String password, String accountId) {
try {
User u = new User();
u.FirstName = firstName;
u.LastName = lastName;
u.Email = email;
u.Username = email;
u.CommunityNickname = generateNickname(firstName, lastName, email);
u.EmailEncodingKey = 'UTF-8';
u.LocaleSidKey = 'en_US';
u.LanguageLocaleKey = 'en_US';
u.TimeZoneSidKey = 'America/Los_Angeles';
u.Alias = generateAlias(firstName, lastName);
if (!String.isBlank(phoneNumber)) {
u.Phone = phoneNumber;
}
String userId = Site.createExternalUser(u, accountId, password);
if (userId == null) {
throw new RegistrationException('Site.createExternalUser returned null - user creation failed');
}
System.debug(LOG_PREFIX + ' Created external user: ' + userId);
return userId;
} catch (Exception ex) {
System.debug(LOG_PREFIX + ' Error creating user: ' + ex.getMessage());
System.debug(LOG_PREFIX + ' Stack trace: ' + ex.getStackTraceString());
throw ex;
}
}
/**
* This a sample implementation and customer's need to provider their own implementation
* Generate unique community nickname
* Returns: Nickname string (max 40 chars)
*/
private static String generateNickname(String firstName, String lastName, String email) {
String nickname = (firstName != null ? firstName : '') + (lastName != null ? lastName : '');
if (String.isBlank(nickname)) {
nickname = email.substringBefore('@');
}
nickname = nickname.replaceAll('[^a-zA-Z0-9]', '');
nickname = nickname + String.valueOf(System.now().getTime()).substring(7);
return nickname.substring(0, Math.min(nickname.length(), 40));
}
/**
* This a sample implementation and customer's need to provider their own implementation
* Generate user alias.
* Returns: Alias string (max 8 chars)
*/
private static String generateAlias(String firstName, String lastName) {
String alias = '';
if (firstName != null) alias += firstName.substring(0, Math.min(firstName.length(), 4));
if (lastName != null) alias += lastName.substring(0, Math.min(lastName.length(), 4));
if (String.isBlank(alias)) alias = 'user';
return alias.substring(0, Math.min(alias.length(), 8));
}
/**
* Perform auto-login after successful registration and update result object
*/
private static void performAutoLogin(String email, String password, String startUrl, RegistrationResult result) {
try {
PageReference loginResult = Site.login(email, password, startUrl);
if (loginResult != null) {
result.redirectUrl = loginResult.getUrl();
result.success = true;
result.message = 'Registration and login successful!';
System.debug(LOG_PREFIX + ' Auto-login successful, redirect URL: ' + result.redirectUrl);
} else {
result.success = true;
result.redirectUrl = null;
result.message = 'Registration successful but auto-login failed. Please login manually.';
System.debug(LOG_PREFIX + ' Auto-login failed - Site.login returned null');
}
} catch (Exception loginEx) {
result.success = true;
result.redirectUrl = null;
result.message = 'Registration successful but auto-login failed. Please login manually.';
System.debug(LOG_PREFIX + ' Auto-login exception: ' + loginEx.getMessage());
}
}
/**
* Create a business account to be associated with registering user
* Returns: Business account ID
*/
private static String createBusinessAccount(String firstName, String lastName) {
Account account = new Account();
account.Name = firstName + ' ' + lastName;
String recordTypeId = getDefaultAccountRecordTypeId();
if (String.isNotBlank(recordTypeId)) {
account.RecordTypeId = recordTypeId;
}
ID siteAdminId = getSiteAdminId();
if (siteAdminId != null) {
account.OwnerId = siteAdminId;
}
insert account;
System.debug(LOG_PREFIX + ' Created business account: ' + account.Id);
return account.Id;
}
/**
* Get default account record type ID from Commerce configuration
* Returns: Account record type ID (15 chars), or null
*/
private static String getDefaultAccountRecordTypeId() {
String networkId = Network.getNetworkId();
List<CommerceConfigRelatedRecord> configs = [
SELECT ConfigReferenceId
FROM CommerceConfigRelatedRecord
WHERE ContextId = :networkId
AND ConfigUseCase = 'SelfRegistration'
AND ConfigKey = 'AccountRecordType'
LIMIT 1
];
if (!configs.isEmpty() && String.isNotBlank(configs[0].ConfigReferenceId)) {
String recordTypeId = configs[0].ConfigReferenceId;
if (recordTypeId.length() == 18) {
recordTypeId = recordTypeId.substring(0, 15);
}
System.debug(LOG_PREFIX + ' Found account record type ID: ' + recordTypeId);
return recordTypeId;
}
return null;
}
/**
* Get site admin ID
* Returns: Site admin user ID as ID, or null
*/
private static ID getSiteAdminId() {
return Site.getAdminId();
}
/**
* Create BuyerAccount for Commerce integration
*/
private static void createBuyerAccount(String accountId) {
BuyerAccount buyerAccount = new BuyerAccount(
BuyerId = accountId,
Name = 'Buyer-' + accountId,
IsActive = true
);
insert buyerAccount;
System.debug(LOG_PREFIX + ' Created BuyerAccount: ' + buyerAccount.Id);
}
/**
* Add buyer to all default buyer groups
*/
private static void addBuyerToDefaultGroups(String accountId, List<String> defaultBuyerGroupIds) {
List<BuyerGroupMember> members = new List<BuyerGroupMember>();
for (String buyerGroupId : defaultBuyerGroupIds) {
if (String.isNotBlank(buyerGroupId)) {
members.add(new BuyerGroupMember(
BuyerId = accountId,
BuyerGroupId = buyerGroupId
));
}
}
if (!members.isEmpty()) {
insert members;
System.debug(LOG_PREFIX + ' Added account to ' + members.size() + ' default BuyerGroups');
}
}
/**
* Async wrapper to assign permission set (avoids Mixed DML)
*/
@future
private static void assignPermissionSetToUserAsync(String userId, String permissionSetGroupId) {
assignPermissionSetToUser(userId, permissionSetGroupId);
}
/**
* Assign permission set group to user
*/
private static void assignPermissionSetToUser(String userId, String permissionSetGroupId) {
PermissionSetAssignment psa = new PermissionSetAssignment(
AssigneeId = userId,
PermissionSetGroupId = permissionSetGroupId
);
insert psa;
System.debug(LOG_PREFIX + ' Assigned permission set group to user: ' + psa.Id);
}
/**
* Get permission set group ID from NetworkSelfRegistration configuration
* Returns: Permission set group ID
*/
private static String getPermissionSetGroupIdFromNetwork() {
String networkId = Network.getNetworkId();
if (String.isBlank(networkId)) {
System.debug(LOG_PREFIX + ' No network ID found');
throw new RegistrationException('No network ID found');
}
List<NetworkSelfRegistration> selfRegConfigs = [
SELECT PermissionSetGroupId
FROM NetworkSelfRegistration
WHERE NetworkId = :networkId
LIMIT 1
];
if (selfRegConfigs.isEmpty() || String.isBlank(selfRegConfigs[0].PermissionSetGroupId)) {
System.debug(LOG_PREFIX + ' No permission set group configured in NetworkSelfRegistration');
throw new RegistrationException('No permission set group configured in NetworkSelfRegistration');
}
String permSetGroupId = selfRegConfigs[0].PermissionSetGroupId;
System.debug(LOG_PREFIX + ' Found permission set group ID from NetworkSelfRegistration: ' + permSetGroupId);
return permSetGroupId;
}
/**
* Get default buyer group IDs from CommerceConfigRelatedRecord
* This follows Salesforce's official implementation pattern
* Returns: List of BuyerGroup IDs configured for self-registration
*/
private static List<String> getDefaultBuyerGroupsFromConfig() {
String networkId = Network.getNetworkId();
List<String> buyerGroupIds = new List<String>();
try {
List<CommerceConfigRelatedRecord> configs = [
SELECT ConfigReferenceId
FROM CommerceConfigRelatedRecord
WHERE ContextId = :networkId
AND ConfigUseCase = 'SelfRegistration'
AND ConfigKey = 'DefaultBuyerGroup'
];
for (CommerceConfigRelatedRecord config : configs) {
if (String.isNotBlank(config.ConfigReferenceId)) {
buyerGroupIds.add(config.ConfigReferenceId);
}
}
System.debug(LOG_PREFIX + ' Found ' + buyerGroupIds.size() + ' default buyer groups: ' + buyerGroupIds);
} catch (Exception e) {
System.debug(LOG_PREFIX + ' Error retrieving buyer groups: ' + e.getMessage());
throw new RegistrationException('Error retrieving buyer groups');
}
return buyerGroupIds;
}
}