Building Reusable and Configurable Lightning Web Component (LWC) For Salesforce Experience Cloud or Communities, Using Chart.js as an Example

Building Reusable and Configurable Lightning Web Component (LWC) For Salesforce Experience Cloud / Communities,Using Chart.js as an Example

Introduction

We often build Lightning Web Components (LWC) as Salesforce developers to deliver interactive features. However, configuring them for non-technical admins usually requires editing code. What if admins could adjust properties like chart type, title, or data filters directly in Community Builder? In this post, you’ll learn how to build an LWC using Chart.js that exposes @api properties—making it fully configurable in Experience Cloud without touching code.

Why Public Properties Matter

By marking component attributes with @api and declaring them in the .js-meta.xml file, you empower admins to control your component’s behavior—chart type (pie, bar, e.g.), legend placement, title, and data filters—via drag-and-drop. This aligns well with Salesforce’s philosophy of admin-driven configuration, boosting flexibility and reducing developer dependencies.

Example Scenario: Visualizing Case Metrics for a Customer Support Community

Objective

A Salesforce customer support team wanted to empower their Experience Cloud community admins to visualize case metrics dynamically. These metrics included case statuses such as “New,” “Working,” “Closed,” and others. The solution needed to ensure that admins could adjust the chart type, title, and legend placement directly in Experience Builder. They should be able to manage data filters as well. This should be done without requiring developer assistance.

Implementation Steps
  1. Backend Logic with Apex
    An Apex controller, CaseController, was built to fetch case metrics grouped by status. The method, getCaseData, accepted a comma-separated string of statuses as input, making it flexible for filtering specific case types.
  2. Dynamic LWC Component
    The LWC, dynamicChartComponent, was designed to display the case metrics using Chart.js. It included the following features:
    • Public @api properties for admin configurability:
      • chartType: Determines the type of chart (e.g., pie, bar, doughnut).
      • legendPosition: Sets the legend’s location (e.g., top, left).
      • chartTitle: Allows custom titles for charts.
      • caseStatuses: Filters the case data by status.
    • @wire to fetch data from the Apex controller and dynamically update the chart.
  3. Configurable Metadata
    The .js-meta.xml file declared public properties, exposing them in Experience Builder. Admins could modify these properties via drag-and-drop without touching the code.
  4. Chart.js Integration
    • The component loaded the Chart.js library dynamically using loadScript.
    • Once data was available, the component initialized or updated the chart with user-defined settings.
  5. Experience Builder Deployment
    After deploying the LWC and Apex components:
    • The admin added the dynamicChartComponent to the Experience Builder page.
    • Configured the properties:
      • Chart Type: Doughnut
      • Legend Position: Right
      • Title: “Support Case Distribution”
      • Case Status Filter: New, Working, Closed
  6. Testing and Validation
    The community was published. The chart updated instantly based on user interactions. It was also updated with property changes in the Experience Builder.
Outcome

The implemented solution allowed admins to:

  • Visualize key case metrics dynamically in various chart formats.
  • Configure charts without involving developers, fostering agility.
  • Provide community users with actionable insights through interactive visual dashboards.

This approach not only streamlined community management but also enhanced user engagement with visually appealing and up-to-date data.

Demo
Sample Code
public without sharing class CaseController {
@AuraEnabled(cacheable=true)
public static List<AggregateResult> getCaseData(String statuses) {
List<String> statusList = String.isNotBlank(statuses) ? statuses.split(',') : null;
return [
SELECT Status, COUNT(Id)
FROM Case
WHERE Status IN :statusList
GROUP BY Status
];
}
}
<!–dynamicChartComponent.html–>
<template>
<lightning-card title={chartTitle} icon-name="standard:case">
<div class="chart-container">
<canvas width="400" height="400"></canvas>
</div>
</lightning-card>
</template>
/*dynamicChartComponent.js*/
import { LightningElement, api, wire } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
import CHARTJS_UMD from '@salesforce/resourceUrl/chartjs';
import getCaseData from '@salesforce/apex/CaseController.getCaseData';
export default class DynamicChartComponent extends LightningElement {
@api chartType = 'pie'; // Default chart type
@api legendPosition = 'top'; // Default legend position
@api chartTitle = 'Case Status Distribution';
@api caseStatuses = 'New,Closed'; // Comma-separated case statuses to filter
@api status;
isChartJsInitialized = false;
chart;
caseLabels = [];
caseData = [];
@wire(getCaseData, { statuses: '$caseStatuses' })
wiredCases({ error, data }) {
debugger;
if (data) {
console.log('data+++'+this.data);
console.log('data+++'+JSON.stringify(this.data));
this.caseLabels = data.map(caseRecord => caseRecord.Status);
this.caseData = data.map(caseRecord => caseRecord.expr0 );
console.log('caseData+++'+this.caseData);
this.updateChart();
} else if (error) {
console.error('Error fetching case data', error);
}
}
renderedCallback() {
if (this.isChartJsInitialized) return;
this.isChartJsInitialized = true;
loadScript(this, CHARTJS_UMD)
.then(() => {
this.initializeChart();
})
.catch(error => {
console.error('Error loading Chart.js', error);
});
}
initializeChart() {
const canvas = this.template.querySelector('canvas');
const ctx = canvas.getContext('2d');
this.chart = new window.Chart(ctx, {
type: this.chartType,
data: {
labels: this.caseLabels,
datasets: [{
label: 'Cases',
data: this.caseData,
backgroundColor: ['rgba(75, 192, 192, 0.5)', 'rgba(153, 102, 255, 0.5)', 'rgba(255, 159, 64, 0.5)']
}]
},
options: {
responsive: false,
plugins: {
legend: {
position: this.legendPosition
},
title: {
display: true,
text: this.chartTitle
}
}
}
});
}
updateChart() {
if (this.chart) {
this.chart.data.labels = this.caseLabels;
this.chart.data.datasets[0].data = this.caseData;
this.chart.update();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!–dynamicChartComponent.js-meta.xml–>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata&quot;
fqn="dynamicChartComponent">
<apiVersion>64.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
<target>lightningCommunity__Default</target>
<target>lightningCommunity__Page</target>
</targets>
<targetConfigs>
<targetConfig targets="lightningCommunity__Default">
<property
name="chartType"
type="String"
label="Select Status"
datasource="bar,line,pie,doughnut"
default="pie"
/>
<property name="legendPosition" type="String" default="top" label="Legend Position" />
<property name="chartTitle" type="String" label="Chart Title" />
<property name="caseStatuses" type="String" label="Case Status Filter (Comma Separated)" />
</targetConfig>
</targetConfigs>
</LightningComponentBundle>

Conclusion

By adding @api properties and defining them in .js-meta.xml, you create LWC components that are both developer-driven and admin-configurable. In this Chart.js example, admins can tailor how data is visualized—shape, labels, filters—entirely from Community Builder. This approach reduces dev workload, increases flexibility, and empowers business users with dynamic dashboards.

Leave a comment