Browse Templates/Real Estate Platform

Real Estate Platform

A comprehensive real estate database schema designed for property listing and management platforms. Includes property details with specifications, agent profiles, active listing management with pricing, customer inquiry tracking, and appointment scheduling for property viewings. Suitable for platforms like Zillow, Realtor.com, or custom real estate solutions.

PostgreSQL5 tablesOther
real-estatepropertylistingsagentshousing
Created with ERD Studio

About this schema

A property and a listing are not the same thing, and separating them is the central decision here. properties holds what is physically true — the address, the bedrooms, the square footage, the year it was built. listings holds an attempt to sell or rent it: a price, an agent, a type and a window of time. The same house listed three times over ten years is one properties row and three listings.

Inquiries arrive against a listing, not against a property, because the question is always about this price with this agent. inquiries stores the sender's name, email and phone as plain columns rather than pointing at an account, since the people who ask about a house have usually never registered for anything.

Appointments carry both listing_id and inquiry_id, with the inquiry nullable. A viewing usually grows out of an inquiry and should stay attached to it, but a walk-in booked by phone has no inquiry behind it — and a schema that demanded one would force the agent to invent a fake message before booking.

Key relationships

properties → listings
One property, many listings over time. Price and status live on the listing, so relisting never rewrites the building's facts.
agents → listings
The agent is attached to the listing rather than the property, so a seller switching agency changes one row and the history stays readable.
listings → inquiries
Every question is about a specific offer. An expired listing keeps its inquiries, which is how an agent sees what interest a price attracted.
listings → appointments
Viewings point at the listing directly, so a tour can be booked without an inquiry existing first.
appointments → inquiries
Nullable link back to where the interest started, which is what connects a viewing to the message that caused it.

Design decisions

Price belongs to the listing, not the property

Putting price on properties is the obvious shortcut and destroys history on the first price change. A property is listed, withdrawn, relisted at a different figure and perhaps rented rather than sold — each of those is a listings row with its own price, status and dates. The building's own attributes change rarely, so they stay where they cannot be overwritten by market activity.

Inquiries store contact details instead of a user account

Requiring registration before asking about a house is the fastest way to receive no inquiries. Holding name, email and phone on the row means the form works for anyone. What it gives up is identity: two messages from the same person are two unrelated rows, and deduplicating them is an application-level guess on email.

listing_type is a column, not two tables

Sale and rental differ in what price means and little else at this level, so one listings table with listing_type covers both. The honest limit is that rentals eventually need a term, a deposit and a lease, none of which belong on a sale row — at that point the rental path earns its own table rather than more nullable columns here.

expires_at on the listing rather than a manual status sweep

status says what the listing is now; expires_at says when it stops being that on its own. Storing the date means a stale listing can be filtered out by a query even if nobody remembered to close it, which matters because the thing people forget to update is exactly the listing that stopped selling.

Tables in this template

agentsAgents
7 cols
propertiesProperties
12 cols
listingsListings
8 cols
inquiriesInquiries
8 cols
appointmentsAppointments
9 cols

Frequently asked

Why separate properties from listings at all?
Because the same building gets sold more than once. Merging them means each new sale either overwrites the last price or creates a duplicate property, and the address then exists several times with no way to say they are the same house.
How do I find price history for an address?
All listings rows for that property_id, ordered by listed_at. That is only possible because the price lives on the listing — with a single price column on the property, previous figures would have been overwritten.
Can two agents share one listing?
Not as modelled — listings has a single agent_id. Co-listing needs a listing_agents join table; the single column is the simplification that fits most markets and is the first thing to change if yours does not.
Why is inquiry_id on appointments nullable?
Because viewings are often booked by phone or in person with no prior message. Making it required would force agents to create a fake inquiry just to record a real appointment.
Where would photos go?
On the property, not the listing — the pictures are of the building and stay valid across relistings. A property_images table with a position column is the usual addition, with listing-specific shots as an optional override.