By Jean-Baptiste Brossard

1 – Introduction

Cloning in Salesforce can be a huge time saver for users. When records contain many fields, it’s often easier and quicker for users to clone an existing record and change a few fields rather than creating a new one from scratch.
A standard Salesforce clone button is available for most objects, but we found it’s usually not enough for most of our clients’ requirements.
What most of our clients want is a simple way of cloning records:

  • With all its fields and not only the ones displayed on the page
  • Optionally also cloning its related records (i.e. the records appearing in the related lists)
  • Optionally also add cloning business rules (e.g. can’t clone closed accounts, clear certain fields when cloned, etc)

To achieve this, we decided to build a generic cloning framework that we could reuse with minimum changes for each type of objects to clone (e.g. Account, Contact, Custom objects, etc).
The cloning framework consists of a set of generic Apex classes and methods that perform the cloning of any object type.
In this article I’m going to show you how we’ve done it.
I assume that you are already familiar with Salesforce and Apex coding.

2 – Cloning Framework Overview

The cloning framework is basically a set of apex classes and methods that perform generic cloning operations. These classes can be instantiated and the methods called from various places to clone records.
The most common place to call the cloning framework methods is from a record detail page.
The user workflow is as follow:

So in in a typical case scenario, you’d need to:

  • Create a custom clone button and add it on your detail page
  • Create a custom clone page to select cloning options (e.g. which related records to clone)
  • Create a custom controller for the above page to call the cloning methods

Note that this is the typical scenario, but you don’t actually need a custom page and controller to use the cloning framework, you can call the cloning methods from anywhere (e.g. batch, schedule, etc).

3 – What’s in the Cloning Framework?


3.1 – Core Class: CloneHelperBase

Let’s look at what needs to take place when cloning a record.  It’s actually pretty simple.
To clone a record, we need to:

  1. Create a copy of the original record with the same values for all its fields
  2. Create a copy of all records related to the original record, and link them to the cloned record (i.e. the copy of the original record).
    Optionally, we might also want to select which type of related records are cloned.

To create a copy of the original record, we’re lucky that the sObject class exposes a method called clone that does exactly what we need. With the sObject.clone method, we can clone any record we want without having to manually go through all its fields.
To create a copy of all records related to the original record (aka child records), we first need to locate all child records (i.e. run an SOQL query). Once we’ve done that, we can simply clone each child record with the sObject.clone method like we did for the parent record. After a child record is cloned, it must be re-parented to point to the cloned parent record rather than the original record.
So we pretty much have the core of our cloning framework right there.
A virtual class called CloneHelperBase that contains the following methods:

  • CloneOriginalRecord:
    To clone the original record i.e. call the sObject.clone
  • CloneChildRecords:
    To clone all child records, i.e. query all child records, then clone and re-parent them
  • CloneOriginalRecordAndChildren:
    A public method to perform the whole cloning operation (original record + child records) by calling the 2 methods above.


3.2 – Extended Classes for Each Type of Object to Clone

The class CloneHelperBase is a generic class that works with any type of object to clone.
It’s a virtual class that must be extended before it can be instantiated. So for each type of object we want to clone (e.g. Account, Contact, Custom objects, etc), we need to create a new class that extends CloneHelperBase. The extended class will contain information specific to the object type to clone.
The information we need about the object to clone can be for instance:

  • The Salesforce URL suffix to access the object (e.g. ‘/500/o’ for cases)
  • The concrete ObjectType of the record to clone (e.g. Schema.Case.SObjectType for cases)
  • The list of child record types we want to clone
  • For each type of child record we want to clone, we also need additional information, e.g. the object type name (Event, Attachment, etc), the parentId field name (used for the SOQL query), an additional filter if required for the SOQL query, etc

The additional information needed for each type of child record to clone is stored in a class called ChildRecCloneInfo (it’s a local class of the CloneHelperBase class). The CloneHelperBase stores a list of ChildRecCloneInfo in a map member called ChildRecCloneInfoMap.
So for instance to clone Cases, we need to create the following extended class:

