[SalesForce] How to use metadata API in lightning web component

I am pretty new to metadata API, I wonder how can I use metadata API to push some JSON metadata just like in jsForce(I successfully use jsForce in this way below in my local web app, but I am not sure how to use it in Lightnint web component):

enter image description here

I have a Lightning web component here, and I want to push the same metadata above here, but I do not know how to use similar metadata API(I do not have to use jsForce, any API does same thing will be fine):

enter image description here

Best Answer

Calling metadata API from LWC is not trivial as mentioned in Salesforce doc

By security policy, sessions created by Lightning components aren’t enabled for API access. This prevents even your Apex code from making API calls to Salesforce. Using a named credential for specific API calls allows you to carefully and selectively bypass this security restriction.

The restrictions on API-enabled sessions aren’t accidental. Carefully review any code that uses a named credential to ensure you’re not creating a vulnerability.

As a workaround, I have used a VF page to generate a valid session Id which has API access.

I'm also using apex-mdapi open-source library to call metadata API

Sample code to create a Custom object using the Metadata API:

VF Page to generate API access enabled session Id(SessionId.page)

<apex:page>
    Start_Of_Session_Id{!$Api.Session_ID}End_Of_Session_Id
</apex:page>

LWC Component

<template>
    <lightning-button variant="brand" label="Create Object" title="Primary action" onclick={createObjectInApex} class="slds-m-left_x-small"></lightning-button>
</template>

import { LightningElement } from 'lwc';
import createObject from '@salesforce/apex/CreateObjectCtrl.execute';

export default class CreateObject extends LightningElement {
    createObjectInApex() {
        createObject()
        .then(() => {
            console.log("Metadata api called successfully");
        })
        .catch(error => {
            console.log(error);
        })
    }
}

Apex Controller

public with sharing class CreateObjectCtrl {
    @AuraEnabled
    public static void execute() {
        MetadataService.MetadataPort service = createService();     
        MetadataService.CustomObject customObject = new MetadataService.CustomObject();
        customObject.fullName = 'Test__c';
        customObject.label = 'Test';
        customObject.pluralLabel = 'Tests';
        customObject.nameField = new MetadataService.CustomField();
        customObject.nameField.type_x = 'Text';
        customObject.nameField.label = 'Test Record';
        customObject.deploymentStatus = 'Deployed';
        customObject.sharingModel = 'ReadWrite';

        service.createMetadata(new MetadataService.Metadata[] { customObject });        
    }


    private static MetadataService.MetadataPort createService() { 
        MetadataService.MetadataPort service = new MetadataService.MetadataPort();
        service.SessionHeader = new MetadataService.SessionHeader_element();
        //NOTE: in context of LWC UserInfo.getSessionId() has not API enabled access
        service.SessionHeader.sessionId = apiEnabledSessionId(); 
        return service;     
    }


    private static String apiEnabledSessionId(){
        PageReference sessionPage = Page.SessionId;
        String vfContent = sessionPage.getContent().toString();
        Integer startIndex = vfContent.indexOf('Start_Of_Session_Id') + 'Start_Of_Session_Id'.length();
        Integer endIndex = vfContent.indexOf('End_Of_Session_Id');

        return vfContent.substring(startIndex, endIndex);
    }
}