sf-cli-plugin-authoring
Use when authoring a custom Salesforce CLI (`sf`) plugin — internal team tooling that wraps deploy/test workflows, an ISV plugin distributed via npm, or migrating a legacy `sfdx`-style plugin to the v2 `sf` topic-and-command shape. Triggers: 'sf plugin generate', 'extend SfCommand class', 'oclif command structure', 'sf-plugins-core flags', 'topic separator sf v2', 'JSON output for plugin', 'plugin hooks prerun command_not_found', 'distribute internal sf plugin npm registry', 'sign Salesforce CLI plugin'. NOT for using existing `sf` plugins in CI automation (use `devops/salesforce-cli-automation`), CPQ price-rule plugins (use `apex/cpq-apex-plugins`), OmniStudio DataPack CLI usage (use `omnistudio/omnistudio-deployment-datapacks`), or generic JS/TS package publishing.
Best use case
sf-cli-plugin-authoring is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when authoring a custom Salesforce CLI (`sf`) plugin — internal team tooling that wraps deploy/test workflows, an ISV plugin distributed via npm, or migrating a legacy `sfdx`-style plugin to the v2 `sf` topic-and-command shape. Triggers: 'sf plugin generate', 'extend SfCommand class', 'oclif command structure', 'sf-plugins-core flags', 'topic separator sf v2', 'JSON output for plugin', 'plugin hooks prerun command_not_found', 'distribute internal sf plugin npm registry', 'sign Salesforce CLI plugin'. NOT for using existing `sf` plugins in CI automation (use `devops/salesforce-cli-automation`), CPQ price-rule plugins (use `apex/cpq-apex-plugins`), OmniStudio DataPack CLI usage (use `omnistudio/omnistudio-deployment-datapacks`), or generic JS/TS package publishing.
Teams using sf-cli-plugin-authoring should expect a more consistent output, faster repeated execution, less prompt rewriting, better workflow continuity with your supporting tools.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
- You already have the supporting tools or dependencies needed by this skill.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/sf-cli-plugin-authoring/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How sf-cli-plugin-authoring Compares
| Feature / Agent | sf-cli-plugin-authoring | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use when authoring a custom Salesforce CLI (`sf`) plugin — internal team tooling that wraps deploy/test workflows, an ISV plugin distributed via npm, or migrating a legacy `sfdx`-style plugin to the v2 `sf` topic-and-command shape. Triggers: 'sf plugin generate', 'extend SfCommand class', 'oclif command structure', 'sf-plugins-core flags', 'topic separator sf v2', 'JSON output for plugin', 'plugin hooks prerun command_not_found', 'distribute internal sf plugin npm registry', 'sign Salesforce CLI plugin'. NOT for using existing `sf` plugins in CI automation (use `devops/salesforce-cli-automation`), CPQ price-rule plugins (use `apex/cpq-apex-plugins`), OmniStudio DataPack CLI usage (use `omnistudio/omnistudio-deployment-datapacks`), or generic JS/TS package publishing.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# sf CLI Plugin Authoring
Activate when a developer or platform team is building a **custom `sf` CLI plugin** — not consuming one. The deliverable is a TypeScript package whose `commands/` tree extends `SfCommand` from `@salesforce/sf-plugins-core`, exposes well-typed flags, emits a stable JSON shape under `--json`, and is distributable via `npm install`, `sf plugins install`, or `sf plugins link` for local development. This skill covers scaffold layout, the `SfCommand` lifecycle, flag patterns (`requiredOrg`, `requiredHub`, `string`, `directory`, `boolean`), JSON contracts, hooks, message catalogs, telemetry hand-off, signed-plugin distribution, and the two migration paths from sfdx-style plugins (colon-separated topics → space-separated, plus the v2 flag and behavior changes).
This skill is not for choosing a CI platform, debugging an existing third-party plugin, or scripting against the stock `sf` commands — those belong to `devops/salesforce-cli-automation`. It is also not for CPQ "plugin" interfaces (`apex/cpq-apex-plugins`) or OmniStudio DataPack tooling (`omnistudio/omnistudio-deployment-datapacks`), which share the word "plugin" but are unrelated technologies.
---
## Before Starting
Gather this context before any code is written:
- **Why a plugin and not a script?** A shell script that invokes stock `sf` commands is the right answer for one-off team automation. A plugin earns its weight when (a) the same logic is invoked from many pipelines and benefits from a single update path, (b) the JSON contract for callers needs to be stable across versions, (c) the workflow composes existing `sf` commands plus custom logic that benefits from typed flags and `--json` plumbing, or (d) it is being distributed to customers as part of an ISV offering. If none of those apply, write a script and stop here.
- **`sf` topic separator is a space, not a colon.** v2 commands look like `sf my topic my command`. The legacy `sfdx force:apex:test:run` style is colon-separated. A plugin authored for v2 must declare `topicSeparator: " "` in `package.json` `oclif.topics` configuration, and command files belong in nested directories matching the topic tree.
- **Stay on `@salesforce/sf-plugins-core`, not `@oclif/core` directly.** `SfCommand` extends `Command` from oclif but adds the JSON wrapper, telemetry hooks, prompt helpers (`this.confirm`, `this.prompt`), spinner integration, and the standard `--json` and `--flags-dir` behavior. Authoring directly against `@oclif/core` reinvents these — and breaks the JSON contract callers rely on.
- **Decide the org-flag posture up front.** A command that requires an authenticated org uses `Flags.requiredOrg()`. A command that targets a Dev Hub for scratch-org work uses `Flags.requiredHub()`. A utility command (e.g., a metadata transform that runs offline) declares no org flag. Mixing these midway through development requires re-templating the command class.
- **Plan the JSON contract before the human output.** `sf` invocations from CI parse `--json` output. Every property of the typed `Result` interface returned from `run()` becomes part of the public contract. Renaming or restructuring fields after release is a breaking change.
---
## Core Concepts
### Project layout from the template
The canonical scaffold is generated by `sf dev generate plugin` (which forks `salesforcecli/plugin-template-sf` for internal Salesforce-owned plugins or `plugin-template-sf-external` for community/ISV plugins). The result:
```text
my-plugin/
├── package.json # oclif config, sf-plugins-core peer dep, scripts
├── bin/
│ ├── dev.js # local dev runner (ts-node + register hooks)
│ └── run.js # production runner (compiled JS)
├── messages/
│ └── mytopic.mycommand.md # localizable summary, description, examples, errors
├── src/
│ ├── commands/
│ │ └── mytopic/
│ │ └── mycommand.ts # the command class
│ └── hooks/ # optional prerun, postrun, command_not_found
├── test/
│ └── commands/
│ └── mytopic/
│ └── mycommand.nut.ts # NUT (non-unit, end-to-end) tests
└── tsconfig.json
```
`messages/` is loaded by `Messages.loadMessages('my-plugin', 'mytopic.mycommand')`. Strings live in markdown headings (`# summary`, `# description`, `# examples`, `# flags.target-org.summary`) so translators and reviewers can find them without grepping code.
### `SfCommand` lifecycle
A minimal command:
```ts
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('my-plugin', 'mytopic.mycommand');
export type MycommandResult = {
recordsFound: number;
ids: string[];
};
export default class Mycommand extends SfCommand<MycommandResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static readonly flags = {
'target-org': Flags.requiredOrg(),
name: Flags.string({
summary: messages.getMessage('flags.name.summary'),
char: 'n',
required: true,
}),
};
public async run(): Promise<MycommandResult> {
const { flags } = await this.parse(Mycommand);
const conn = flags['target-org'].getConnection();
const result = await conn.query<{ Id: string }>(
`SELECT Id FROM Account WHERE Name LIKE '${flags.name}%' LIMIT 200`
);
this.log(`Found ${result.totalSize} records.`);
return { recordsFound: result.totalSize, ids: result.records.map(r => r.Id) };
}
}
```
Key behaviors:
- The generic parameter `<MycommandResult>` is what `--json` emits under the `result` key. Always type it.
- `this.parse(Mycommand)` returns flags and args parsed against the static schema. Do not access `this.argv` directly.
- `this.log()`, `this.warn()`, `this.error()` are buffered. In `--json` mode they are suppressed and replaced by structured fields. Never `console.log()` directly — it leaks into the JSON stream and breaks parsers.
- The return value of `run()` becomes the `result` field in `--json` output. The wrapping object also includes `status`, `warnings`, and (on failure) `name`, `message`, `stack`, `actions`.
### Flag patterns
`@salesforce/sf-plugins-core` provides flag factories that wrap oclif's primitives with sf-specific validation and the standard `--flags-dir` machinery:
| Flag | When to use | Behavior |
|---|---|---|
| `Flags.requiredOrg()` | Command targets a single authenticated org | Resolves to an `Org` instance; respects `--target-org` alias, `target-org` config, and `SF_TARGET_ORG` env |
| `Flags.optionalOrg()` | Org is sometimes needed (e.g., a transform that can either read from disk or from an org) | Same resolution, but undefined if not provided |
| `Flags.requiredHub()` | Command targets a Dev Hub (scratch-org work) | Resolves to a `Org` instance; respects `--target-dev-hub` alias |
| `Flags.string({ ... })` | String input | Adds `summary`, `char` shortcut, `required`, validation |
| `Flags.boolean({ ... })` | Toggle | `--flag` / `--no-flag` |
| `Flags.directory({ exists: true })` | Path to existing directory | Validates existence pre-run |
| `Flags.file({ exists: true })` | Path to existing file | Same |
| `Flags.integer({ min, max })` | Numeric input with bounds | Validates range pre-run |
| `Flags.salesforceId({ length: 18 })` | sObject Id input | Validates 15/18-char Id format |
| `Flags.duration({ unit: 'minutes' })` | Wait/timeout flags | Returns a `Duration` object with helpful arithmetic |
| `Flags.orgApiVersion()` | API version override | Validates against the org's supported versions |
Avoid hand-rolling validation in `run()` — pre-run flag validation produces cleaner error messages with suggested actions, and works correctly in `--json` mode.
### JSON contract and `--json` semantics
The wrapper around `run()`'s return value is fixed:
```json
{
"status": 0,
"result": {
"recordsFound": 12,
"ids": ["001...", "001..."]
},
"warnings": []
}
```
On error (any uncaught throw or `this.error(...)` call):
```json
{
"status": 1,
"name": "MissingNameError",
"message": "The --name flag is required.",
"exitCode": 1,
"stack": "..."
}
```
Treat the `result` shape as a public API. Add fields freely; rename or restructure only on a major version bump and after deprecation warnings emitted via `this.warn(messages.getMessage('warning.deprecated-shape'))`. The `status` field is `0` on success, `1` on caught error, `2` on uncaught — do not invent your own status codes.
### Hooks
Hooks are functions that run at well-defined lifecycle points. The most useful for sf plugins:
- `prerun` — receives `{ Command, argv, config }`; runs before every command (yours or another plugin's, depending on registration scope). Used for licence checks, environment validation, telemetry.
- `postrun` — receives `{ Command, result, argv, config }`; runs after every command. Used for cleanup or success telemetry.
- `command_not_found` — runs when no command matches. Used to suggest similar commands; respect this hook rather than reinventing fuzzy-match.
Register in `package.json`:
```json
{
"oclif": {
"hooks": {
"prerun": "./lib/hooks/prerun"
}
}
}
```
Hooks should be **idempotent and fast**. A 200ms hook compounds across every command in CI.
---
## Common Patterns
### Pattern 1 — Wrapping a stock `sf` command with org-aware logic
**When to use:** Internal tooling that pre-validates org state (e.g., "before deploy, ensure no test classes are missing `@isTest`"), then delegates to `sf project deploy start`.
**How it works:** Implement the command, run validation against the org via `flags['target-org'].getConnection()`, and shell out to `sf` for the actual deploy with `child_process.execFile` capturing JSON. Re-emit a merged result containing your validation summary plus the deploy outcome.
```ts
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
const exec = promisify(execFile);
const { stdout } = await exec('sf', ['project', 'deploy', 'start', '--json', '--target-org', flags['target-org'].getUsername()!]);
const deployResult = JSON.parse(stdout);
return { validationFindings: findings, deploy: deployResult.result };
```
**Why not the alternative:** A bash wrapper script can't return a typed JSON contract; downstream pipelines parse plain stdout and break on warning messages.
### Pattern 2 — Migrating an `sfdx` plugin to `sf` v2 with deprecation aliases
**When to use:** An ISV or internal plugin currently exposes `sfdx mytopic:mycommand`; consumers' CI scripts will break if the legacy name is removed wholesale.
**How it works:** In v2, command files live at `src/commands/mytopic/mycommand.ts`, invoked as `sf mytopic mycommand`. Add an alias entry to the command class:
```ts
public static readonly aliases = ['mytopic:mycommand'];
public static readonly deprecateAliases = true;
```
`deprecateAliases: true` emits a deprecation warning when the colon-style invocation is used, prompting consumers to migrate. Hold the alias for at least one major version. Document both spellings in `examples` so search hits match either.
### Pattern 3 — Distributing a private internal plugin
**When to use:** A platform team builds a plugin used only inside the organization; publishing to public npm is not appropriate.
**How it works:** Publish to a private registry (Artifactory, GitHub Packages, AWS CodeArtifact). In CI runner setup, configure `~/.npmrc` with the registry URL and auth token, then `sf plugins install @company/sf-plugin-name@1.4.0`. Pin the version — the CI image must have a deterministic plugin set.
For developer machines, prefer `sf plugins link <local-path>` during plugin development; this symlinks the working copy and avoids the install/uninstall churn while iterating.
**Why not the alternative:** Distributing the plugin via `git clone` plus `npm install` works locally but skips the `sf plugins` registry step that allows `sf plugins inspect` and unloading on conflict.
---
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| One-off team automation that just calls stock `sf` commands | Bash or Node script, not a plugin | Plugin overhead unjustified; no stable JSON contract needed |
| Repeated logic invoked from 3+ pipelines with typed JSON | Plugin extending `SfCommand` | Single update path, JSON contract stays stable |
| Needs to authenticate against a target org | `Flags.requiredOrg()` | Honors aliases, env vars, and config; consistent UX with stock commands |
| Targets a Dev Hub (scratch-org work) | `Flags.requiredHub()` | Distinct flag; clearer error when wrong org type used |
| ISV plugin distributed via AppExchange-adjacent npm | `plugin-template-sf-external` template | Includes signing, lint config, release workflow tuned for external consumers |
| Internal Salesforce-team plugin | `plugin-template-sf` (Salesforce-internal template) | Includes Salesforce-specific test infrastructure |
| Migrating from `sfdx` topic naming | Add `aliases` + `deprecateAliases: true` | Consumers transition gradually with warnings |
| Need to run logic before every command | `prerun` hook | Standard oclif lifecycle; respects `--json` mode |
| Need to react when no command matches | `command_not_found` hook | Better UX than silent failure |
---
## Recommended Workflow
1. Confirm a plugin is the right answer (see *Before Starting*); if a script suffices, write a script and stop.
2. Choose the template: `plugin-template-sf-external` for ISV/community plugins, `plugin-template-sf` for Salesforce-internal. Generate the scaffold with `sf dev generate plugin --name <plugin-name>`.
3. Decide the topic tree before writing code; the directory structure under `src/commands/` mirrors the runtime topic tree (`mytopic/mycommand.ts` becomes `sf mytopic mycommand`).
4. Write the JSON result type first — `type CommandResult = { ... }` — and treat it as the public contract from day one.
5. Implement the command class extending `SfCommand<CommandResult>`. Use `Flags.requiredOrg()` / `Flags.requiredHub()` / `Flags.string()` rather than parsing manually.
6. Write the messages markdown file (`messages/<command>.md`) for `summary`, `description`, `examples`, and any flag summaries.
7. Add at least one NUT test (`test/commands/<topic>/<command>.nut.ts`) that runs the real command end-to-end against a scratch org or mocked HTTP responses (`nock` for callouts).
8. Locally link the plugin with `sf plugins link .` and exercise both human and `--json` output. Verify `--json` output against the typed contract.
9. Update the README with the JSON shape spec and any required org permissions.
10. Publish (private or public registry); consumers install with `sf plugins install <name>@<version>`. Document the install line in the README.
---
## Review Checklist
Run through these before merging plugin changes:
- [ ] Every command class declares `<ResultType>` on `SfCommand<ResultType>` — no `any` or unparameterized returns.
- [ ] No `console.log` / `console.error` calls — use `this.log` / `this.warn` / `this.error` so `--json` mode stays clean.
- [ ] Every flag has a `summary` (and a `description` if behavior is non-obvious) sourced from the `messages/` catalog.
- [ ] The JSON return shape is documented in the README and unchanged from the previous patch version.
- [ ] If migrating from `sfdx`, command-level `aliases` array is set and `deprecateAliases: true` so consumers see a warning.
- [ ] At least one NUT test exercises the command; if the command makes HTTP callouts, they're mocked with `nock`.
- [ ] The plugin's `package.json` `oclif.topicSeparator` is `" "` (space), not `:` — v2 default.
- [ ] Hooks (if any) are idempotent and complete in <100ms or guarded by a feature flag.
- [ ] Private-registry plugins pin the version in CI runners; public-registry plugins follow semver discipline.
---
## Salesforce-Specific Gotchas
Non-obvious behaviors that bite real plugin authors:
1. **`console.log` from inside `run()` corrupts `--json` output.** A stray `console.log('debug')` is interleaved with the JSON envelope; downstream JSON parsers fail with `Unexpected token`. Use `this.log()` exclusively — it's suppressed in `--json` mode by `SfCommand`. The same trap fires for libraries that log to stdout (some crypto libraries, some HTTP clients in debug mode); audit transitive dependencies.
2. **`Flags.requiredOrg()` resolves the org before `run()` executes.** If the user has stale auth (`sf org logout` after token expiry) the failure happens during flag parsing, not in `run()`. Catch this in NUT tests by setting up a deliberately-stale auth and asserting the right error path. The error message is `NoAuthInfoFoundError`, not a generic auth failure.
3. **Plugin install order affects `command_not_found` hook resolution.** If two plugins register the same hook, the order is alphabetical by plugin name, not install order. Don't rely on "last installed wins" — that's an oclif core, not sf, behavior.
4. **`sf plugins link` does NOT recompile TypeScript on save.** The linked plugin runs from `lib/` (the compiled output), not `src/`. Either run `npm run build --watch` in a separate terminal or use `bin/dev.js` (which uses ts-node) for live iteration. Many "my changes aren't taking effect" reports trace to this.
5. **`oclif.topicSeparator` defaults to `:` in older `@oclif/core` versions.** A scaffold generated from a stale template will produce `sf mytopic:mycommand` and break v2 consumers. Confirm `package.json` sets `topicSeparator: " "` explicitly, even though it's the v2 default.
6. **Private npm registries need explicit auth in the CI runner image, not just the developer's `~/.npmrc`.** The runner's `npm install -g @scope/plugin` will silently fail if the registry token isn't configured; the failure surfaces only when the plugin's command is invoked and `command_not_found` fires. Pre-warm with `sf plugins install <name>` in the image build, not just in the pipeline step.
7. **`SfCommand` swallows `process.exit()` calls in `--json` mode.** Calling `process.exit(1)` inside `run()` bypasses the JSON envelope and emits an empty stdout — pipeline parsers receive zero bytes. Throw a typed error or call `this.error('msg', { exit: 1 })` instead; both go through the JSON wrapper.
8. **Version pinning in CI runner images vs. plugin install commands.** `sf plugins install @scope/plugin` (no version) installs latest; in CI this means a release on Tuesday silently changes pipeline behavior on Wednesday. Always pin: `sf plugins install @scope/plugin@1.4.0`.
---
## Output Artifacts
| Artifact | Description |
|---|---|
| Plugin scaffold | `package.json` + `src/commands/<topic>/<command>.ts` + `messages/<command>.md` + `bin/run.js` + `bin/dev.js` |
| JSON contract documentation | README section listing every field in the typed `Result`, with stability notes |
| NUT test | `test/commands/<topic>/<command>.nut.ts` exercising at minimum the success and one failure path |
| Migration map | If migrating from sfdx: table of `sfdx topic:command` → `sf topic command`, with deprecation timeline |
| Distribution runbook | Build, publish (registry-specific), install (`sf plugins install`), verify (`sf plugins inspect`) |
---
## Related Skills
- devops/salesforce-cli-automation — for consuming `sf` (including custom plugins) inside CI scripts
- apex/sf-cli-and-sfdx-essentials — foundational `sf` daily-use command coverage
- devops/salesforce-dx-project-structure — project layout the plugin's commands typically operate against
- devops/salesforce-code-analyzer — model for an officially-maintained sf plugin (good reference reading)Related Skills
cpq-apex-plugins
Use when implementing Salesforce CPQ plugin interfaces in Apex or JavaScript (JS QCP) to customize quote calculation, product search, order creation, contracting, or configuration screens. Trigger keywords: SBQQ plugin, QuoteCalculatorPlugin, ProductSearchPlugin, OrderPlugin, ContractingPlugin, ConfigurationInitializerPlugin, SBQQ__CustomScript__c, JS QCP, calculate callback, CPQ plugin registration. NOT for standard Apex triggers on SBQQ__Quote__c or SBQQ__QuoteLine__c. NOT for Flow-based customization of CPQ processes. NOT for declarative CPQ configuration such as price rules, discount schedules, or product rules that do not require code.
lightning-bolt-template-authoring
Use when an admin or partner needs to package an Experience Cloud (Community) site as a reusable Lightning Bolt Solution for distribution — covers the export workflow from Experience Builder, what gets bundled (ExperienceBundle, custom apps, flow categories, theme, layouts, navigation menus) versus what does NOT (data, CMS content, files), choosing Bolt vs managed package vs unlocked package vs cloning a site, sandbox-to-production promotion, multi-org distribution, AppExchange listing as a Bolt, and template versioning via the LightningBolt metadata `versionNumber`. Triggers: 'turn this community into a reusable template', 'package an Experience Cloud site to ship to multiple orgs', 'export Experience Builder template for AppExchange', 'should we use a Bolt or a managed package for this community', 'create an industry-specific community starter', 'how do we version our partner portal template', 'distribute branded Experience site across business units'. NOT for general Experience Cloud site build, content, or member setup (use admin/experience-cloud-site-setup, admin/experience-cloud-cms-content, admin/experience-cloud-member-management). NOT for shipping Apex / LWC / data-model functionality as a product (use devops/managed-package-development, devops/second-generation-managed-packages, devops/unlocked-package-development). NOT for moving a single Experience site between sandbox and prod as a one-off (use admin/experience-cloud-deployment-admin, devops/cicd-for-experience-cloud).
configuration-workbook-authoring
Author the Salesforce Configuration Workbook — the structured, reviewable handoff document an admin uses to execute a feature across Objects/Fields, Page Layouts, Profiles/PSGs, Sharing, Validation, Automation, List Views, Reports, Integrations, and Data. Triggers: 'salesforce configuration workbook', 'admin handoff document', 'implementation workbook'. NOT for object design itself (use admin/custom-field-creation, admin/lookup-and-relationship-design, agents/object-designer/AGENT.md), NOT for permission set design (use admin/permission-set-architecture, agents/permission-set-architect/AGENT.md), NOT for Flow construction (use skills/flow/* and agents/flow-builder/AGENT.md), and NOT for the deployment manifest (use skills/devops/metadata-api-retrieve-deploy).
xss-and-injection-prevention
Use when writing or reviewing Visualforce pages, Apex controllers, or LWC components that output user-supplied data, build dynamic queries, or construct HTTP responses. Triggers: 'XSS in Visualforce', 'SOQL injection vulnerability', 'how to encode output in Apex', 'JSENCODE Visualforce', 'open redirect prevention'. NOT for Apex CRUD/FLS enforcement (use soql-security or apex-crud-and-fls), NOT for Shield encryption (use shield-encryption-key-management), NOT for AppExchange security review process (use secure-coding-review-checklist).
visualforce-security-and-modernization
Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).
transaction-security-policies
Transaction Security policy creation and configuration: condition builder, enhanced policies, enforcement actions (block, MFA, notification, end session), real-time monitoring mode, and policy troubleshooting. NOT for Event Monitoring log analysis or Shield Event Monitoring setup (use event-monitoring). NOT for Apex testing or debug-log analysis.
sso-saml-troubleshooting
Diagnosing broken SAML SSO into Salesforce — IdP-initiated vs SP-initiated flows, signing-certificate validity / expiry, NameID format mismatches, RelayState handling, audience / entityId / issuer mismatches, clock skew, the SAML Assertion Validator in Setup, the Login History debug log, and the My Domain prerequisite for SSO. Covers the standard diagnostic loop: read the SAML response, identify which check failed, fix at the IdP or SP. NOT for OAuth / OpenID Connect SSO (see security/oauth-openid-troubleshooting), NOT for setting up SSO from scratch (see security/sso-saml-setup).
shield-kms-byok-setup
Configure Shield Platform Encryption with customer-supplied (BYOK) or customer-held (Cache-Only Key Service) tenant secrets, rotate them, and recover. NOT for Classic Encryption or field masking.
shield-event-log-retention-strategy
Use when designing Salesforce Shield Event Monitoring retention, SIEM routing, and storage-tier strategy — which event types to keep, for how long, where, and how to answer audit queries across hot/warm/cold tiers. Triggers: 'shield event log retention', 'route event monitoring to splunk', 'how long to keep login history', 'siem salesforce integration', 'event monitoring storage tier'. NOT for enabling Shield (see salesforce-shield-deployment).
session-management-and-timeout
Use this skill when configuring session timeout values, concurrent session limits, session IP locking, or logout behavior in Salesforce. Covers org-wide session settings, profile-level overrides, Connected App session policies, and Metadata API SecuritySettings deployment. NOT for OAuth token refresh flows, login IP ranges, or MFA/identity-provider configuration.
session-high-assurance-policies
Enforce step-up authentication for sensitive pages/objects using High Assurance session level and login flow policies. NOT for initial MFA enrollment UX.
service-account-credential-rotation
Use when designing credential rotation for integration users, connected apps, named credentials, and OAuth client secrets in Salesforce. Covers rotation cadence, zero-downtime handover, secret storage, and detection of stale credentials. Triggers: 'rotate integration user password', 'connected app secret rotation', 'named credential rotation', 'stale service account', 'zero downtime secret rotation'. NOT for end-user password policies.