Now and then I have to work something out that requires a lot of searching around to find all the pieces. When that happens I often write up the instructions on the theory that other people might benefit from the complete answer.

This time I wanted to work out how to create a buttons in a Salesforce Lightning Web Component that allow a user to manage the records.

There are two links that matter in lots of use cases:

  1. The edit link for a specific record
  2. The manage records link for custom metadata type to add or delete records

The link to a specific record is fairly easy. It’s just a simple row action and well supported. The manage records link on the other hand took a bit more research.

Custom Metadata Type

Obviously, the first thing you need is to create your custom metadata type. Exactly what you create isn’t important here, the one I was working with was the field list type from my email invalidation tool.

For the purposes of this discussion I will use Your_Metadata_Type__mdt as the placeholder name. Swap in whatever your actual metadata type is called.

The LWC

Next we create our LWC to help us manage the records. The common use case here is when you’re creating a settings page for a custom app or tool. Hopefully one day we’ll be able to make these settings pages, but for the foreseeable future they generally need to be a custom app, with a Lightning Record Page, populated with one or more LWCs. Putting all those pieces together is beyond the scope of this article – I am telling you because that is the context of my assumptions.

A basic metadata setting for the LWC should look something like this:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
  <apiVersion>64.0</apiVersion>
  <isExposed>true</isExposed>
  <targets>
    <target>lightning__AppPage</target>
  </targets>
</LightningComponentBundle>

We will also need a basic HTML file for this LWC (obviously yours will probably have more elements – mine does):

<template>
  <lightning-card title="Metadata Configuration" icon-name="standard:custom_metadata_type">
    <div slot="actions">
      <lightning-button variant="brand" label="Manage" title="Manage Custom Metadata"
        onclick={handleManageRecords}>
      </lightning-button>
    </div>
    <div class="slds-m-around_medium">
      <template if:true={hasData}>
        <lightning-datatable key-field="Id" data={metadataRows} columns={columns} hide-checkbox-column="true"
          onrowaction={handleRowAction}>
        </lightning-datatable>
      </template>
    </div>
  </lightning-card>
</template>

In this example I put the manage button at the top and a simple lightning datatable which will hold one row per metadata record. The button has an onclick action that will call handleManageRecords in the LWC’s JavaScript controller. The rows will each have a row action block that triggers handleRowAction.

The JavaScript Controller

Things start to get more interesting when we get to the actual JavaScript. In fact most of the important work is here.

The JavaScript starts by loading the required modules. Three from Salesforce, and two from our custom Apex Controller (coming up next).

import { LightningElement, wire } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import getMetaDataRecords from '@salesforce/apex/MyMetadataController.getMyMetadataRecords';
import getMetadataPrefix from '@salesforce/apex/MyMetadataController.getCustomMetadataObjectPrefix';

Datatables need to know what columns they are displaying – and it’s best to put those in a variable for easy maintenance:

const COLUMNS = [
  {
    label: 'Label',
    fieldName: 'Label',
    type: 'text',
    sortable: true
  },
  {
    label: 'Developer Name',
    fieldName: 'DeveloperName',
    type: 'text',
    sortable: true
  },
  // Plus whatever other fields you need.
]

To make the Edit links work for each row we need to extend the provide NavigationMixIn class we imported above.

export default class MyMetadataLWC extends NavigationMixin(LightningElement) {
  columns = COLUMNS;
  metadataRows = [];

  // This does the work of actually loading the Custom Metadata.
  @wire(getMyCustomMetadata)
  wiredMyCustomMetadata({ error, data }) {
    if (data) {
      // Convert map to list
      const recordList = [];
      const recordNames = Object.getOwnPropertyNames(data);
      for (let i = 0; i < recordNames.length; i++) {
        recordList.push(data[recordNames[i]]);
      }
      this.metadataRows = recordList;
    } else if (error) {
      this.metadataRows = [];
    }
  }

  // Simple helper method to make the templates a little more stable.
  get hasData() {
    return this.metadataRows && this.metadataRows.length > 0;
  }

  // The edit action defined here is the really important part for navigating
  // directly to the record you want to update.
  handleRowAction(event) {
    const actionName = event.detail.action.name;
    const row = event.detail.row;

    if (actionName === 'edit') {
      // Navigate to the record page for editing
      this[NavigationMixin.Navigate]({
        type: 'standard__recordPage',
        attributes: {
          recordId: row.Id,
          objectApiName: 'Your_Metadata_Type__mdt',
          actionName: 'edit'
        }
      });
    }
  }

  async handleManageRecords() {
    const metadataPrefix = await getMetadataPrefix();
    // Navigate to the Custom Metadata Type management page
    this[NavigationMixin.Navigate]({
      type: 'standard__webPage',
      // I'll explain this bit below.
      attributes: {
        url: '/lightning/setup/CustomMetadata/page?address=%2F' + metadataPrefix + '%3Fsetupid%3DCustomMetadata'
      }
    });
  }
}

Currently the NavigationMixIn only provides direct links to Custom Metadata Records, not the manage records page (or the create record form). For the edit links that’s just fine: we use the NavigationMixIn’s behaviors and off we go. But to get to the Manage Records page we need to create the link itself.

If you go to the page Manage Records page for your Custom Metadata type you’ll see the URL is in the form: /lightning/setup/CustomMetadata/page?address=%2Fm01%3Fsetupid%3DCustomMetadata

Salesforce knows which Custom Metadata Type you’re talking about from this bit: %2Fm01%3F

In URLs anything that starts with % should be followed by a two digit hex code. These are URL Encoded characters (w3Schools has a complete reference if you want it). %2F is / and %3F at the end is ?. Between them is the custom metadata type’s prefix, in this case m01. This value is Metadata Type and Org Specific: you cannot safely hard-code it. We have to get that from the describe of the type, and that means diving into Apex.

The Apex Controller

To bolt this all together we need two tiny pieces of Apex. The first method will give us the list of records to display, the second gives us that prefix we need for the URL.

public with sharing class MyMetadataController {
  // This gets a list of all the metadata records we plan to display. Pretty standard for LWCs.
  @AuraEnabled(cacheable=true)
  public static Map<String, Your_Metadata_Type__mdt> getMyMetadataRecords() {
    Map<String, Your_Metadata_Type__mdt> records = Your_Metadata_Type__mdt.getAll();
    return records;
  }

  // This provides us with the key prefix for the custom type – needed for the link.
  @AuraEnabled
  public static string getCustomMetadataObjectPrefix() {
    return Your_Metadata_Type__mdt.sobjecttype.getDescribe()
      .getKeyPrefix();
  }
}

Of course you’ll also need test coverage for these functions as well. Everything you need is kicking around in the working example.

Working Example

My working example is embedded in the Email Invalidation Tool I built a few months ago and am slowing improving.