CPQ — Configure Price Quote
CPQ is CRMown's structured quoting engine for orgs that sell configurable products: a product catalog with options + bundles, dynamic price rules (volume discounts, percent off, tier-based pricing), versioned quotes with line items, branded PDF rendering via Playwright, public share links, e-signature capture (type or draw), and one-click conversion to a Finance invoice on acceptance. Distinct from Proposals (which is freeform) — CPQ is for repeatable line-item-and-rule pricing where the catalog matters. Plan-gated to Max and Own.
Steps
- Tools → Quotes in the sidebar (/dashboard/cpq) — the sidebar entry reads Quotes, not CPQ. It opens on the Quotes tab, which lists quotes with status filters (All, Draft, Pending approval, Choose approver, Sent, Viewed, Accepted). The Products and Price rules tabs sit next to it for admins and owners only — on a non-admin login the Quotes tab is all you see. If the page instead reads “Quotes are part of the Enterprise suite”, activate Enterprise under Settings → Billing.
- Products tab: build your catalog. Each CpqProduct has name, SKU, basePrice, currency, taxable, productType (standard / configurable / bundle / service), category, and configOptions (e.g. "color: red/blue/green", "tier: small/medium/large" with per-option price impact).
- Price rules tab: define discount logic. Each CpqPriceRule has ruleType (volume_discount / percent_discount / fixed_discount / tier_based), conditions (e.g. {"quantity": {"gte": 10}}), effectConfig (e.g. {"discountPercent": 15}), and an optional productId (null = applies globally to all products). Set requiresApproval=true on a rule to gate matching quotes through the Approval & Review Process.
- Quotes tab > "+ New quote": pick a contact (or enter ad-hoc customer details: name, email, company, address), then add line items. Each line item references a CpqProduct, has quantity, optional manual discount + reason, and any selected configOptions.
- The pricing engine fires on every save: walks each line item, applies matching CpqPriceRules in order, then any manual line-item discount, then quote-level adjustments. Result is a structured pricing breakdown shown live in the builder UI — subtotal, discounts, tax, grand total — all rounded to 2 decimal places at the engine boundary.
- When the quote's status moves out of "draft" (e.g. you click "Send"), the system checks for triggered approval rules. If a CpqPriceRule with requiresApproval=true matched, an ApprovalProcess gets auto-resolved and the quote transitions to pending_approval (1 matching process), pending_approval_choice (multiple processes — you pick one in a banner), or stays draft with a warning (no matching process configured).
- Send the quote: enter the customer email and click Send. The PDF renders via Playwright, gets uploaded to R2, and the customer receives an email with a public share link (/share/quote/<token>). They view, sign (type their name OR draw the signature on a canvas), and accept right in the browser.
- On accept: the quote transitions to "accepted", a Finance invoice is auto-created with the same line items pre-filled (you can convert manually before that via "Convert to Invoice"), and the customer / your team get acceptance notifications. The accepted signature blob (PNG for drawn, name string for typed) plus IP + user-agent are captured server-side for audit.
- Use Revise to spin up a v2 of an existing quote — copies all line items + customer details into a new draft you can edit. Original quote stays unchanged for audit; the new one references it via parentQuoteId.
Good to know
- CPQ products and price rules are sandboxable — make changes in a sandbox first (Enterprise > Sandbox), test against cloned quote data, then promote selected products / rules back to production. This is the recommended flow when rolling out new pricing.
- Configurable threshold for approval: don't hardcode "anything over 25% off needs approval." Build a global CpqPriceRule with productId=null + requiresApproval=true + approvalThreshold to express that as actual configuration. The pricing engine reads it at quote time.
- Quote numbers are race-safe: an OrgQuoteCounter table holds the per-org per-year counter. Generation uses an atomic upsert-with-increment so two simultaneous "Create quote" requests can't collide on the same number.
- Public share token is a cuid stored on the Quote row at create time. Treat it as the auth credential for /share/quote/<token> — anyone with the link can view + accept. Disable a leaked link by revising the quote (which invalidates the parent's token).
- PDF rendering uses Playwright (already installed for the Lead Magnet feature). The HTML template renderQuoteHtml lives in cpq/pdf-renderer.ts. Customizing the template applies to all future PDF renders — re-render existing quotes via Revise.
- E-signature is E-SIGN Act compliant: the typed-name path uses the customer's typed full name as the signature; the draw path captures a base64 PNG of the canvas. Both are stored in Quote.acceptedSignature alongside IP + user-agent + timestamp.
- Quote line items support a parentLineItemId — used for bundle children. The parent line item references a "configurable" or "bundle" CpqProduct; the children reference the bundle's component products. Bundles can be priced as a single line or itemized.
- Discount on a line item supports two modes: discountPercent (e.g. 10 = 10% off) OR a fixed discount amount via discount + discountType="amount". The pricing engine resolves whichever is set; both being set is rejected at validation time.
- CPQ quotes show locked-pending-approval banners when an ApprovalRequest is open against them. The lock is enforced at the API layer (PATCH on a locked quote returns 423 Locked) — UI just renders the banner. Same lock pattern as PipelineItem under Approvals.
Troubleshooting
Error messages
cpq_access_required— Your plan doesn't include CPQ. Upgrade to Max or buy an Own license.quote_locked— An ApprovalRequest is open against this quote — edits are blocked until the approval resolves. HTTP 423.pricing_error: rule_match_failed— A CpqPriceRule's conditions JSON couldn't be evaluated — likely malformed (missing operator, wrong field name). Fix the rule's conditions in the Price rules tab.quote_status_invalid_for_action— You tried an action that doesn't apply to the quote's current status (e.g. accept on a draft, send on an accepted quote). Check the quote's status before retrying.public_token_not_found— The /share/quote/<token> URL is invalid — quote may have been revised (which generates a new token) or deleted.signature_required— Customer tried to accept without providing a typed name or drawn signature. The frontend should disable the button before this fires server-side.
Plans
Max plan: full CPQ access. Own license: full CPQ access. Free / Starter / Pro: not available — upgrade prompt shown. The /cpq/* API surface is gated server-side via checkCpqAccess. Permissions: cpq:read (broad), cpq:write (TEAM+), cpq:admin (OWNER/ADMIN/EXECUTIVE/DIRECTOR — for product + rule management).
Related
- Approval & Review Process — discount rules with requiresApproval=true auto-route quotes through your approval chains
- Invoicing — accepted quotes auto-convert to Finance invoices with line items pre-filled
- Proposals — different product: freeform proposals vs. structured CPQ catalog quotes; use Proposals for narrative + custom pricing, CPQ for repeatable line-item pricing
- Sandbox Environments — test new products and price rules against cloned quote data before promoting to production
- Pipeline / Deals — quotes can be linked to a PipelineItem; quote acceptance can trigger workflow advancement