The short answer: Product2 is a flat list. It has a single Family picklist, no hierarchy, no variants and no versioning, and every price lives on a separate PricebookEntry. A catalogue of any size outgrows that shape, and the two decisions that determine whether it stays manageable are how you categorise products and what you do when one is retired.
The fields that carry meaning
Product2 is a small object. Name, ProductCode, Description, Family, IsActive, StockKeepingUnit and QuantityUnitOfMeasure are most of it.
Neither ProductCode nor StockKeepingUnit is unique by default. Both are plain text. If an external system owns your catalogue, add an external Id field marked unique and load against that, otherwise your second data load creates a duplicate catalogue and nothing complains.
The product search on an opportunity looks at name and product code. Put the distinguishing word first. “Support, Premium” beats “Premium Support Package” when forty products start with Premium.
Family is one level, and that is the whole problem
Family is a single picklist value. There is no sub-family, no category tree and no multiple membership.
It has a real consumer: product family forecasting groups on it, and so do most dashboards. Family should therefore be the dimension your business plans by, at the grain leadership talks about, usually between four and a dozen values.
For anything finer, add your own fields rather than splitting Family into sixty values. A Category picklist alongside Family works, as does a lookup to a custom Product Category object once categories need owners, margins or their own reporting.
What does not work is encoding the hierarchy in the product name. “Hardware / Networking / Switches / 24-port” reads fine in a list view and is useless in a report, because grouping on a text field with slashes in it groups on the whole string.
The entry count problem
Every product needs an entry in the standard price book plus one in each price book it is sold from, and in a multi-currency org, one per currency as well. The arithmetic is unforgiving: five hundred products, four price books and three currencies is six thousand PricebookEntry records before anyone has sold anything.
Two things keep that manageable. Have fewer price books than you think you need, since a book per customer is a modelling mistake that line-item discounting solves better. And automate entry creation, because a product added by hand to the standard book and nowhere else presents to users as a product that does not exist.
SELECT Product2Id, COUNT(Id)
FROM PricebookEntry
WHERE IsActive = true
GROUP BY Product2Id
HAVING COUNT(Id) < 4
An audit query like that, against your expected number of books, finds gaps before a rep does.
Retiring a product that has history
You cannot delete a Product2 that has been sold, and you should not want to. Deleting it would break line items and every report that reads them.
Set IsActive = false. The product stops appearing in the search, existing opportunity and quote lines keep pointing at it, and history stays intact. Deactivate the PricebookEntry records too where you want the entry-level record to say so.
The harder question is revisions. Editing a product’s name or code rewrites history, because every deal closed three years ago now shows the new name. If what you sell has genuinely changed, create a new product with a new code and deactivate the old one. If only the label changed, edit in place and accept that old reports use the new wording.
Price changes are different: a new price is a new PricebookEntry value, and the old line items keep the price they were sold at because UnitPrice is stored on the line. That part of the model is sound.
Keeping a large catalogue usable
Record types on Product2 give different families different layouts and required fields, which matters once hardware, services and subscriptions need different attributes. Beyond that, an annual review deactivating anything not sold in two years is the most effective thing you can do. Catalogues grow and nothing removes from them.
SELECT Id, Name, ProductCode FROM Product2
WHERE IsActive = true AND Id NOT IN (
SELECT Product2Id FROM OpportunityLineItem
WHERE CreatedDate = LAST_N_DAYS:730
)
Frequently asked
Can a product be in two families? No. Use a custom multi-select or a junction to a category object if you need it, and keep Family single for forecasting.
Do inactive products disappear from reports? No, historic line items still report normally. Inactive only affects selection on new records.
Can products have parents or bundles? Not in the standard object. Bundles are a CPQ feature or a custom build with a junction object.
Is Family required? No, and leaving it blank breaks product family forecasting and every grouped report. Make it required.
