WithPm Listings (XML)
Builds property pages from PropertyMe Grow CRM's REAXML listing feed. Replaces the API-based sync: only properties published as listings appear on the site.
by Stallioni Net Solutions · github.com/sanjeev-stallioni/withpm-listings
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/sanjeev-stallioni/withpm-listings/archive/refs/heads/main.zipA WordPress plugin that builds property pages from a REAXML listing feed — the format PropertyMe's Grow CRM (and most Australian real-estate CRMs) use to publish listings to portals and agency websites.
A property exists on the website because it is published as a listing in the CRM. Nothing else creates one.
Why this exists
The site this was written for originally pulled every managed property from a property-management API. That produced pages for the whole rent roll, including properties that were never advertised.
The agency wanted the opposite: only the advertised stock — For Lease and Leased — which is exactly the set the CRM already publishes as a listing feed. So this plugin drops the API entirely and treats the feed as the only source of truth.
One consequence is worth understanding before installing: if a property is not published as a listing, it will not appear on the website.
How it works
CRM publishes a listing
│
▼
REAXML file delivered by FTP ──► drop directory (often the site root)
│
▼
moved out of the public directory, then parsed
│
▼
property page created/updated + photos + geocoded map location
On each run the plugin:
- Finds REAXML files in the drop directory, verifying both the filename pattern and the file's actual content.
- Moves each file out of the (publicly served) drop directory before parsing it, so listing data stops being downloadable at the first opportunity.
- Parses with external entities disabled (XXE-safe).
- Creates or updates a property page per
<rental>element. - Imports photos and the floorplan, skipping anything already fetched.
- Geocodes the address to place the property on a map.
- Moves properties that have left the feed to Draft — never deletes them.
What comes from the feed
| Field | Source in REAXML |
|---|---|
| Page title | <address> — unit/street number + street, street type expanded (Ct → Court) |
| Suburb, postcode | <address><suburb>, <postcode> |
| Bedrooms, bathrooms, cars | <features> — garages and carports summed |
| Rent | <rent> |
| Description | <description> — all HTML stripped |
| Property type | <category name="…">, mapped to the site's own labels |
| For Lease / Leased | the <rental status="…"> attribute |
| Inspection date & times | <inspectionTimes> |
| Photos, floorplan | <objects><img> / <floorplan> |
| Listing agent | <listingAgent> — matched to an agent page by email |
| Map location | not in the feed — geocoded from the address (see below) |
Listing status
The <rental status="…"> attribute drives whether a page stays public:
| Status | REA's meaning | Effect here |
|---|---|---|
current |
Published to realestate.com.au | Published, For Lease |
leased |
The property has been leased or rented | Stays published, Leased |
withdrawn |
"removes the property … use this when the rental listing should no longer appear" | Moved to Draft |
offmarket |
"the listing should be unpublished and removed … temporarily" | Moved to Draft |
deleted |
"the listing should be removed and not accessible by the agency" | Moved to Draft |
Delisting is therefore an explicit event, not an inference from absence.
withdrawn is REA's canonical takedown status, with a worked <rental> example
for it in their documentation.
Minimal status files
A status change may be delivered as a file carrying only agentID and
uniqueID — no address, no photos, no description. REA's own Leased Listing
and Withdrawn Listing examples are both this shape.
Takedown statuses are handled before any parsing, so a payload-free withdrawal
works. A payload-free leased or current file is skipped, leaving the
existing page untouched rather than overwriting it with blanks.
Things REAXML does not carry
- Coordinates. The feed has a complete street address but no latitude or longitude, so each address is geocoded once via the Google Geocoding API and cached. An unchanged address is never geocoded twice.
- An inspection summary. The feed carries inspection times only. The summary field is deliberately left empty so the page template can fall back to its own default text.
Requirements
- WordPress 6.0+, PHP 7.4+
- Advanced Custom Fields — the plugin writes to an existing ACF field group (see Adapting to another site below)
- A Google API key with the Geocoding API enabled, for map locations
- A CRM configured to deliver REAXML over FTP to the site
Installation
- Copy the plugin folder into
wp-content/plugins/and activate it. - Go to Settings → WithPm Listings.
- Set the drop directory — where the CRM's FTP delivers. Leave blank for the site root, which is where many CRMs deliver by default.
- Set a Google API key, or leave it blank to reuse the one Elementor already stores for its map widget.
- Press Import now.
To keep it updating on its own, tick Import automatically and choose an
interval. WordPress only runs scheduled work when someone visits the site, so on
a quiet site add a server cron calling wp-cron.php.
Replacing an existing API-based sync
Deactivate the old plugin first — otherwise its scheduled run will recreate the pages you are about to remove. Then remove the old property pages (there is a Remove all imported properties button here for pages this plugin created), activate this plugin, and import.
The first import downloads every photo in the feed. Measured at roughly 2 seconds per photo, so a 12-property feed with 20 photos each takes about 8 minutes — well beyond a typical PHP execution limit. Expect it to stop early; press Import now again and it resumes, because photos already fetched are skipped by their
_wl_srcmarker. Repeat until the count stops rising.
Settings
| Setting | What it does |
|---|---|
| Drop directory | Where the CRM delivers, relative to the WordPress folder. Paths outside the install are rejected. |
| Google API key | For map locations. Falls back to Elementor's stored key. |
| Import automatically | Enables the scheduled import. |
| Interval | Every 15 minutes, hour, or 6, 8, or 12 hours. |
| Missing listings | Move properties to Draft when they are absent from a delivery. Only safe on a feed that sends the entire portfolio every time — see below. |
| Detail-page layout | Elementor layout copied onto new pages. Leave on auto-detect if a Theme Builder template already covers single posts. |
"Missing listings" and delta feeds
REAXML is a stateful, incremental format. REA's specification is explicit that elements "may have been supplied in a previous XML file", and a CRM may deliver one listing per file, sending only what changed.
On such a feed, absence from a delivery means nothing, and the Missing listings sweep — which drafts every property not named in the delivery just processed — will draft the entire site the first time a single listing is pushed. Leave it off unless you have confirmed the feed sends the full portfolio in every delivery.
Delisting is instead signalled by status (see below), which needs no sweep.
Security
The feed is third-party content arriving in a public directory, so it is treated as untrusted:
- Files are moved out of the public drop directory before being parsed.
- XML is parsed with network access off and entity substitution disabled, so a malicious feed cannot read local files (XXE).
- All HTML is stripped from descriptions before storage. The field it lands in is rendered unescaped by ACF, so an unfiltered description would execute as markup on the page.
- HTML entities are decoded BEFORE tags are stripped, repeatedly until stable.
The other order is a stored-XSS hole and an easy one to reintroduce: strip the
tags first and
<script>passes through as inert text, then the decode turns it back into a live `