The real estate industry is drowning in manual work: listings entered multiple times, subpages built one by one, CRM out of sync with the portal, price reporting left until the last minute. On its own, each of these looks harmless. Together they create a system where a single price change for one apartment requires edits in three places—and half of them get forgotten.
Plugins patch symptoms#
A typical listing portal grows by bolting on plugins: one to import from the CRM, another to generate subpages, a third for the map, a fourth for price reports. Each solves one problem and adds two. After a year, the portal is a fragile patchwork no one wants to touch. The failure modes we see most often:
- Plugin version conflicts. A CMS update (e.g., WordPress) or one plugin breaks another; the listing import fails silently, and no one notices until a customer complains.
- Double publishing. A CRM webhook retries after a timeout and the same listing creates two subpages. Without an idempotency key, the portal ends up with duplicates that cannibalize each other in search.
- Stale pages. The CRM has a new price, but the subpage cache was never invalidated. The customer sees last week's price, and the agent gets a call: "but the site says it's cheaper."
- Schema drift. One plugin expects a
floor_areafield, the portal exportsarea, and the new CRM calls itarea_m2. The mapping lives in one person's head and disappears with them.
None of these is a bug in any single plugin. They are the result of missing one layer that guards the data.
Product-driven approach: one data flow#
Instead of patching symptoms, you design the cause. The core is a canonical listing contract: one shape of data that everything coming from the CRM reduces to, and from which every subpage is rendered. The flow looks like this:
- Input from the CRM. A webhook (push) or a periodic poll (pull) fetches the raw listing. For CRMs without webhooks, polling every few-to-fifteen minutes is plenty for real estate, where a listing does not change by the second.
- Normalize to a canonical schema. Raw CRM fields are mapped onto one fixed data contract—this is where "single source of truth" stops being a slogan and becomes code.
- Deduplication. A match key (e.g.,
crm_offer_idplus address and area) detects whether this is a new listing or an update to an existing one. Idempotency at this step eliminates double publishing on webhook retries. - Render from a template. A canonical listing plus a template produces a subpage. Hundreds of listings = hundreds of consistent, fast pages from the same code, with no manual assembly.
- Publish queue and cache invalidation. The change goes into a queue that publishes the page and invalidates the cache (in the Next.js stack: on-demand revalidation / ISR), so the customer never sees a stale price.
At every step that touches a price, an offer's legal status, or data for a regulatory report, room is left for human approval. We automate the tedious work, not the responsibility.
The data contract: how to enforce "a single source of truth"#
Field mapping is not an implementation detail—it is the heart of the system. You write it down explicitly, in one place, instead of keeping it in your head. An example mapping fragment (fields are illustrative—in a real deployment they follow the schema of the specific CRM):
| CRM field | Canonical field | Subpage field | Note |
|---|---|---|---|
price_gross / price | price_pln | Price | validation: number ≥ 0, currency PLN |
floor_area / area | area_m2 | Area | unit forced to m² |
offer_status | status | Status badge | enum: active / reserved / sold |
photos[] | photos[] | Gallery | URL dedup, size limit |
lat / lng | geo | Map | coordinate range validation |
offer_id | crm_offer_id | (key) | idempotency and dedup key |
When a new CRM appears or a plugin renames a field, only the mapping layer changes—the rest of the system neither knows nor needs to know where the data came from. That is the difference between "CRM integration" as a slogan and as a contract.
Result: time instead of friction#
In one implementation (Estate OS), 450 listings publish automatically from the CRM—publishing time dropped from hours to minutes, and manual re-entry errors disappeared, because no one re-enters anything anymore. That is a real figure from one deployment, not a promised outcome for everyone.
What we honestly do not promise: a specific percentage increase in traffic or a fixed ROI—those depend on the market, listing quality, and SEO, none of which the pipeline alone controls. What a product approach actually changes, and what you can observe after deployment, is this:
- Time from a CRM change to publication—from hours (a manual loop) to minutes (a queue). Measurable in the queue logs.
- Number of fields entered by hand—from a full form per listing down to zero, because the data comes from one source.
- Number of duplicates and CRM–portal mismatches—trending to zero thanks to the idempotency key and a single data contract.
Property-tech built as a product scales from a few dozen to thousands of listings, because the architecture (monorepo, queues, clean data contracts) was a foundation, not an afterthought. The same pipeline that publishes 450 listings publishes 5,000 with no code change—only the number of jobs in the queue changes.
Compliance and reporting without the fire drill#
Some real estate obligations are recurring and relentless—for example price reporting (dane.gov.pl for developers). In the "plugin patchwork" model this is a monthly fire drill: someone manually collects the data and sends it at the last minute. In the product model the report is built from the same canonical source as the subpages, with integrity verification and a log of who approved the submission and when.
Wherever the portal processes personal data (e.g., customer inquiries, contacts), GDPR applies: data minimization, a clear legal basis, and retention. That is another reason to keep one data layer—it is easier to locate and erase a specific person's data there than across four plugins, each holding its own copy.
Try it: design a listing pipeline for your CRM#
Related paths#
See how this pattern works in practice in Estate OS, the developer website and dane.gov.pl integration, syncing the CRM with the sales team, and our PropTech and CRM sync services.
FAQ#
What does "PropTech as a product" mean?#
It means building a real estate system as a cohesive product—monorepo, one canonical data contract, CRM synchronization, automatic publishing of listings and subpages—instead of a plugin patchwork you have to babysit by hand. The cause is a single source of truth, not yet another plugin patching a symptom.
Can listings be published automatically?#
Yes. A listing enters the CRM once, and hundreds of subpages generate and publish themselves through a task queue—publishing time drops from hours to minutes, and manual re-entry errors vanish, because no one re-enters anything anymore. Price changes and legal status changes are left for a human to approve.
How do you avoid duplicates and stale pages?#
Through idempotency and cache invalidation. Each listing has a key (e.g., crm_offer_id plus address and area), so a webhook retry updates the existing subpage instead of creating a second one. A CRM change goes into a queue that publishes the page and invalidates its cache (on-demand revalidation / ISR), so the customer never sees last week's price.
What do typical plugins break in a listing portal?#
Most often four things: version conflicts on updates, double publishing on webhook retries, stale pages when the cache isn't invalidated, and schema drift when a CRM field is renamed. All of them disappear once there is one layer mapping CRM data onto a canonical listing contract.
Is the system compliant with requirements (e.g., dane.gov.pl)?#
Price reporting and recurring obligations are automated in the data pipeline, with integrity verification and an approval log—the report is built from the same source as the subpages. GDPR compliance is supported by a single data layer, where it is easier to find and erase a specific person's data. Final responsibility for approving a regulatory submission stays with a human.
