Moderation is not a status field
Rejecting a listing is one line. "Why was it rejected, who decided, was it appealed" is a data model question.
· 4 min
The first version had a status field: active, inactive, rejected. The moderation decision was written into it. Three months later the question came: "Why was this listing pulled last week?"
There was no answer. The field held the last value, not the previous one, the person who decided, or the reason. A deleted row does not come back.
Turning the decision into an event
A moderation decision is no longer a field on the listing but its own record: who, when, which reason code, which outcome. Listing visibility is derived from those records.
- The question became a single query: the decision history of a listing.
- The same listing can be decided again; the earlier record stays.
- An appeal is a new record attached to a decision; no separate table was needed.
The cost
Query count went up. "Should this appear in the list" now reads three tables instead of one field. I balanced it with indexes and a cached visibility state, but the cost is real: more complex reads.
In return the system is accountable. When someone asks "who closed this" a month later, the answer is one click away.
The general rule
If a field holds something that changes over time and will be questioned later, it should be a record, not a field. Balance, stock, publication state, approval — same pattern. A field knows only the last value; a record knows the history.
Real Estate Platform
Multi-role property platform: listing lifecycle, agency management, subscriptions and moderation.