SfSkills Domains Docs GitHub

Home / data / cpq-data-model

cpq-data-model

Use when querying, mapping, or building against Salesforce CPQ managed-package objects (SBQQ__ namespace): Quote, QuoteLine, QuoteLineGroup, DiscountSchedule, PriceRule, Subscription. Trigger keywords: SBQQ__Quote__c, QuoteLineModel, CPQ object graph, CPQ subscription, CPQ price rule. NOT for driving quote calculation, amend or renew from code — use apex/cpq-api-and-automation. NOT for bulk-loading the CPQ product catalog — use data/product-catalog-migration-cpq.

Open the skill folder on GitHub


CPQ Data Model

This skill activates when work involves Salesforce CPQ managed-package objects in the SBQQ__ namespace — quoting, pricing, contracting, or renewal. It provides the complete SBQQ__ object graph, explains why standard Quote/QuoteLineItem is not the quoting layer in CPQ orgs, and defines when programmatic operations must use the CPQ Quote API instead of direct DML.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

The SBQQ__ Object Graph

Salesforce CPQ installs a self-contained managed-package object graph with the SBQQ__ namespace prefix. The top-level quoting object is SBQQ__Quote__c, not Quote. Every downstream CPQ object rolls up to it.

Primary quoting objects:

Pricing and discount objects:

Contract and subscription objects:

CPQ Quote API: QuoteModel and QuoteLineModel

For any programmatic write to a CPQ quote — adding lines, changing quantities, applying discounts — the CPQ Quote API must be used instead of direct DML. The API is invoked via a SBQQ namespace Apex class call or via the REST endpoint https://<instance>/services/apexrest/SBQQ/ServiceRouter.

The typical read-calculate-save flow:

  1. Call SBQQ.QuoteService.read(quoteId) to retrieve a QuoteModel.
  2. Mutate inputs (quantities, discounts) on the QuoteLineModel objects inside.
  3. Call SBQQ.QuoteService.calculate(quoteModel, callbackClass) to trigger the pricing engine asynchronously.
  4. In the callback, call SBQQ.QuoteService.save(quoteModel) to persist the calculated output back to SBQQ__Quote__c and SBQQ__QuoteLine__c.

Skipping the calculate step and writing directly to SBQQ__NetPrice__c corrupts the quote because rollup formulas and approval thresholds depend on engine-calculated values.

Contract-to-Subscription Relationship

When a CPQ quote is contracted:

  1. The Opportunity is marked Closed Won with a primary quote set (SBQQ__Primary__c = true on SBQQ__Quote__c).
  2. The "Contract" action creates a standard Contract record and links it to the quote via a custom field.
  3. For each SBQQ__QuoteLine__c with a subscribed product, one SBQQ__Subscription__c is created. It stores SBQQ__StartDate__c, SBQQ__EndDate__c, SBQQ__Quantity__c, and SBQQ__NetPrice__c from the contracted line.
  4. Renewals are generated from SBQQ__Subscription__c records, not from the original quote lines. The renewal quote's lines pull SBQQ__RenewalPrice__c from each subscription.

Common Patterns

Pattern: Query CPQ Quote with Lines for Reporting

When to use: Read-only reports, validations, or integrations that need the full quote header and line details without triggering recalculation.

How it works:

SELECT
    Id,
    Name,
    SBQQ__Opportunity2__c,
    SBQQ__NetAmount__c,
    SBQQ__StartDate__c,
    SBQQ__EndDate__c,
    SBQQ__SubscriptionTerm__c,
    SBQQ__Status__c,
    (
        SELECT
            Id,
            SBQQ__ProductName__c,
            SBQQ__Quantity__c,
            SBQQ__ListPrice__c,
            SBQQ__CustomerPrice__c,
            SBQQ__NetPrice__c,
            SBQQ__Discount__c,
            SBQQ__Group__c
        FROM SBQQ__LineItems__r
    )
FROM SBQQ__Quote__c
WHERE SBQQ__Opportunity2__c = :opportunityId

Why not the alternative: Querying Quote and QuoteLineItem returns standard quote objects. In a CPQ org the standard Quote may not exist or may have stale synced data. Pricing always lives on SBQQ__Quote__c.

Pattern: Programmatic Quote Line Update via CPQ Quote API

When to use: Apex automation that must change quantity or discount on an existing CPQ quote and have prices correctly recalculated.

How it works:

// 1. Read the quote into a QuoteModel
SBQQ.QuoteModel quoteModel = SBQQ.QuoteService.read(quoteId);

// 2. Mutate the first line quantity
quoteModel.lineItems[0].record.SBQQ__Quantity__c = 5;

// 3. Calculate (async — provide a callback class name)
String quoteJSON = JSON.serialize(quoteModel);
SBQQ.QuoteService.calculate(quoteJSON, 'MyQuoteCalculatorCallback');

