Problem: Currently we have no standard way to populate default values while cloning records in salesforce. For eg. If I want to clone a Case record, I have a requirement to set Status field as Open on UI while cloning.
Approach: We will be using create new record page feature of lwc to implement this feature. Lets dive dive into the steps-
1.Create a metadata where we will define which fields to populate

2. Now write a method to populate Default-
/*populate defaultValues While Cloning Record*/
private static void populateDefaultValues(sObject newSobj, String objectAPIName) {
String recordTypeId;
String strRecordTypeName = '';
try {
recordTypeId = String.valueOf(newSobj.get('RecordTypeId'));
strRecordTypeName = Schema.getGlobalDescribe().get(objectAPIName).getDescribe().getRecordTypeInfosById().get(recordTypeId).getDeveloperName();
} catch(Exception e) {
//if object don't have record type, you will get an error and I don't want to break the flow thats why added this piece of code inside try catch.
system.debug(e.getMessage());
}
//query metadata to populate data set in metadata based on object name
list<Custom_Clone_Default__mdt> lstMdtRec = [SELECT Id, Label, Field_Value__c, Object_Name__c, Record_Type_Name__c
FROM Custom_Clone_Default__mdt WHERE Object_Name__c = :objectAPIName
AND Record_Type_Name__c =:strRecordTypeName];
for (Custom_Clone_Default__mdt oMetaData : lstMdtRec){
Schema.DescribeFieldResult dfr = fieldMap.get(oMetaData.Label).getDescribe();
Schema.DisplayType t = dfr.getType();
if(oMetaData.Field_Value__c == 'CURRENTUSER'){
newSobj.put(oMetaData.Label, userinfo.getUserId());
}else{
if(t == DisplayType.Boolean){
newSobj.put(oMetaData.Label, !String.isBlank(oMetaData.Field_Value__c) ? boolean.valueOf(oMetaData.Field_Value__c) : false);
}else if(t == DisplayType.Date){
newSobj.put(oMetaData.Label, !String.isBlank(oMetaData.Field_Value__c) ? date.valueOf(oMetaData.Field_Value__c) : null);
} else if(t == DisplayType.DateTime){
newSobj.put(oMetaData.Label, !String.isBlank(oMetaData.Field_Value__c)? datetime.valueOf(oMetaData.Field_Value__c) : null);
}else{
newSobj.put(oMetaData.Label, oMetaData.Field_Value__c);
}
}
}
}
3. Write a method to fetch current record with all unique editable fields except createdbyid, lastmodifedid, lastmodifieddate and createddate field and make fields as default using method ‘populateDefaultValues’
@AuraEnabled
public static sObject getClonedValues(Id recordId, String objectAPIName) {
Map<String, Schema.SobjectType> schemaMap = Schema.getGlobalDescribe();
fieldMap = schemaMap.get(objectAPIName).getDescribe().fields.getMap();
String soqlQuery = 'Select ';
Schema.DescribeFieldResult fieldResult;
Set<String> ignoreFields = new Set<String>{'createdbyid', 'lastmodifiedbyid', 'lastmodifieddate', 'createddate'};
for(String s : fieldMap.keySet()) {
if(!ignoreFields.contains(s)) {
fieldResult = fieldMap.get(s).getDescribe();
if(fieldResult.isCreateable() && !fieldResult.isUnique()) {
soqlQuery += s + ',';
}
}
}
soqlQuery = soqlQuery.removeEnd(',');
soqlQuery += ' FROM ' + objectAPIName + ' WHERE ID = \'' + recordId + '\'';
Sobject record = Database.query(soqlQuery);
populateDefaultValues(record, objectAPIName);
try {
return record;
} catch(Exception e) {
return null;
}
}
You can refer my blog to check how we can get record type Id, developer name and label using apex.
Now write a lwc component, which will use ‘getClonedValues’ method to create map which we will pass inside create new page as shown below-
import { LightningElement, api, wire } from 'lwc';
import { encodeDefaultFieldValues } from 'lightning/pageReferenceUtils';
import { NavigationMixin } from 'lightning/navigation';
import getClonedValues from '@salesforce/apex/CloneSingleRecord.getClonedValues';
export default class Custom_clone_lwc extends NavigationMixin(LightningElement) {
@api recordId;
@api objectApiName;
recordTypeId;
@api async invoke() {
this.fetchClonedValues(this.recordId, this.objectApiName);
}
fetchClonedValues(recId, objectName) {
getClonedValues({ recordId: recId, objectAPIName : objectName }).then(data => {
const map = new Map(Object.entries(data));
let keys = Object.keys(data);
let finalObj = {};
for (let i = 0; i < keys.length; i++) {
var skey = keys[i];
var val = map.get(keys[i]);
if(val) {
finalObj[skey] = val;
}
}
if(finalObj['RecordTypeId']) {
this.recordTypeId = finalObj['RecordTypeId'];
}
console.log('finalObj>>'+ JSON.stringify(finalObj));
const defaultValues = encodeDefaultFieldValues(finalObj);
let self = this;
this[NavigationMixin.Navigate]({
type: 'standard__objectPage',
attributes: {
objectApiName: objectName,
actionName: 'new'
},
state: {
defaultFieldValues: defaultValues,
recordTypeId: self.recordTypeId
}
});
}).catch(error => {
console.log('error>> ' + JSON.stringify(error));
});
}
}
Make this component compatible with quick action using xml as shown below
<?xml version="1.0"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>56</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordAction</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordAction" >
<actionType>Action</actionType>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Check out my below blog post also to find out how we can fetch record type information in apex using various ways-