18

I worked on Managed packages for nearly 10 years now and never missed Custom Metadata Types. Not before they came and not after they were announced. I didn't come across a single great and technical severe reason to use Custom Metadata Types over regular Custom Object.

Unlike Custom Objects they:

  1. Don't support triggers
  2. Are hard to create in Apex Tests
  3. Have a very limited number of field types
  4. Have an inflexible UI to managed records

The only use case where CMDT are somewhat nice is, that you cannot package Custom Object records in packages. But that is also not an issue. You can perfectly store hundreds of Custom records in JSON serialized form in a single packaged Static Resource and unpackage them on install.

So tell me what your reasons are to favor them over Custom Objects?

3
  • 1
    I used custom metadata types a lot in the past, but now I tend to favor the old good custom settings. Both have advantages and disadvantages. But I personally prefer custom settings for one reason: unit tests. I like to define my own data in tests and have control over that. Commented Jul 16, 2020 at 11:42
  • 1
    fflib Selector pattern and apexMocks (or equivalent) can address the "hard to create in unit tests"
    – cropredy
    Commented Jul 16, 2020 at 22:24
  • @cropredy that sounds interesting. Would you mind providing a code example. I am using fflib everywhere. Commented Jul 17, 2020 at 6:35

7 Answers 7

19

Here are some "advantages" vs using a Custom Object:

  • Can be Deployed
  • Can use Field Definition or Entity Particle to create references to Custom Object Metadata
  • Retrieve doesn't cost against SOQL limits (unless it contains long text)
  • Can be referenced in formula fields
  • Unit Testing isn't too bad as well as the code is well composed. Because they are treated similar to @seeAllData, it actually makes integration testing the current configuration of the ORG much easier.
  • Can be "protected" to store secrets in managed packages

However (IMO), Salesforce missed too many features to make them the clear choice in every scenario:

  • Records cannot be statically referenced in code (this would make dependencies much more robust)
  • No direct/global access in VF or LWC
  • Objects cannot reference them. This results in a lot of "magic" relationships being setup (EG string matching).
  • No Triggers (As mentioned above)

I've created a public google sheet that attempts to compare the features of each of the configuration options (please add comments if I missed anything).

My Approach

  • I tend to favor CMDT over Custom Settings, UNLESS I need user specific configuration.
  • I'll use Custom Objects where it's critical to ensure referential integrity to data records.
  • Custom Labels were once commonly used because they could be deployed, but now with CMDT there isn't much reason to use them (for configuration).
8
  • 3
    Confirming that Custom Metadata is not directly accessible in LWC. One needs to build a method on the APEX side to retrieve what they need and return it to the LWC.
    – Arthlete
    Commented Jul 14, 2020 at 20:07
  • You can avoid SOQL query limit by using Custom Settings.
    – dzh
    Commented Jul 15, 2020 at 1:23
  • @dzh true. I would be nice to have a big comparison table between the different methods.
    – NSjonas
    Commented Jul 15, 2020 at 5:28
  • @NSjonas I never wanted to reference them in formulas and SOQL limits never are an issue on a handful of config records. So for me your answer only contains issues 😉 Commented Jul 15, 2020 at 6:55
  • 1
    @NSjonas Want to add to your "advantages" list the fact that you can create references to other metadata via the "Metadata Reference" field data type? I've encountered a few scenarios where references to entity definitions and dependent field definitions has come in very handy (esp. preserving referential integrity, as opposed to using strings for object/field name references). Commented Jul 22, 2020 at 6:19
16

A reason I haven't seen articulated yet applies specifically to the managed package context:

Packaged, protected Custom Metadata Types are an ideal solution for secret storage in a managed package context. While Protected Custom Objects and Protected Custom Settings can also offer secret storage, only Custom Metadata Types allow those secrets to be packaged and deployed, and protected from everything outside the boundary of packaged Apex.

5
  • 1
    "Protected Custom Settings" you mean?
    – Mark Pond
    Commented Jul 14, 2020 at 20:30
  • 3
    No, protected CMTs. Same protection as with Custom Settings, but the records are metadata and are deployable.
    – David Reed
    Commented Jul 14, 2020 at 20:34
  • specifically referring to this wording: "While Protected Custom Objects can also offer secret storage" - while it is true custom settings are a special type of custom object under the hood, this language implies that you can somehow protect a traditional custom object.
    – Mark Pond
    Commented Jul 14, 2020 at 20:51
  • 7
    I don't think it's especially widely used, but you can protect custom objects.
    – David Reed
    Commented Jul 14, 2020 at 20:52
  • 2
    Nice, learned something new today! Thanks for the education.
    – Mark Pond
    Commented Jul 14, 2020 at 20:55
6

The big reason for me to use Custom Metadata is the logical separation between Custom Metadata and Custom Objects. In my head they serve a different purpose and that helps me better organize things in my org. I would store any configurations for my applications in Custom Metadata rather than Custom Objects. The way I see it Custom Objects should retain data about records in the org.

Additionally as NSjonas pointed out CM doesn't count against the governor limits in the org. They can be refreshed to sandboxes and are cached data which would provide better performance.

4