// 4. In the callback implementation:
// global class MyQuoteCalculatorCallback implements SBQQ.QuoteCalculatorPlugin {
//     global void onAfterCalculate(SBQQ.QuoteModel quote) {
//         SBQQ.QuoteService.save(quote);
//     }
// }

Why not the alternative: Direct DML update new SBQQ__QuoteLine__c(Id=..., SBQQ__Quantity__c=5) will save the quantity but will NOT recalculate SBQQ__NetPrice__c, SBQQ__CustomerPrice__c, or the quote-level SBQQ__NetAmount__c. The quote totals become stale and approval rules may fire incorrectly.


Decision Guidance

SituationRecommended ApproachReason
Read CPQ quote data for a report or integrationSOQL on SBQQ__Quote__c / SBQQ__QuoteLine__cDirect query is safe for reads; no pricing engine involved
Update quantity or discount on an existing quoteCPQ Quote API (QuoteModel/QuoteLineModel)Ensures pricing engine recalculates all derived fields
Create a net-new quote programmaticallyCPQ Quote API with SBQQ.QuoteService.save()Required for product configuration and bundle resolution
Query contracted subscriptions for renewalSOQL on SBQQ__Subscription__c with SBQQ__Contract__c lookupSubscriptions, not quote lines, are the source of truth for active terms
Check if a product has volume discountsQuery SBQQ__DiscountSchedule__c and SBQQ__DiscountTier__cDiscount schedules are managed-package objects, not standard pricebook entries
Apply conditional pricing logicConfigure SBQQ__PriceRule__c with conditions and actionsPrice rules fire during the CPQ save event; do not implement pricing in Apex triggers

Step-by-step instructions for an AI agent or practitioner working on this task:

  1. Confirm CPQ is installed — Verify the SBQQ__ namespace exists in the org (check Installed Packages or run a SOQL query on SBQQ__Quote__c). Do not proceed with SBQQ__ object references if CPQ is not installed.
  2. Identify the operation type — Determine whether the task is read-only (SOQL), programmatic write (CPQ Quote API), contract creation, or renewal. Each path has different object entry points and constraints.
  3. Map the relevant objects — For quoting work, start at SBQQ__Quote__c. For pricing logic, check SBQQ__PriceRule__c and SBQQ__DiscountSchedule__c. For subscription/renewal work, start at SBQQ__Subscription__c joined to Contract.
  4. Avoid direct DML on calculated fields — If the task involves changing prices, quantities, or discounts, route through the CPQ Quote API (SBQQ.QuoteService.read, calculate, save). Flag any direct DML on SBQQ__NetPrice__c, SBQQ__CustomerPrice__c, or SBQQ__RegularPrice__c as a defect.
  5. Validate field-level security for CPQ objects — CPQ objects require separate permission set assignments. Confirm the running user has access to the relevant SBQQ__ objects and fields before querying or writing.
  6. Test with the CPQ pricing engine in a sandbox — Changes to price rules, discount schedules, or quote line fields must be validated in a sandbox with CPQ fully configured. Unit tests that mock SBQQ.QuoteService are required for Apex automation.
  7. Check for managed-package version constraints — Some CPQ API behaviors differ between CPQ package versions. Note the installed version and check the CPQ Release Notes for deprecation notices on any API class or method used.

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

  1. Standard Quote vs SBQQ__Quote__c confusion — In CPQ orgs the standard Quote object may exist (synced from CPQ) but is NOT the pricing source of truth. Writing prices to standard Quote or QuoteLineItem has no effect on CPQ-calculated values and will be overwritten on the next CPQ save.
  2. Direct DML corrupts CPQ totals — Updating SBQQ__NetPrice__c directly via DML saves the value but does not trigger the CPQ pricing engine. The quote-level SBQQ__NetAmount__c rollup becomes stale. Approval rules that evaluate SBQQ__NetAmount__c may fire incorrectly or not at all.
  3. Child relationship names are namespace-prefixed — The relationship from SBQQ__Quote__c to SBQQ__QuoteLine__c is traversed as SBQQ__LineItems__r, not QuoteLineItems. Using the wrong relationship name in SOQL silently returns zero rows rather than throwing an error.
  4. Subscriptions are per-line, not per-quote — When a quote is contracted, one SBQQ__Subscription__c is created for each contracted line with a subscription-type product. Developers expecting a single subscription per contract are surprised to find N subscription records for N lines.
  5. Price rules fire on every CPQ save, not just API calls — SBQQ__PriceRule__c records with no well-scoped conditions execute against every line on every save. A poorly scoped price rule can cause unexpected price overrides and CPU timeout in large quotes.

Output Artifacts

ArtifactDescription
SOQL query templateReady-to-use queries for SBQQ__Quote__c with lines, groups, and subscriptions
CPQ Quote API code sketchApex pattern for read-calculate-save using SBQQ.QuoteService
Object relationship mapReference listing of SBQQ__ objects, their parent/child relationships, and key fields
CPQ data model checklistReview checklist for Apex, Flow, or integration code that touches CPQ objects

Package files