A practical guide to triggering GitHub Actions workflows directly from Salesforce using Lightning Web Components (LWC) and Apex middleware — includes architecture, code samples, and best practices for secure automation.
Overview
This guide demonstrates how to connect Salesforce to GitHub Actions to trigger CI/CD workflows from within the CRM. The pattern uses a Custom Metadata Type for configuration, an Apex service class as middleware to talk to the GitHub API, and a reusable Lightning Web Component to provide a simple admin UI. Use this approach to centralize DevOps triggers, reduce context switching, and empower non-developer admins to start automated workflows safely.
Why this matters
Embedding GitHub workflow triggers in Salesforce helps organizations:
- Reduce the friction of launching CI/CD jobs from business-facing interfaces.
- Improve governance by storing defaults in Custom Metadata.
- Enable secure, auditable initiation of automated processes without sharing developer credentials widely.
Architecture & components
Key components:
- Custom Metadata Type (Github_Settings__mdt) to store defaults (owner, repo, workflow, branch, PAT).
- Apex middleware (GitHubActionService) to call the GitHub Actions API securely from Salesforce.
- Lightning Web Component (gitHubWorkflowTrigger) acting as the admin UI and orchestration layer.
Step-by-step
1. Set up a GitHub PAT (or GitHub App)
Generate a Personal Access Token with repo and workflow scopes (or prefer a GitHub App / fine-grained tokens for org use). Store a short expiry and rotate regularly according to your security policy.
2. Define your GitHub Workflow
Create the workflow file in your repository under .github/workflows. Ensure it exposes inputs if you need to pass parameters (such as ORG_ALIAS).
3. Create Custom Metadata Type
Create a Custom Metadata Type named Github_Settings__mdt with fields to store defaults:
- Github_Branch__c (Text 255)
- Github_Workflow__c (Text 255)
- Github_Owner__c (Text 255)
- Github_Repo__c (Text 255)
- Github_PAT__c (Text 255) — consider storing an obfuscated value and restricting visibility
4. Apex middleware
Use an Apex class that reads Custom Metadata and calls the GitHub Actions API. Example (trimmed for brevity):
public class GitHubActionService {
@AuraEnabled(cacheable=true)
public static Map getDefaultGitHubSettings(String githubFlow) {
Map settings = new Map();
try {
Github_Settings__mdt config = Github_Settings__mdt.getInstance(githubFlow);
if (config != null) {
settings.put('githubOwner', config.Github_Owner__c);
settings.put('githubRepo', config.Github_Repo__c);
settings.put('githubWorkflow', config.Github_Workflow__c);
settings.put('githubBranch', config.Github_Branch__c);
settings.put('githubPAT', config.Github_PAT__c);
}
} catch (Exception e) {
throw new AuraHandledException('Error retrieving GitHub settings: ' + e.getMessage());
}
return settings;
}
@AuraEnabled
public static String triggerWorkflow(
String githubOwner,
String githubRepo,
String githubWorkflow,
String githubBranch,
String githubPat,
String orgAlias
) {
try {
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://api.github.com/repos/' + githubOwner + '/' + githubRepo + '/actions/workflows/' + githubWorkflow + '/dispatches');
request.setMethod('POST');
request.setHeader('Accept', 'application/vnd.github+json');
request.setHeader('X-GitHub-Api-Version', '2022-11-28');
request.setHeader('Authorization', 'Bearer ' + githubPat);
request.setHeader('Content-Type', 'application/json');
Map payload = new Map();
payload.put('ref', githubBranch);
Map inputs = new Map();
inputs.put('ORG_ALIAS', orgAlias);
payload.put('inputs', inputs);
String body = JSON.serialize(payload);
request.setBody(body);
HttpResponse response = http.send(request);
if (response.getStatusCode() == 204) {
return 'SUCCESS: GitHub workflow triggered successfully.';
} else {
return 'ERROR: ' + response.getStatus() + ' - ' + response.getBody();
}
} catch (Exception e) {
return 'ERROR: ' + e.getMessage();
}
}
} 5. Lightning Web Component
Build a simple LWC admin panel to load defaults, validate inputs, and call the Apex method. Key files include the JS, HTML and meta XML for exposing targets.
// gitHubWorkflowTrigger.js
import { LightningElement, track, api, wire } from 'lwc';
import triggerWorkflow from '@salesforce/apex/GitHubActionService.triggerWorkflow';
import getDefaultGitHubSettings from '@salesforce/apex/GitHubActionService.getDefaultGitHubSettings';
export default class GitHubWorkflowTrigger extends LightningElement {
@track githubOwner = '';
@track githubRepo = '';
@track githubWorkflow = '';
@track githubBranch = '';
@track githubPat = '';
@track resultMessage = '';
@track isLoading = false;
@track settingsLoaded = false;
@api orgAlias = 'orgAlias';
@api githubFlow = 'Run_LWC_Flow';
@wire(getDefaultGitHubSettings , { githubFlow: '$githubFlow' })
wiredSettings({ error, data }) {
if (data) {
this.githubOwner = data.githubOwner || '';
this.githubRepo = data.githubRepo || '';
this.githubWorkflow = data.githubWorkflow || '';
this.githubBranch = data.githubBranch || '';
this.githubPat = data.githubPAT || '';
this.settingsLoaded = true;
} else if (error) {
this.resultMessage = 'Error loading default settings: ' + (error.body?.message || error.message);
}
}
handleInputChange(event) {
const field = event.target.name;
this[field] = event.target.value;
}
async handleTriggerClick() {
if (this.isLoading || !this.settingsLoaded) return;
if (!this.githubOwner || !this.githubRepo || !this.githubWorkflow || !this.githubBranch || !this.githubPat) {
this.resultMessage = 'ERROR: Please fill in all required GitHub fields';
return;
}
this.isLoading = true;
this.resultMessage = '';
try {
const result = await triggerWorkflow({
githubOwner: this.githubOwner,
githubRepo: this.githubRepo,
githubWorkflow: this.githubWorkflow,
githubBranch: this.githubBranch,
githubPat: this.githubPat,
orgAlias: this.orgAlias
});
this.resultMessage = result;
} catch (error) {
this.resultMessage = 'ERROR: ' + error.body.message;
} finally {
this.isLoading = false;
}
}
}Integration and testing
- Expose the LWC on Experience Builder (or App, Home, Record pages) and configure properties (orgAlias, githubFlow).
- Publish the Experience site and trigger the component.
- Confirm the workflow execution in the repository Actions tab.
Security and best practices
- Avoid storing long-lived PATs; prefer GitHub Apps or fine-grained tokens for organizations.
- Restrict access to Custom Metadata and component visibility via profiles/permission sets.
- Log actions and responses for auditability and monitoring.
Conclusion
This pattern turns Salesforce into a lightweight DevOps orchestrator — empowering admins and reducing developer friction for CI/CD tasks. Test thoroughly in sandboxes and adapt authentication to your security standards before deploying to production.
Assigned category: DevOps






Leave a Reply