We've implemented them and it was one of the biggest disappointments as an ISV, here's why:

  • Doesn't support every object (surprise, Users don't work!)
  • Creating via code is absolute pain as it's either async or metadata deploy - both means thousands of lines of code (but only second one is reliable)
  • Impossible to relate to an SObject (kinda makes sense since it's deployable, but opposite should be possible)
  • Doesn't have a createdbyid, lastmodifieddate, etc

There was also some obscure SOQL limitation that I can't remember at the moment.

Basically it's only upside (deployable, wow) hardly justifies all the downsides.

1
  • The primary SOQL limitation is only supporting AND (and OR on the same field for handling multiple options). If you want to use OR across fields with CMT SOQL it isn't actually supported.
    – Phil W
    Commented Mar 31, 2021 at 6:47
3

Some benefits not listed but I like are:

  • Page Layouts (custom settings can get quite messy for customers given we don't control the layout). With metadata types, we can publish layouts.
  • An indirect one seems to be for the customers: they can manage their configuration and migrate through environment more easily than custom settings.
    • This can make their lives much easier because some rely on manual steps to configure environments - including production.
  • Allow reference to object and field in a controlled way.
    • This usually looks like a string in a custom setting. Using "Metadata Relationship" with custom metadata types this is tied to real object and fields while editing, visualizing.

I still agree that a lot should be improved and not being able to fully replace hierarchical custom settings also make them a little less appealing. Plus, unit tests experience with them still more painful.

3

Robert - in response to your point about Custom Metadata being hard to set up in unit tests

I use the selector pattern to query all sobjects, including Custom Metadata, then, use mocking via dependency injection to return the results to the testmethod. This decouples the testmethod from any live custom metadata (and thus achieves test isolation). Here is a small fragment

Standard fflib Selector and apexmocks pattern follows; assumes an Application class as per fflib pattern.

Since you can't construct Custom Metadata label/developername, I resort to the JSON deserialize approach. Here, I'm using the SObject Fabricator repo but there are other ways to do this including maps, json strings, or other libraries. Once you go the dependency injection route to unit tests, it is inevitable you will need at some point to have to construct sobjects that aren't supported by new TheSObject(..). I liked SObject Fabricator for its expressiveness.

fflib_ApexMocks mocks = new fflib_ApexMocks();
// Given mockSelector results
MyCustomMetadata__mdt[] mockMdts = new List<MyCustomMetadata__mdt> {
                (MyCustomMetadata__mdt) new sfab_FabricatedSObject (MyCustomMetadata__mdt.class) // [
                        .setField(MyCustomMetadata__mdt.Label,'foo')
                        .setField(MyCustomMetadata__mdt.SomeField__c,true)                          
                        .toSObject(),
                (MyCustomMetadata__mdt) new sfab_FabricatedSObject (MyCustomMetadata__mdt.class)    // [1] not troubleshooter
                        .setField(MyCustomMetadata__mdt.Label,'bar')
                        .setField(MyCustomMetadata__mdt.SomeField__c,false)
                        .toSObject()
        };

// Given mock selector(s)
MyCustomMetadatasSelector mockMdtsSelector = (MyCustomMetadatasSelector) mocks.mock(MyCustomMetadatasSelector.class);
mocks.startStubbing();
mocks.when(mockMdtsSelector.sObjectType()).thenReturn(MyCustomMetadata__mdt.SObjectType);
mocks.when(mockMdtsSelector.selectByLabel(new Set<String>{'foo'}))
            .thenReturn(new List< MyCustomMetadata__mdt> {mockMdts[0]});
mocks.when(mockMdtsSelector.selectByLabel(new Set<String>{'bar'}))
            .thenReturn(new List<MyCustomMetadata__mdt> {mockMdts[1]});
mocks.stopStubbing();
// Given mocks injected
Application.Selector.setMock(mockCrsdToDescriptionsSelector);

// when code-under-test invoked
Test.startTest();
new MyClass().doStuff();
Test.stopTest();

// Then verify
.. asserts and/or mocks.verify

Note that if the selector method is called with a set containing only foo, the first mocked custom metadata is retrieved by the dependency-injected selector. And, if called with a set containing bar, the second mocked custom metadata is retrieved.

Since you can't construct Custom Metadata label/developername, I resort to the JSON deserialize approach. Here, I'm using the SObject Fabricator repo but there are other ways to do this including maps, json strings, or other libraries. Once you go the dependency injection route to unit tests, it is inevitable you will need at some point to have to construct sobjects that aren't supported by new TheSObject(..). I liked SObject Fabricator for its expressiveness.

I know you use your own testobject fabricator so adapt as necessary.

1

I've always seen them as an alternative to Custom Settings as much as an alternative to Custom Objects.

However, one use case that stands out where they were really useful was in configuring mappings in a point-to-point integration inbound to Salesforce where they essentially acted as the middleware.

We could create mappings of values from the source system to values in specified fields on specified SObjects in SFDC, and kept this to be a declarative exercise. Again, nothing that couldn't be done in JSON, but this was a Professional Services engagement where the customer was extremely keen to avoid code-heavy solutions (and they'd have classed JSON manipulation as that) due to having no available in-house resources for that.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .