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.

Best use case

cpq-apex-plugins is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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.

Teams using cpq-apex-plugins should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

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

$curl -o ~/.claude/skills/cpq-apex-plugins/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/cpq-apex-plugins/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/cpq-apex-plugins/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How cpq-apex-plugins Compares

Feature / Agentcpq-apex-pluginsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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.

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

# CPQ Apex Plugins

This skill covers implementing Salesforce CPQ plugin interfaces — both Apex-based and JavaScript Quote Calculator Plugin (JS QCP) — that let practitioners intercept and customize CPQ's calculation engine, configurator, order creation, and contracting flows. Activate when a business requirement cannot be met by declarative CPQ features alone and a coded plugin hook is required.

---

## Before Starting

Gather this context before working on anything in this domain:

- **Identify the plugin type needed.** Nine distinct plugin interfaces exist in CPQ. Selecting the wrong type causes the customization to never fire or to conflict with other plugins. The plugin type determines the interface, the method signatures, and the registration field in CPQ Settings.
- **Check for an active JS QCP.** Only one calculator plugin type — Apex `QuoteCalculatorPlugin` OR a JavaScript QCP stored in `SBQQ__CustomScript__c` — can be active at a time. Mixing both simultaneously causes unpredictable calculation behavior. Query `SELECT Id, SBQQ__Active__c FROM SBQQ__CustomScript__c WHERE SBQQ__Active__c = true` to confirm.
- **Confirm the CPQ package version.** Plugin interfaces evolved across managed package versions. The JS QCP with its seven hooks (`onInit`, `onBeforeCalculate`, `onAfterCalculate`, `onBeforePriceRules`, `onAfterPriceRules`, `onBeforeCalculatePrices`, `onAfterCalculatePrices`) is the modern replacement for the legacy Apex `QuoteCalculatorPlugin`. Orgs on older package versions may not expose all hooks.
- **Know the governor limits context.** Plugin methods execute synchronously inside the CPQ calculation engine's managed package context. Callouts from synchronous plugin methods are not permitted. Use the `SBQQ.CalculateCallback` interface for any calculation that needs async Apex.

---

## Core Concepts

### Plugin Type Taxonomy

CPQ exposes nine distinct plugin interfaces. Each targets a different extension point:

| Plugin Interface | Apex Namespace | Primary Purpose |
|---|---|---|
| `QuoteCalculatorPlugin` | `SBQQ` | Override quote line pricing and totals (legacy — prefer JS QCP) |
| JS QCP (`SBQQ__CustomScript__c`) | JavaScript | Modern calculator hook with 7 lifecycle events |
| `ProductSearchPlugin` | `SBQQ` | Filter or reorder product catalog search results |
| `QuoteTermPlugin` | `SBQQ` | Generate or transform quote terms before finalization |
| `ProductRulePlugin` | `SBQQ` | Extend product rule evaluation logic |
| `ConfigurationAttributePlugin` | `SBQQ` | Custom behavior for configuration attribute changes |
| `ConfigurationInitializerPlugin` | `SBQQ` | Pre-populate options before configurator screen loads |
| `OrderPlugin` | `SBQQ` | Hook into order creation from a quote |
| `ContractingPlugin` | `SBQQ` | Hook into contract creation from an order |

The `CpqPlugin` interface is the base that all Apex plugins extend — never implement it directly.

### JS QCP vs. Legacy Apex QuoteCalculatorPlugin

The **JS QCP** (`SBQQ__CustomScript__c`) is the current supported approach for calculator customization. It stores JavaScript code in the `SBQQ__Code__c` field of an `SBQQ__CustomScript__c` record. The record must have `SBQQ__Active__c = true` and be associated with a quote template or set as the default script.

JS QCP exposes seven named hooks, each receiving `(quoteModel, quoteLineModels, conn)`:

1. `onInit` — fires when the calculator initializes; use to set default field values
2. `onBeforeCalculate` — fires before the calculation engine runs; use to pre-process line fields
3. `onAfterCalculate` — fires after calculation; use to post-process totals
4. `onBeforePriceRules` — fires before CPQ evaluates price rules
5. `onAfterPriceRules` — fires after price rules complete
6. `onBeforeCalculatePrices` — fires before each pricing pass
7. `onAfterCalculatePrices` — fires after each pricing pass

Each hook must return a resolved `Promise`. Returning nothing or a non-Promise causes the calculation to hang indefinitely.

The **legacy Apex `QuoteCalculatorPlugin`** implements `SBQQ.QuoteCalculatorPlugin` with `calculate(quoteLines, callback)`. It is still functional but not recommended for new development. It cannot coexist with an active JS QCP.

### Plugin Registration

Each plugin type has a dedicated registration field on the CPQ Settings record (`SBQQ__CustomActionSettings__c` or the CPQ package settings object). The Apex class name (fully qualified, no namespace prefix for the developer's own code) is entered in the corresponding field:

- **Quote Calculator Plugin:** `SBQQ__QuoteCalculatorPlugin__c` on `SBQQ__CustomActionSettings__c`
- **Product Search Plugin:** `SBQQ__ProductSearchPlugin__c`
- **Order Plugin:** `SBQQ__OrderPlugin__c`
- **Contracting Plugin:** `SBQQ__ContractingPlugin__c`
- **Configuration Initializer Plugin:** `SBQQ__InitializerPlugin__c`

For the JS QCP, no Settings field is used — the active `SBQQ__CustomScript__c` record drives activation.

### Async Calculations with CalculateCallback

Apex plugin methods that need to perform callouts or DML before returning results must use the `SBQQ.CalculateCallback` interface. The plugin's `calculate` method receives a callback object; the implementation invokes `callback.run(quoteLines)` to signal completion. This prevents synchronous blocking of the CPQ engine but adds complexity — errors thrown after `callback.run()` do not surface cleanly.

---

## Common Patterns

### Pattern 1: JS QCP for Custom Pricing Logic

**When to use:** The business requires pricing logic that reads external data (via JS `fetch` to a Salesforce endpoint), evaluates complex conditional pricing not expressible in price rules, or needs access to quote-level aggregates mid-calculation.

**How it works:**

1. Author a JavaScript object with the required hook functions exported as named exports.
2. Store the code in `SBQQ__CustomScript__c.SBQQ__Code__c`. Set `SBQQ__Active__c = true`.
3. Each hook receives `quoteModel` (the quote record + fields) and `quoteLineModels` (array of quote line records + fields).
4. Return a resolved `Promise` from each hook, even if no changes are made (`return Promise.resolve()`).
5. To modify a line, mutate properties on the `quoteLineModels` entries; CPQ reads them back after the hook resolves.

**Why not Apex triggers:** Apex triggers on `SBQQ__QuoteLine__c` fire outside the CPQ calculation transaction. CPQ recalculates on save and overwrites any values set by triggers during the calculation pass, creating an infinite loop or silent data loss.

### Pattern 2: Apex OrderPlugin for Custom Order Line Logic

**When to use:** The business requires Apex logic (DML on custom objects, platform events, callouts) to run immediately when CPQ converts a quote to an order. No JS equivalent exists for OrderPlugin.

**How it works:**

1. Create an Apex class implementing `SBQQ.OrderPlugin`.
2. Override `onBeforeInsert(List<Order> orders, SBQQ.DefaultOrderProduct defaultOrderProduct, Database.UnitOfWork uow)` and/or `onAfterInsert`.
3. Use the provided `uow` (Unit of Work) to register new records. Do not perform DML directly — register work through the UoW so CPQ's transaction management controls commit order.
4. Enter the class name in **CPQ Settings > Order Management > Order Plugin**.

**Why not a trigger:** Order triggers may fire multiple times during CPQ's order creation sequence. The `OrderPlugin` gives deterministic pre/post hooks with access to CPQ's internal Unit of Work.

### Pattern 3: ConfigurationInitializerPlugin to Pre-Select Options

**When to use:** The configuration screen must open with specific product options already selected or specific attribute values pre-populated based on the opportunity, account segment, or another quote field.

**How it works:**

1. Create an Apex class implementing `SBQQ.ConfigurationInitializerPlugin`.
2. Override `initialize(SBQQ.ProductModel productModel, String configurationId)`.
3. Mutate `productModel` to set default option quantities or attribute values.
4. Register the class in CPQ Settings under the Configuration section.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| New calculator customization, org on modern CPQ package | JS QCP (SBQQ__CustomScript__c) | Modern supported approach; seven lifecycle hooks; no Apex compilation required |
| Existing Apex QuoteCalculatorPlugin, need to extend | Extend existing Apex plugin OR migrate to JS QCP | Cannot mix; extending keeps one active type; migration preferred long-term |
| Logic needed during order creation | Apex OrderPlugin | No JS equivalent exists for OrderPlugin |
| Logic needed during contract creation | Apex ContractingPlugin | No JS equivalent exists for ContractingPlugin |
| Custom product catalog filtering | Apex ProductSearchPlugin | Exposes `search()` returning filtered SObject lists |
| Pre-populate configurator options | Apex ConfigurationInitializerPlugin | Fires before the configuration screen loads |
| Pricing logic expressible as price rules | Declarative price rules (no plugin) | Plugins add deployment and maintenance cost; use declarative where sufficient |
| Async callout needed inside calculator | Apex QuoteCalculatorPlugin + SBQQ.CalculateCallback | Only Apex plugin + callback supports async patterns; JS QCP can use JS Promises with fetch |

---

## Recommended Workflow

1. **Identify which plugin type is required.** Match the business requirement against the plugin taxonomy table. Confirm with the requester whether the hook fires in the right lifecycle phase (e.g., before vs. after price rules).
2. **Check for conflicting active plugins.** Query `SBQQ__CustomScript__c` for active JS QCP records and inspect CPQ Settings registration fields. Identify any existing Apex plugin of the same type before writing a new one.
3. **Implement the plugin class or JS QCP code.** Follow the exact method signatures from the CPQ Plugins Developer Guide. For Apex, ensure the class is `global` and `with sharing` unless the plugin needs system-mode access for a documented reason. For JS QCP, ensure every hook returns a `Promise`.
4. **Register the plugin in CPQ Settings.** Enter the Apex class name in the correct Settings field, or activate the `SBQQ__CustomScript__c` record. Verify no other plugin of the same type is registered.
5. **Test in a sandbox with full CPQ calculation flows.** Add a line, change quantity, apply discounts, and save the quote. Verify the plugin fires at the expected lifecycle point using debug logs with `SBQQ` category at `FINEST` level.
6. **Validate governor limit headroom.** Review debug logs for CPU time and SOQL counts inside the plugin execution. CPQ's own calculation logic consumes significant limits; plugin code must be lean.
7. **Deploy and verify post-deployment.** Confirm the CPQ Settings record retains the plugin registration after deployment (Settings records are not metadata — they live in the org's data layer and must be set manually or via a post-install script).

---

## Review Checklist

- [ ] Plugin interface implemented exactly matches the CPQ interface for the chosen plugin type (correct namespace, method signatures)
- [ ] Only one calculator plugin type is active (JS QCP or Apex QuoteCalculatorPlugin, not both)
- [ ] Every JS QCP hook function returns a resolved or rejected `Promise` — no hook returns `undefined` or `void`
- [ ] Apex plugin class is declared `global` (required by managed package interface)
- [ ] Plugin is registered in CPQ Settings in the correct field, not just deployed as metadata
- [ ] Debug logs confirmed the plugin fires at the expected calculation lifecycle point
- [ ] Governor limits (CPU, SOQL, heap) reviewed in debug logs under realistic quote line counts
- [ ] No Apex triggers on `SBQQ__Quote__c` or `SBQQ__QuoteLine__c` performing DML that conflicts with the plugin

---

## Salesforce-Specific Gotchas

1. **CPQ Settings is data, not metadata** — Plugin registration fields on `SBQQ__CustomActionSettings__c` are stored as org data, not in a `.settings` metadata file. A change set or package deployment does not carry these values. After every deployment, you must manually update or script the Settings record to re-register the plugin class name.

2. **JS QCP hooks that return `undefined` hang the calculator indefinitely** — CPQ's calculation engine awaits the Promise returned by each hook. If a hook function has no explicit return statement, it returns `undefined` rather than a resolved Promise. The quote save UI spins without error until the user's session times out. Always end every hook with `return Promise.resolve()` even when no changes are made.

3. **Apex triggers on SBQQ__QuoteLine__c overwrite values set during calculation** — CPQ performs multiple recalculation passes on save. An `after update` trigger that sets a field on `SBQQ__QuoteLine__c` runs between passes; CPQ's next pass reads the stored line and overwrites the field with its calculated value. The trigger's changes appear to stick briefly then revert. Use JS QCP or Apex plugin hooks — not triggers — for any field that CPQ owns.

4. **Only the first registered plugin of each type is called** — CPQ does not chain multiple plugins of the same type. If `SBQQ__OrderPlugin__c` is already set to `ExistingOrderPlugin`, registering a second class `NewOrderPlugin` in the same field simply replaces the first. There is no multi-plugin chaining mechanism; you must combine logic into a single class or use a dispatcher pattern.

5. **`global` access modifier is mandatory on Apex plugin classes** — CPQ plugin interfaces are defined inside the managed `SBQQ` namespace. Implementing a managed-package interface requires the implementing class to be `global`. An `public` implementing class compiles successfully in a scratch org or sandbox but throws a runtime `System.TypeException` when CPQ tries to instantiate it through the interface.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| Apex plugin class | `global class MyPlugin implements SBQQ.<PluginInterface>` with all required methods implemented |
| JS QCP script | JavaScript object with named hook functions stored in `SBQQ__CustomScript__c.SBQQ__Code__c` |
| CPQ Settings registration steps | Which field to update and what value to enter for the specific plugin type |
| Plugin test plan | Ordered list of CPQ UI actions that confirm the plugin fires and produces correct output |

---

## Related Skills

- `apex/quote-pdf-customization` — Custom VF-based CPQ quote PDF controllers; separate extension point from the CPQ plugin framework covered here
- `admin/cpq-pricing-rules` — Declarative price rules; evaluate these before reaching for a calculator plugin
- `admin/cpq-quote-templates` — Quote template configuration; understand the template-level JS QCP association before authoring plugin code

Related Skills

apex-managed-sharing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.

lwc-imperative-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Call Apex methods imperatively from LWC — on button click, lifecycle hooks, or conditional logic. Covers import syntax, cacheable vs non-cacheable, async/await patterns, error handling, loading states, and Promise.all. NOT for wire service (use wire-service-patterns) and NOT for testing Apex mocks (use lwc-testing).

dataweave-for-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when transforming structured data inside Apex — CSV → JSON, XML → SObject list, JSON → flattened CSV, or schema-mapping a third-party payload to a Salesforce model — and the existing options (`JSON.deserialize`, `Dom.Document`, hand-written loops) are getting unwieldy. Triggers: 'apex transform csv json xml without external library', 'system.dataweave script', 'salesforce native dataweave apex execute', 'transform xml to sobject apex no mulesoft', 'json reshape salesforce apex script'. NOT for MuleSoft Anypoint DataWeave running off-platform (use mulesoft-anypoint-architecture), NOT for Apex JSON serialization basics (use apex-json-serialization), NOT for Bulk API CSV ingest (use bulk-api-2-patterns).

flow-invocable-from-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Author @InvocableMethod Apex classes that Flow can call as Actions. Design the input / output variable contract, bulk semantics (one list in, one list out), null handling, and error surfacing. Also covers the inverse direction: calling a flow from Apex via Flow.Interview. NOT for general Apex authoring (use apex-service-selector-domain). NOT for REST-exposed Apex (use apex-rest-resource-patterns).

flow-apex-defined-types

8
from PranavNagrecha/AwesomeSalesforceSkills

Design and use Apex-Defined Types as Flow variables for structured non-sObject data (HTTP callout payloads, External Service responses, complex configuration). Trigger keywords: apex-defined type, flow variable, @AuraEnabled class, flow http callout response. Does NOT cover building HTTP Callout Actions themselves, External Services schema, or raw Apex invocable methods.

scheduled-apex-failure-detection-and-monitoring

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when nightly batch / scheduled Apex jobs are failing without anyone noticing — covers why uncaught exceptions in `execute()` go to the debug log instead of email, how to query `AsyncApexJob` for `Status`, `NumberOfErrors`, and `ExtendedStatus`, when to implement `Database.RaisesPlatformEvents` so the platform publishes `BatchApexErrorEvent` on uncaught failures, how to subscribe to that event with an Apex trigger and notify operators, and how to layer a custom watcher schedule on top so silent-failure modes (job that never started, scheduled class deleted, queue stuck on `Queued`) still surface. Triggers: 'nightly batch failed at 2am with no notification', 'how do we know if a scheduled apex job is failing', 'BatchApexErrorEvent vs custom retry logic', 'Setup Apex Jobs only shows last 7 days, where else can I look', 'job is stuck in queued status nobody noticed for a week'. NOT for general Apex exception handling patterns (use apex/apex-exception-handling-and-logging), NOT for Batch Apex authoring or chunking strategy (use apex/batch-apex-design), NOT for Setup → Apex Jobs UI walkthrough as an admin task (use admin/batch-job-scheduling-and-monitoring), NOT for retry logic itself (use apex/scheduled-apex-retry-patterns once authored).

platform-events-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when publishing or subscribing to Salesforce Platform Events from Apex, comparing Platform Events with Change Data Capture, or designing event-triggered error handling and monitoring. Triggers: 'EventBus.publish', 'platform event trigger', 'CDC vs Platform Events', 'replay ID', 'high-volume event'. NOT for Flow-only publish/subscribe automation.

health-cloud-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when extending Health Cloud via Apex: implementing HealthCloudGA managed-package interfaces, automating care plan lifecycle hooks, processing referrals using Industries Common Components invocable actions, or enforcing HIPAA-compliant logging governance for clinical Apex code. Trigger keywords: CarePlanProcessorCallback, HealthCloudGA namespace, ReferralRequest, ReferralResponse, care plan invocable actions, clinical Apex extension, Health Cloud Apex API. NOT for standard Apex triggers or generic Apex development unrelated to Health Cloud managed-package extension points.

fsl-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when writing Apex that calls Field Service Lightning scheduling APIs — AppointmentBookingService, ScheduleService, GradeSlotsService, or OAAS — to book, schedule, grade, or optimize service appointments programmatically. Trigger keywords: FSL Apex namespace, GetSlots, schedule service appointment via code, appointment booking API, FSL optimization API. NOT for standard Apex patterns unrelated to FSL, admin-level scheduling policy configuration, or declarative FSL scheduling.

fsc-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when extending Financial Services Cloud (FSC) behavior through Apex: customizing financial rollup recalculation, disabling built-in FSC triggers to write custom trigger logic, implementing Compliant Data Sharing (CDS) participant/role integrations, or building custom FSC action handlers. NOT for standard Apex unrelated to the FSC managed package, standard Salesforce sharing rules, or configuring FSC rollups through the Admin UI — use the admin/financial-account-setup skill for declarative rollup setup.

entitlement-apex-hooks

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when writing Apex triggers or classes that interact with CaseMilestone records — completing milestones, detecting violations, or reacting to SLA state changes. Trigger keywords: CaseMilestone trigger, auto-complete milestone Apex, milestone violation polling, CompletionDate write pattern. NOT for entitlement process admin setup, milestone configuration in Setup UI, or Flow-based milestone actions.

dynamic-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when building or reviewing code that constructs SOQL/SOSL at runtime, inspects schema metadata via Schema.describe methods, accesses fields dynamically on sObjects, or performs runtime type inspection. Triggers: 'Database.query', 'Schema.getGlobalDescribe', 'Schema.describeSObjects', 'dynamic field access', 'SObjectType', 'DescribeFieldResult'. NOT for static SOQL queries or query performance tuning — use soql-fundamentals or soql-query-optimization.