Force CockpitA VSCode extension that provides a Salesforce utilities cockpit. It connects to Salesforce orgs via the SF CLI and offers operational tools for monitoring and general utilities — all from within VSCode. Contact: Pablo Fernández Posadas InstallationFrom the VS Code Marketplace
From a
|
| Tab | Description |
|---|---|
| Overview | Org info card, storage usage bars, SOQL query editor with results table |
| Utils | Built-in utilities (Clone User, Reactivate OmniScript) and custom YAML scripts |
| Monitoring | SOQL-powered Chart.js dashboards loaded from YAML config files |
Overview Tab
The Overview tab shows org connection details and storage usage bars (Data Storage and File Storage), and provides a quick SOQL query editor.
Monitoring Tab
The Monitoring tab displays live charts built from SOQL queries. Each chart is defined by a YAML configuration file. Charts are rendered using Chart.js and can be refreshed manually or on a timer.
Where charts come from
Charts are loaded from three sources (merged at runtime, later wins):
| Source | Path | Purpose |
|---|---|---|
| Bundled | {extension}/force-cockpit/monitoring/{category}/*.yaml |
Shared defaults shipped with the extension |
| User-defined | {workspace}/force-cockpit/monitoring/{category}/*.yaml |
Your own charts, committed to git |
| Private | {workspace}/force-cockpit/private/monitoring/{category}/*.yaml |
Personal charts, not committed to git |
The user-defined path can be customised via the VSCode setting forceCockpit.cockpitPath (see Configuration).
Private charts
Checking Private in the chart edit form saves the config to force-cockpit/private/monitoring/ instead of the shared folder. The extension automatically adds force-cockpit/private/ to .gitignore on startup so these files are never committed.
Private charts show a 🔒 badge on their card. Use the All / Shared / Private filter above the category pills to show only the configs you care about.
You cannot save a private chart with the same category + name as an existing shared one (and vice versa) — the extension will show an error.
Sub-categories
Monitoring configs support two levels of nesting: {category}/{sub-category}/*.yaml. Clicking a parent category pill reveals a second row of narrower sub-pills to drill down.
Adding a new monitoring chart
Pick or create a category folder under
force-cockpit/monitoring/in your workspace:{workspace}/ └── force-cockpit/ └── monitoring/ └── orders/ ← any name you like └── my-chart.yamlCreate the YAML file using the schema below.
Reload the Monitoring tab — your chart appears automatically. No rebuild or restart needed.
YAML schema
name: Open Orders by Status # Display name shown on the card
description: Count of open orders grouped by status. # Subtitle shown on the card
soql: |
SELECT Status, COUNT(Id) RecordCount
FROM Order
WHERE Status != 'Cancelled'
GROUP BY Status
labelField: Status # API name of the field used as chart labels (X-axis or pie slices)
valueFields: # One or more datasets to plot
- field: RecordCount # API name of the numeric field
label: Orders # Legend label for this dataset
format: number # optional: number | currency | percent
chartType: bar # bar | line | pie | doughnut | metric | table
stacked: false # true = stacked bars/lines (bar and line only)
refreshInterval: 0 # Auto-refresh in seconds. 0 = manual refresh only
Field reference
| Field | Required | Values | Description |
|---|---|---|---|
name |
Yes | string | Card title |
description |
No | string | Card subtitle |
soql |
Yes | SOQL string | Any valid SOQL query |
labelField |
Yes* | API name | Field whose values become chart labels or the first table column. *Not required for metric type. |
valueFields |
Yes | array | At least one { field, label } entry |
valueFields[].field |
Yes | API name | Field to plot or display |
valueFields[].label |
Yes | string | Dataset legend label or column header |
valueFields[].format |
No | currency | percent |
Number formatting on axes, tooltips, and table cells |
chartType |
No | bar | line | pie | doughnut | metric | table |
Default chart type (user can override for chart types) |
stacked |
No | true | false |
Stack bars or lines (bar and line only) |
refreshInterval |
No | integer (seconds) | 0 disables auto-refresh |
Multiple datasets (grouped charts)
You can plot multiple fields from the same query side by side:
name: Order Amounts by Status
soql: |
SELECT Status, SUM(TotalAmount) Total, COUNT(Id) Count
FROM Order
GROUP BY Status
labelField: Status
valueFields:
- field: Total
label: Total Amount (€)
format: currency
- field: Count
label: Number of Orders
chartType: bar
refreshInterval: 60
Stacked bars
Add stacked: true to a bar or line chart with multiple valueFields to render them as stacked segments:
name: Revenue by Category
soql: SELECT Name, Hardware__c, Software__c, Services__c FROM Account__c
labelField: Name
valueFields:
- field: Hardware__c
label: Hardware
format: currency
- field: Software__c
label: Software
format: currency
- field: Services__c
label: Services
format: currency
chartType: bar
stacked: true
Metric cards (KPI)
Use chartType: metric to display a single large number. labelField is not required. The first value of the first valueField is shown as the headline number:
name: Open Orders
description: Total orders waiting to be processed.
soql: SELECT COUNT(Id) Cnt FROM Order WHERE Status = 'Open'
valueFields:
- field: Cnt
label: Open Orders
chartType: metric
refreshInterval: 30
Table view
Use chartType: table to render a scrollable, sortable table. Works with any SOQL — aggregate or not. Click any column header to sort. Use format: currency or format: percent on valueFields to format numeric columns:
name: Recent Orders
description: Last 20 orders by creation date.
soql: |-
SELECT OrderNumber, Status, TotalAmount
FROM Order
ORDER BY CreatedDate DESC
LIMIT 20
labelField: OrderNumber
valueFields:
- field: Status
label: Status
- field: TotalAmount
label: Amount (€)
format: currency
chartType: table
Examples
The extension ships with example charts under force-cockpit/monitoring/examples/:
- Open Orders by Status — bar chart of orders grouped by status
- Accounts by Type — bar chart of accounts grouped by type
- Open Orders — metric card showing a live count
- Recent Orders — table view of the last 20 orders
Editing and saving charts in the UI
Each card has an Edit button that opens an inline form. Changes to the SOQL field trigger an auto-preview after 800 ms. Check Private to save to the private folder; leave unchecked to save to the shared workspace path. Clicking Save writes the YAML — it never overwrites bundled extension charts.
Utils Tab — YAML Scripts
The Scripts sub-tab executes scripts defined in YAML files. Three script types are supported. Scripts live under force-cockpit/scripts/{category}/*.yaml (shared) or force-cockpit/private/scripts/{category}/*.yaml (private, git-ignored). Sub-categories are also supported: {category}/{sub-category}/*.yaml gives a second row of pills for drilling down.
# Apex script — requires org connection
name: My Apex Script
description: What this script does.
apex: |
System.debug('Hello from Apex');
# Terminal command — no org connection required
name: My Command
description: Runs a local shell command.
command: npm run build
# JavaScript script — runs in Node.js VM sandbox, org connection is optional
name: My JS Script
description: Query Salesforce with jsforce.
js: |
const result = await query("SELECT Id, Name FROM Account LIMIT 5");
log(JSON.stringify(result.records, null, 2));
Exactly one of apex:, command:, or js: is required. Click Execute on any script card to run it.
Configurable Inputs
Scripts can declare input variables that are prompted at execution time. Add an inputs: section to your YAML:
name: Update Order Status
description: Updates an order and its line items.
inputs:
- name: orderId
label: Order ID
required: true
- name: status
label: Status
type: picklist
required: true
options:
- New
- Submitted
- Completed
- Cancelled
- In Progress
apex: |
Id orderId = '${orderId}';
// ... use orderId and status in your Apex code
Each input supports:
| Field | Required | Description |
|-------|----------|-------------|
| name | Yes | Variable identifier (alphanumeric + underscore) — used as ${name} in the script body |
| label | No | Display label (defaults to name) |
| type | No | string (text input, default) or picklist (dropdown) |
| required | No | If true, Execute is disabled until the field is filled |
| options | Picklist only | List of selectable values |
Write ${variableName} in your script code where you want the value substituted. Escaping is handled automatically (Apex-safe for apex, JSON-safe for js, raw for command).
System Placeholders
In addition to user-defined inputs, scripts can use built-in system placeholders that are automatically resolved from the connected org:
| Placeholder | Description |
|---|---|
${orgUsername} |
Salesforce username (not alias) of the connected org |
System placeholders use the same ${name} syntax and type-appropriate escaping as user inputs. If no org is connected, they resolve to an empty string. If a user-defined input has the same name as a system placeholder, the user input takes precedence.
name: Show My User
apex: |
System.debug('Running as: ${orgUsername}');
| Type | Badge | Org required | Output |
|---|---|---|---|
| Apex | Blue | Yes | Debug log (USER_DEBUG filter available) |
| Command | Purple | No | stdout/stderr |
| JavaScript | Green | No | log() / console.log() output |
JS script context: connection (jsforce Connection or null), org (OrgDetails or null), query(soql), log(), error(), console, fs, path, yaml.
Private scripts
Check Private when creating or editing a script to save it to force-cockpit/private/scripts/ instead of the shared folder. The extension automatically adds force-cockpit/private/ to .gitignore on startup. Private scripts show a 🔒 badge and can be filtered with the All / Shared / Private control. You cannot save a private script with the same category + name as an existing shared one.
JS datafix example
Because JS scripts use jsforce directly, they can query and update records in a loop without hitting Apex governor limits (no DML row limits, no CPU timeout). This makes them ideal for bulk datafixes that would otherwise require a Batch Apex class.
name: Backfill Account Region
description: Sets Region__c = 'EU' on all Accounts where it is blank. Processes in chunks of 200.
js: |
const BATCH_SIZE = 200;
let updated = 0;
let done = false;
while (!done) {
const result = await query(
`SELECT Id FROM Account WHERE Region__c = null LIMIT ${BATCH_SIZE}`
);
if (result.records.length === 0) {
done = true;
break;
}
const records = result.records.map(r => ({
Id: r.Id,
Region__c: 'EU'
}));
const res = await connection.sobject('Account').update(records);
const successes = res.filter(r => r.success).length;
const failures = res.filter(r => !r.success).length;
updated += successes;
log(`Batch done — ${successes} updated, ${failures} failed`);
if (failures > 0) {
error(JSON.stringify(res.filter(r => !r.success), null, 2));
}
}
log(`\nDone. Total updated: ${updated}`);
Configuration
Most extension settings are managed via a config.yaml file — making them easy to share across a team by committing the file to git.
The extension loads configuration in this order (later layers override earlier ones):
- Hardcoded defaults — built into the extension
- Bundled
config.yaml— shipped with the extension at its root - User
config.yaml— atforce-cockpit/config.yamlin your workspace (or the customcockpitPath)
Only keys present in a layer override the previous layer — omitted keys keep their default values.
Available settings
| Key | Type | Default | Description |
|---|---|---|---|
apiVersion |
string | "65.0" |
Salesforce API version for all API calls |
protectedSandboxes |
string[] | [] |
Sandbox org names that require confirmation before destructive actions |
panelTitle |
string | "Force Cockpit" |
Display title shown in the panel header and tab |
logoPath |
string | "" |
Path to a logo image relative to the workspace root (empty = default logo) |
Example force-cockpit/config.yaml
apiVersion: "65.0"
protectedSandboxes:
- staging
- uat
panelTitle: "My Team Cockpit"
logoPath: "assets/logo.png"
VSCode setting
One setting remains in VSCode's settings.json because it determines where the config file lives:
| Setting | Default | Description |
|---|---|---|
forceCockpit.cockpitPath |
"" |
Absolute path to the force-cockpit folder. Defaults to {workspace root}/force-cockpit if empty. |
Note: Changes to
config.yamlare picked up automatically — no window reload needed.
Releases
New versions are published automatically via GitHub Actions.
To create a release:
- Go to the Actions tab in the GitHub repository.
- Select Release → Run workflow.
- Choose the version bump type (
patch,minor, ormajor) or enter an explicit version string. - Click Run workflow.
The workflow will:
- Bump the version in
package.json - Update
CHANGELOG.mdwith the version and date - Push a version commit and git tag to
main - Build and package the
.vsix - Create a GitHub Release with the
.vsixattached - Publish the extension to the VS Code Marketplace
The .vsix for every release is available on the GitHub Releases page.
Development
npm install
npm run build # Build extension (copy assets + esbuild bundle)
npm run watch # Build in watch mode
npm run compile # TypeScript type-check only
npm run package # Build + create .vsix
npm run audit:prod # Check production dependencies for known vulnerabilities
Security
- Dependency auditing: Every PR runs
npm auditagainst production dependencies in CI. Dependabot opens weekly PRs when vulnerable packages have updates available. - Webview CSP: The webview uses a strict Content Security Policy with nonce-based script loading — no inline scripts are allowed.
.npmrchardening:audit-level=high,engine-strict=true, andsave-exact=trueensure safe and reproducible installs.