[et_pb_dmb_code_snippet admin_label=”Code Snippet” style=”docco” linenums=”off” usetabwidth=”off” use_border_color=”off” border_color=”#ffffff” border_style=”solid” saved_tabs=”all” body_font_size=”13px” body_line_height=”1.4em”]Ly9IZWxwZXIgY2xhc3MgdG8gaGFuZGxlIGNsb25pbmcgb2YgQ2FzZXMKcHVibGljIHdpdGggc2hhcmluZyBjbGFzcyBDbG9uZUhlbHBlckNhc2UgZXh0ZW5kcyBDbG9uZUhlbHBlckJhc2UKewoJcHVibGljIENsb25lSGVscGVyQ2FzZSgpCgl7CgkJQ29uY3JldGVPYmplY3RUeXBlID0gU2NoZW1hLkNhc2UuU09iamVjdFR5cGU7CgkJT2JqZWN0VHlwZUhvbWVQYWdlTmFtZSA9ICcvNTAwL28nOwoKCQlJc1N0YW5kYXJkQ0xvbmUgPSB0cnVlOwoKCQkvL1BvcHVsYXRlIGxpc3Qgb2YgQ2hpbGRSZWNDbG9uZUluZm8gd2l0aCBhbGwgY2hpbGQgcmVjb3JkcyBpbmZvIGZvciBjYXNlcwoJCS8vQWxsIENoaWxkUmVjQ2xvbmVJbmZvIGFyZSBpbml0aWFsaXNlZCBhcyAiZG9uJ3QgY2xvbmUiIGFuZCB3aWxsIGJlIGVuYWJsZWQgCgkJLy9mb3IgY2xvbmluZyBieSB0aGUgY2xpZW50IG9mIENsb25lSGVscGVyQ2FzZSBjbGFzcyBpZiByZXF1aXJlZAoKCQkvL0FkZCBjaGlsZCByZWNvcmQgaW5mbyBmb3IgRW1haWxzCgkJQ2hpbGRSZWNDbG9uZUluZm9NYXAucHV0KFNjaGVtYS5FbWFpbE1lc3NhZ2UuU09iamVjdFR5cGUsIG5ldyBDbG9uZUhlbHBlckJhc2UuQ2hpbGRSZWNDbG9uZUluZm8oZmFsc2UsIFNjaGVtYS5FbWFpbE1lc3NhZ2UuU09iamVjdFR5cGUsICdQYXJlbnRJZCcsICcnKSk7CgoJCS8vQWRkIGNoaWxkIHJlY29yZCBpbmZvIGZvciBDYXNlIENvbW1lbnRzCgkJQ2hpbGRSZWNDbG9uZUluZm9NYXAucHV0KFNjaGVtYS5DYXNlQ29tbWVudC5TT2JqZWN0VHlwZSwgbmV3IENsb25lSGVscGVyQmFzZS5DaGlsZFJlY0Nsb25lSW5mbyhmYWxzZSwgU2NoZW1hLkNhc2VDb21tZW50LlNPYmplY3RUeXBlLCAnUGFyZW50SWQnLCAnJykpOwoKCQkvL0FkZCBjaGlsZCByZWNvcmQgaW5mbyBmb3IgQ2FzZSBUYXNrcwoJCUNoaWxkUmVjQ2xvbmVJbmZvTWFwLnB1dChTY2hlbWEuVGFzay5TT2JqZWN0VHlwZSwgbmV3IENsb25lSGVscGVyQmFzZS5DaGlsZFJlY0Nsb25lSW5mbyhmYWxzZSwgU2NoZW1hLlRhc2suU09iamVjdFR5cGUsICdXaGF0SWQnLCAnJykpOwoKCQkvL0FkZCBjaGlsZCByZWNvcmQgaW5mbyBmb3IgQ2FzZSBFdmVudHMKCQlDaGlsZFJlY0Nsb25lSW5mb01hcC5wdXQoU2NoZW1hLkV2ZW50LlNPYmplY3RUeXBlLCBuZXcgQ2xvbmVIZWxwZXJCYXNlLkNoaWxkUmVjQ2xvbmVJbmZvKGZhbHNlLCBTY2hlbWEuRXZlbnQuU09iamVjdFR5cGUsICdXaGF0SWQnLCAnJykpOwoKCQkvL0FkZCBjaGlsZCByZWNvcmQgaW5mbyBmb3IgQ2FzZSBBcnRpY2xlcwoJCUNoaWxkUmVjQ2xvbmVJbmZvTWFwLnB1dChTY2hlbWEuQ2FzZUFydGljbGUuU09iamVjdFR5cGUsIG5ldyBDbG9uZUhlbHBlckJhc2UuQ2hpbGRSZWNDbG9uZUluZm8oZmFsc2UsIFNjaGVtYS5DYXNlQXJ0aWNsZS5TT2JqZWN0VHlwZSwgJ0Nhc2VJZCcsICcnKSk7CgoJCS8vQWRkIGNoaWxkIHJlY29yZCBpbmZvIGZvciBDYXNlIEF0dGFjaG1lbnRzCgkJQ2hpbGRSZWNDbG9uZUluZm9NYXAucHV0KFNjaGVtYS5BdHRhY2htZW50LlNPYmplY3RUeXBlLCBuZXcgQ2xvbmVIZWxwZXJCYXNlLkNoaWxkUmVjQ2xvbmVJbmZvKGZhbHNlLCBTY2hlbWEuQXR0YWNobWVudC5TT2JqZWN0VHlwZSwgJ1BhcmVudElkJywgJycpKTsKCX0KfQo=[/et_pb_dmb_code_snippet]
Let’s look in details at how we specify child record information for case attachments.
[et_pb_dmb_code_snippet admin_label=”Code Snippet” style=”docco” linenums=”off” usetabwidth=”off” use_border_color=”off” border_color=”#ffffff” border_style=”solid” saved_tabs=”all” body_font_size=”13px” body_line_height=”1.4em”]Ly9BZGQgY2hpbGQgcmVjb3JkIGluZm8gZm9yIENhc2UgQXR0YWNobWVudHMKQ2hpbGRSZWNDbG9uZUluZm9NYXAucHV0KFNjaGVtYS5BdHRhY2htZW50LlNPYmplY3RUeXBlLAoJCW5ldyBDbG9uZUhlbHBlckJhc2UuQ2hpbGRSZWNDbG9uZUluZm8oZmFsc2UsIAkKCQkJU2NoZW1hLkF0dGFjaG1lbnQuU09iamVjdFR5cGUsCgkgCQknUGFyZW50SWQnLCAnJykpOwo=[/et_pb_dmb_code_snippet]
  1. ChildRecCloneInfoMap is a member of the base class (CloneHelperBase) that sores information about child record types. Here we’re adding information about Attachment child records on Case objects.
  2. The key in ChildRecCloneInfoMap is the sobject type of the child record. For attachments it’s Schema.Attachment.SObjectType
  3. The value in ChildRecCloneInfoMap is an instance of the CloneHelperBase.ChildRecCloneInfo class. We create it with the following parameters:
    • False: Initialise this type of child record (attachment) as not required to be cloned.
      This value can be changed before each clone operation (e.g. in a custom cloning page controller extension)
    • Attachment.SObjectType: same as the key, it’s the sobject type of the child record
    • ‘ParentId’: the field in the Attachment table that sores a reference to the parent record. We’ll need it to query the Attachment child records to clone.
    • ”: An optional additional where clause (e.g. if we wanted to only clone non private attachments, we could set this value to ‘IsPrivate = false’)


3.3 – Pre and Post Clone Customisations

A Pre and Post clone mechanism is available in the cloning framework to allow further customisations beyond the simple cloning.
The pre and post clone mechanism is implemented by pre and post clone virtual methods called during the cloning process (before and after the clone). The pre and post clone virtual methods are defined on the CloneHelperBase class. By default, they don’t do anything in the CloneHelperBase class.
If you want to customise the clone behaviour for a specific cloning class (e.g. CloneHelperCase as above), all you need to do is override the pre and/or post clone methods in your specific cloning class to perform the required operations.
There are 3 pre and post clone methods you can override:

  • PreCloneOriginalRec: This method is called before the original record is cloned. Here you could for instance check if a record qualifies for cloning according to specific business rules.
  • PostCloneOriginalRec_BeforeSave: This method is called after the original record has been cloned but before it’s saved in the database. Here you could for instance clear unique fields (as they would generate a duplicate error when saving) or set specific values (e.g. change the record owner to the user running the clone, apply specific clone business rules, etc).
  • PostCloneOriginalRec_AfterSave: This method is called after the original record has been cloned and after it’s been saved in the database.

Note: The pre and post clone mechanism is implemented only before and after cloning the original record (as opposed to its child records) but is could be easily extended to child records if needed.

3.4 – Main Method: CloneOriginalRecordAndChildren

CloneOriginalRecordAndChildren is the main method you need to call on the cloning framework to perform a clone.
Once your extended CloneHelperBase class has been created and initialised, you can invoke the CloneOriginalRecordAndChildren method on it to perform a clone.
The CloneOriginalRecordAndChildren method does the following:

  • Save the current database state in case a rollback is needed
  • Clone the original record by calling the CloneOriginalRecord method
  • Browse all child record types defined in ChildRecCloneInfoMap, and for each child one that requires cloning, clone all child records of this type

Below is the code for the CloneOriginalRecordAndChildren method:

[et_pb_dmb_code_snippet admin_label=”Code Snippet” style=”docco” linenums=”off” usetabwidth=”off” use_border_color=”off” border_color=”#ffffff” border_style=”solid” saved_tabs=”all” body_font_size=”13px” body_line_height=”1.4em”]cHVibGljIHZpcnR1YWwgQm9vbGVhbiBDbG9uZU9yaWdpbmFsUmVjb3JkQW5kQ2hpbGRyZW4oKQp7CgkvLzEuIEluaXRpYWxpc2F0aW9uIGFuZCBjaGVjayB0aGF0IE9yaWdpbmFsUmVjIGlzIHNldCBjb3JyZWN0bHkKCVJlc2V0TGFzdEVycm9yKCk7CglpZiAoT3JpZ2luYWxSZWMgPT0gbnVsbCkgcmV0dXJuIFNldEVycm9yKCdFUlJPUiBpbiBDbG9uZUhlbHBlckJhc2UuQ2xvbmVPcmlnaW5hbFJlY29yZEFuZENoaWxkcmVuJywgJ01lbWJlciBPcmlnaW5hbFJlYyBtdXN0IG5vdCBiZSBudWxsJyk7CgoJLy8yLiBHZXQgREIgU2F2ZSBQb2ludCBpbiBjYXNlIGEgcm9sbGJhY2sgaXMgcmVxdWlyZWQKCVN5c3RlbS5TYXZlUG9pbnQgc3AgPSBEYXRhYmFzZS5zZXRTYXZlUG9pbnQoKTsKCgkvLzMuIENsb25lIG9yaWdpbmFsIHJlY29yZAoJaWYgKCFDbG9uZU9yaWdpbmFsUmVjb3JkKCkpCgl7CgkJRGF0YWJhc2Uucm9sbGJhY2soc3ApOwoJCXJldHVybiBmYWxzZTsKCX0KCgkvL0lmIG5vIGNoaWxkIHJlY29yZCB0eXBlIGlzIHNldCBpbiBtYXAsIG5vdGhpbmcgdG8gY2xvbmUKCWlmIChDaGlsZFJlY0Nsb25lSW5mb01hcC5zaXplKCkgPD0gMCkgcmV0dXJuIHRydWU7CgoJU2V0PFNjaGVtYS5TT2JqZWN0VHlwZT4gY2hpbGRPYmplY3RUeXBlcyA9IENoaWxkUmVjQ2xvbmVJbmZvTWFwLmtleVNldCgpOwoJQ2hpbGRSZWNDbG9uZUluZm8gY3JDbG9uZUluZm87CgoJLy80LiBCcm93c2UgY2hpbGQgcmVjb3JkIHR5cGUgbWFwIGFuZCBjbG9uZSBjaGlsZCByZWNvcmRzIGZvciByZXF1aXJlZCB0eXBlcwoJZm9yIChTY2hlbWEuU09iamVjdFR5cGUgY2hpbGRPYmplY3RUeXBlIDogY2hpbGRPYmplY3RUeXBlcykKCXsKCQljckNsb25lSW5mbyA9IENoaWxkUmVjQ2xvbmVJbmZvTWFwLmdldChjaGlsZE9iamVjdFR5cGUpOwoKCQkvL0lnbm9yZSBjaGlsZCByZWNvcmRzIHRoYXQgZG9u4oCZdCBuZWVkIHRvIGJlIGNsb25lZAoJCWlmICghY3JDbG9uZUluZm8uTmVlZENsb25lKSBjb250aW51ZTsJCgoJCS8vQ2xvbmUgY2hpbGQgcmVjb3JkcyBvZiB0aGUgY3VycmVudCB0eXBlCgkJaWYgKCFDbG9uZUNoaWxkUmVjb3JkcyhjckNsb25lSW5mbykpCgkJewoJCQlEYXRhYmFzZS5yb2xsYmFjayhzcCk7CgkJCXJldHVybiBmYWxzZTsKCQl9Cgl9CgoJcmV0dXJuIHRydWU7Cn0K[/et_pb_dmb_code_snippet]
Description of the code above:

  1. Initialisation (i.e. reset the internal error message) and check that OriginalRec is set correctly.
    The OriginalRec member must point to the original record to be cloned. This is done by calling the LoadOriginalRecordFromDB method (defined in the CloneHelperBase class).
  2. Get a DB Save Point in case a rollback is required. If an error occurs at any point during the cloning process, the whole database transaction is rolled back.
  3. Clone the original record by calling the CloneOriginalRecord method (see 3.5 Core Method: CloneOriginalRecord)
  4. Browse child record type map and clone child records for required types.
    The ChildRecCloneInfoMap contains information on child record types. We browse this map, and for each child record type that requires cloning, we clone all child records of this type by calling the CloneChildRecords method (see 3.6 Core Method: CloneChildRecords).


3.5 – Core Method: CloneOriginalRecord

The CloneOriginalRecord is a private method of the CloneHelperBase class and is called from the main CloneOriginalRecordAndChildren method.
It does the following:

  • Call the Pre-Clone method
  • Clone the original record
  • Call the Post-Clone method

Below is the code for the CloneOriginalRecord method:

[et_pb_dmb_code_snippet admin_label=”Code Snippet” style=”docco” linenums=”off” usetabwidth=”off” use_border_color=”off” border_color=”#ffffff” border_style=”solid” saved_tabs=”all” body_font_size=”13px” body_line_height=”1.4em”]cHJvdGVjdGVkIHZpcnR1YWwgQm9vbGVhbiBDbG9uZU9yaWdpbmFsUmVjb3JkKCkKewoJLy8xLiBSZXNldCBzdGF0dXNlcyBmcm9tIGxhc3QgY2xvbmUgb3BlcmF0aW9uCglSZXNldExhc3RFcnJvcigpOwoJQ2xvbmVkUmVjID0gbnVsbDsKCgkvLzIuIENhbGwgdGhlIHZpcnR1YWwgUHJlQ2xvbmUgTWV0aG9kIGJlZm9yZSB0aGUgY2xvbmUKCWlmICghUHJlQ2xvbmVPcmlnaW5hbFJlYygpKSByZXR1cm4gZmFsc2U7CgoJLy8zLiBDbG9uZSBvcmlnaW5hbCByZWNvcmQgd2l0aCB0aGUgc3RhbmRhcmQgY2xvbmUgbWV0aG9kCglDbG9uZWRSZWMgPSBPcmlnaW5hbFJlYy5jbG9uZShmYWxzZSwgdHJ1ZSwgZmFsc2UsIGZhbHNlKTsKCglpZiAoQ2xvbmVkUmVjID09IG51bGwpIHRocm93IG5ldyBSRkV4Y2VwdGlvbignVW5rbm93biBlcnJvciBpbiBDbG9uZUhlbHBlckJhc2UuQ2xvbmVPcmlnaW5hbFJlY29yZC4gQ2xvbmVkUmVjIGlzIG51bGwgYWZ0ZXIgY2xvbmUuLi4nKTsKCgkvLzQuIENhbGwgdmlydHVhbCBQb3N0Q2xvbmUgTWV0aG9kIGFmdGVyIHRoZSBjbG9uZSAoYmVmb3JlIGNsb25lZCByZWNvcmQgaXMgc2F2ZWQpCglpZiAoIVBvc3RDbG9uZU9yaWdpbmFsUmVjX0JlZm9yZVNhdmUoKSkgcmV0dXJuIGZhbHNlOwoKCS8vNS4gU2F2ZSBjbG9uZWQgcmVjb3JkIGluIHRoZSBkYXRhYmFzZQoJdHJ5Cgl7CgkJaW5zZXJ0IENsb25lZFJlYzsKCQlpZiAoVGVzdC5pc1J1bm5pbmdUZXN0KCkgJiYgU2ltdWxhdGVFcnJvciA9PSAxKSAKCQkJdGhyb3cgbmV3IERNTEV4Y2VwdGlvbignU2ltdWxhdGUgRXJyb3IgMScpOwoJfQoJY2F0Y2ggKERNTEV4Y2VwdGlvbiBlKQoJewoJCS8vSWYgZXJyb3JzLCBzaG93IG1lc3NhZ2VzIHRvIHVzZXIgKHJvbGxiYWNrIHdpbGwgYmUgZG9uZSBpbiBjYWxsaW5nIG1ldGhvZCkKCQlDbG9uZWRSZWMgPSBudWxsOwoJCXJldHVybiBTZXRFcnJvcignRVhDRVBUSU9OIGluIENsb25lSGVscGVyQmFzZS5DbG9uZU9yaWdpbmFsUmVjb3JkJywgZSk7Cgl9CgoJLy82LiBDYWxsIHZpcnR1YWwgUG9zdENsb25lIE1ldGhvZCBhZnRlciB0aGUgY2xvbmUgKGFmdGVyIGNsb25lZCByZWNvcmQgaXMgc2F2ZWQpCglpZiAoIVBvc3RDbG9uZU9yaWdpbmFsUmVjX0FmdGVyU2F2ZSgpKSByZXR1cm4gZmFsc2U7CgoJcmV0dXJuIHRydWU7Cn0K[/et_pb_dmb_code_snippet]
Description of the code above:

  1. A bit of initialisation to reset the status, last error and ClonedRec member in case they were already set from a previous clone.
  2. Call the PreClone method before starting the clone (see 2.3 Pre and Post Clone Customisations)
  3. Clone the original record with the standard sObject.clone method.
    The 2 members OriginalRec and ClonedRec are defined on the CloneHelperBase class.
    – OriginalRec points to the Original record (the one being cloned) and is initialised in the LoadOriginalRecordFromDB method.
    – ClonedRec points to the Cloned record (i.e. the copy of the Original record) and is set here.
  4. Call PostClone method after the original record is cloned and before it’s saved (see 2.3 Pre and Post Clone Customisations).
  5. Save the cloned record in the database
    The save is done within a try/catch statement in case a runtime error occurs. In that case the calling method will roll back the whole transaction.
  6. Call PostClone method after the original record is cloned and saved (see 2.3 Pre and Post Clone Customisations).


3.6 – Core Method: CloneChildRecords

The CloneChildRecords is a private method of the CloneHelperBase class and is called from the main CloneOriginalRecordAndChildren method.
It does the following:

  • Clone all child records of a specified type related to original record
  • Re-parent all cloned child records so they relate to the cloned version of the original record
  • Save all cloned child records to the database

Below is the code for the CloneChildRecords method:

[et_pb_dmb_code_snippet admin_label=”Code Snippet” style=”docco” linenums=”off” usetabwidth=”off” use_border_color=”off” border_color=”#ffffff” border_style=”solid” saved_tabs=”all” body_font_size=”13px” body_line_height=”1.4em”]cHJvdGVjdGVkIEJvb2xlYW4gQ2xvbmVDaGlsZFJlY29yZHMoQ2hpbGRSZWNDbG9uZUluZm8gY3JDbG9uZUluZm8pCnsKCVJlc2V0TGFzdEVycm9yKCk7CgoJLy8xLiBHZXQgbGlzdCBvZiBjaGlsZCByZWNvcmRzIHJlbGF0ZWQgdG8gb3JpZ2luYWwgcmVjb3JkCglMaXN0PFNPYmplY3Q+IG9yaWdpbmFsQ2hpbGRyZW4gPSBHZXRDaGlsZFJlY29yZHMoT3JpZ2luYWxSZWMuSWQsIGNyQ2xvbmVJbmZvLkNoaWxkT2JqZWN0VHlwZSwgY3JDbG9uZUluZm8uUGFyZW50SWRGaWVsZE5hbWUsIGNyQ2xvbmVJbmZvLkFkZGl0aW9uYWxXaGVyZUNsYXVzZSk7CgoJLy8yLiBEZWVwIGNsb25lIGxpc3Qgb2YgY2hpbGQgcmVjb3JkcwoJTGlzdDxTT2JqZWN0PiBjbG9uZWRDaGlsZHJlbiA9IG9yaWdpbmFsQ2hpbGRyZW4uZGVlcENsb25lKCk7CgoJLy8zLiBDaGFuZ2UgcGFyZW50IGlkIG9uIGVhY2ggY2xvbmVkIGNoaWxkIHJlY29yZCB0byB0aGUgSWQgb2YgdGhlIGNsb25lZCBwYXJlbnQgcmVjb3JkIChpLmUuIGNsb25lZCB2ZXJzaW9uIG9mIHRoZSBvcmlnaW5hbCByZWNvcmQpCglmb3IgKFNPYmplY3QgY2xvbmVkQ2hpbGQgOiBjbG9uZWRDaGlsZHJlbikgCQkJCQljbG9uZWRDaGlsZC5wdXQoY3JDbG9uZUluZm8uUGFyZW50SWRGaWVsZE5hbWUsIENsb25lZFJlYy5JZCk7CgoJLy80LiBTYXZlIGNsb25lZCBjaGlsZHJlbiBpbiBEQgoJdHJ5Cgl7CgkJaW5zZXJ0IGNsb25lZENoaWxkcmVuOwoJCWlmIChUZXN0LmlzUnVubmluZ1Rlc3QoKSAmJiBTaW11bGF0ZUVycm9yID09IDIpIAoJCQl0aHJvdyBuZXcgRE1MRXhjZXB0aW9uKCdTaW11bGF0ZSBFcnJvciAyJyk7Cgl9CgljYXRjaCAoRE1MRXhjZXB0aW9uIGUpCgl7CgkJLy8gSWYgZXJyb3JzLCBzaG93IG1lc3NhZ2VzIHRvIHVzZXIgKHJvbGxiYWNrIGlzIGRvbmUgaW4gY2FsbGluZyBtZXRob2QpCgkJcmV0dXJuIFNldEVycm9yKCdFWENFUFRJT04gaW4gQ2xvbmVIZWxwZXJCYXNlLkNsb25lQ2hpbGRSZWNvcmRzJywgZSk7Cgl9CgoJcmV0dXJuIHRydWU7Cn0K[/et_pb_dmb_code_snippet]
Description of the code above:

  1. Get list of child records related to original record for the specified type of child record (specified in crCloneInfo parameter).
    The child records’ list is obtained by calling the GetChildRecords internal method.
  2. Deep clone the list of child records.
    This is done in just one line with the standard Salesforce deepClone method on the List class.
  3. Change parent id on each cloned child record to the Id of the cloned parent record (i.e. cloned version of the original record). This is so the cloned child records are not added to the original record but to the cloned version of the original record.
  4. Save all cloned child records in the database.
    The save is done within a try/catch statement in case a runtime error occurs. In that case the calling method will roll back the whole transaction.


4 – Additional Details to Think About

Cloned Record Owner: It could be a good idea to change the owner on the cloned record(s) to the current user. This is not currently done by the cloning framework, but could easily be done in the post clone methods.
Unique Fields: If an object you want to clone happens to have unique fields (e.g. external Ids), you need to make sure these unique fields are cleared before saving the records to the database (otherwise a runtime error will be generated). This can be done easily in the PostCloneOriginalRec_BeforeSave method.
To prevent cloning of unique fields, you could even extend the framework to check each field in an sObject to see if it’s unique (see methods isExternalID and isUnique on DescribeFieldResult class). If a field is unique, you could automatically clear it just after the record is cloned. This would save you having to do it manually in the PostCloneOriginalRec_BeforeSave method.
Lightning: The framework would work fine with lightning. All you’d need to do would be to replace the Visualforce cloning page with a lightning one and update the controller accordingly.

5 – Cloning Framework Demo

See what the cloning looks like with a simple example.


6 – Cloning Framework Source Code

If you’d like the source code for cloning framework files, get in touch with us or simply enter your contact details below.

Oops! We could not locate your form.


7 – Conclusion

With a little bit of upfront effort, you can build a cloning framework and use it to clone practically all types of objects in a very simple and standardised way.
This will save you a lot of coding in the long run and will offer your users more flexibility than the standard Salesforce clone.
Also read: