I spent the better part of a week staring at a Revenue Cloud catalog that had quietly turned into a landfill. Every time a sales rep configured a complex bundle, the org wrote a small pile of records that never went away. Multiply that by hundreds of quotes a month, and the catalog — the thing that’s supposed to be your clean, governed source of truth — becomes impossible to reason about.
This post is the story of that problem and the pattern I landed on to solve it. It’s also the thesis behind Configra, the product I’m building around it.
What “catalog pollution” actually is
In Revenue Lifecycle Management (RLM), the catalog holds your products, bundles, attributes, and the relationships between them. It’s meant to be relatively stable and curated. The problem starts when transient configuration state — the in-progress choices a user makes while configuring a bundle — ends up persisted into catalog-adjacent objects that were never designed to be disposable.
The symptoms are familiar:
- Catalog queries get slower as junk records accumulate.
- Reporting becomes unreliable because real catalog entries and throwaway ones look alike.
- Admins lose confidence — nobody can tell what’s safe to delete.
The root cause is architectural: there’s no clean separation between what the catalog is and what a user is temporarily doing to it during configuration.
The instinct that makes it worse
The tempting fix is to write cleanup jobs — batch Apex that sweeps up orphaned records on a schedule. I went down this road first. It works until it doesn’t: cleanup logic has to know exactly which records are safe to remove, and the moment your configuration model changes, the sweeper is wrong. You end up maintaining a second, fragile model of your data just to undo the mess the first one made.
The cleanup-job approach treats the symptom. The records shouldn’t have been durable in the first place.
The pattern: ephemeral custom objects
The shift that fixed it: stop persisting configuration state into the catalog at all. Instead, model the in-progress configuration in a set of ephemeral custom objects that exist only for the lifetime of a configuration session and are designed from the start to be thrown away.
In Configra these are:
EphemeralConfig__c— the configuration session rootEphemeralLineItem__c— a line being configuredEphemeralAttribute__c— an attribute value on that line
The key properties:
- They’re isolated. Nothing in the durable catalog points into them, so deleting them can never corrupt catalog integrity.
- They’re session-scoped. A configuration session owns its ephemeral records. When the session resolves (commit or abandon), they go.
- They’re cheap to reason about. An admin looking at the catalog sees only the catalog. The scratch space lives elsewhere, clearly labelled as scratch.
Here’s the shape of the root object, trimmed for clarity:
public with sharing class EphemeralConfigService {
public EphemeralConfig__c startSession(Id productId) {
EphemeralConfig__c cfg = new EphemeralConfig__c(
Product__c = productId,
Status__c = 'Draft',
SessionToken__c = generateToken(),
ExpiresAt__c = System.now().addHours(24)
);
insert cfg;
return cfg;
}
// Commit promotes the validated session into real quote lines,
// then the ephemeral records are deleted — not archived, deleted.
public void commit(Id configId) {
EphemeralConfig__c cfg = load(configId);
validate(cfg);
promoteToQuoteLines(cfg);
deleteSession(cfg); // the catalog never saw any of this
}
}
The catalog stays pristine because it never participated in the configuration scratch work. The ephemeral layer absorbs all of it.
The “Placeholder Product” detail
One wrinkle: RLM’s configurator wants to attach line items to something in the catalog while you configure. If you point it at real catalog products mid-session, you’re back to polluting them. The workaround is a single, clearly-named Placeholder Product that the configurator binds to during a session. The ephemeral records carry the real product reference; the placeholder just satisfies the framework’s need for a catalog anchor without ever mutating real entries.
It’s a small thing, but it’s the piece that makes the whole pattern hold together in practice rather than just in theory.
When you don’t need this
Be honest about scope. If your bundles are simple and your quote volume is low, ephemeral objects are over-engineering — the pollution never gets bad enough to matter. This pattern earns its keep when you have complex configurable bundles at volume, which is exactly where RLM catalogs tend to rot.
Where I use this
This is the architecture underneath Configra, which packages the whole thing — ephemeral objects, the placeholder strategy, the configurator integration, and the commit/abandon lifecycle — so you don’t have to build it yourself. If you want to see it in action rather than rebuild it, that’s what the product is for.
The bigger lesson, though, is portable: when transient state keeps polluting durable state, the fix usually isn’t a better cleanup job. It’s a deliberate, disposable place for the transient state to live.