Appearance
Listings
Real estate and directory listings: a search with filters and a map, agents, enquiries, favorites — on Houzez's and Listeo's data.
kitloom/listings · admins: kitloom/listings-filament, kitloom/listings-nova
Video lessons
Recorded in the Filament admin; the Nova admin works the same way.
Listings for two kinds of sites with one module: real estate — properties for sale or rent, with their agents and agencies — and directories — cafés, shops, services, places, with their hours and reviews. Visitors search with filters and a map, save what they like, and ask about a listing with a form that reaches the right person by email.
The data is where the two best-known WordPress themes keep it: properties are Houzez's (property posts with fave_property_* meta, houzez_agent and houzez_agency posts, enquiries in Houzez's CRM tables), directory listings are Listeo's (listing posts with _address, _geolocation_*, opening hours and reviews). A site that ran either theme keeps its listings, addresses, agents and leads as they are.
The admin is kitloom/listings-filament or kitloom/listings-nova. A theme draws the pages: kitloom/keystone-theme (real estate) and kitloom/waypoint-theme (a directory).
Setting it up
- Settings → What the site lists. Properties (real estate), Listings (a directory), or both. A kind that is off has no pages and no admin screens; its records stay in the database.
- Settings → Currency, Area unit, Records per page. The price is printed in the page's language:
$1,250,000,1 250 000 $. Left empty, the currency and the unit are the ones Houzez or Listeo kept — a site that moved from either shows its prices as before — else US dollars and square feet. - Settings → Copy enquiries to. An office address that gets a copy of every enquiry. Without it, an enquiry nobody is named for goes to the site's admin email.
- Settings → Find addresses on the map. On: a listing saved with an address and no coordinates is placed on the map by OpenStreetMap's Nominatim, one address a second, each address asked once. Off: type the coordinates, or pick the point on the map in the editor.
- Settings → Map style. OpenFreeMap's free map by default, which needs no key. A provider of your own (MapTiler, Stadia Maps) gives you a style address with your key in it.
Then the terms: Property types (House, Apartment), Statuses (For sale, For rent), Cities and Areas, Features (Pool, Garden), Labels (Hot offer) — or a directory's Categories, Regions and Features. A city's areas, a category's sub-categories: searching for the city finds the areas too.
Properties
The editor has the price (with a prefix — From — and a postfix — /mo), bedrooms, bathrooms, rooms, garages, area and land with their units, the year built and a reference; the address and the point on the map; the gallery, a video and a virtual tour; Houzez's floor plans and additional details. Contact says whom the page names to ask: the property's agents, its agency, its author, or nobody.
Agents have their photo, position, email, phones, WhatsApp and website, and the agency they work for. An agent linked to a site user (Houzez's user field) sees the enquiries sent to them on the site's account page. Agencies have their logo and contacts; an agency's page lists its agents and every property of theirs.
Directory listings
A listing has its address, its point on the map, a price range (from 15 to 40), its phone, email, website and social links, its opening hours by weekday — a day open twice, a night past midnight — and whether it is verified or featured. Its owner is its author: enquiries go to the listing's own email, else to the owner's. Visitors leave reviews with one to five stars; the average is kept on the listing as Listeo keeps it. A review follows Settings → Discussion as a comment does: with kitloom/comments installed, all of its rules (an account when asked for, held and disallowed words, links, a first comment held, the same review twice, too fast); without it, the account, the held and disallowed words and Comment must be manually approved.
The search
A listing page is its archive — /property/, /listing/ — or a term's — /city/lisbon/, /listing-category/cafes/. The visitor narrows it in the address, and the address can be shared:
| Parameter | What it does |
|---|---|
type, status, location | term slugs, any of them: type=house,apartment |
features | all of them: features=pool,garden |
min_price, max_price | a listing's price range meets the one asked for |
beds, baths | at least |
min_size, max_size | in the site's unit |
featured=1 | featured only |
near=38.71,-9.14&radius=5 | within 5 km, nearest first |
bounds=south,west,north,east | what the map shows |
s | words in the title or the address |
orderby | newest (the default), price, price-desc, featured; size for properties, rating (top rated) for a directory |
Next to the results the filters show what is left to choose, with counts: three houses, two apartments — a category counts its sub-categories' listings too, as choosing it finds them. The map shows every listing found, not only the page's — up to a thousand; past that it asks to zoom in.
Enquiries
The form on a listing's page sends a name, email, phone, message and what it is about (Book a viewing, I want to rent…). Each person it goes to gets their own enquiry, under their own lead of that address — as each Houzez agent has a CRM of their own — and an email whose Reply answers the visitor. The same address asking again is the same lead: what it gives fills only what the lead lacks — a form never overwrites the name or phone the agent has.
Listings → Enquiries in the admin lists them all, with their status: new, read, answered, closed, and a private note. An agent with an account answers theirs on the site's account page; Manage every enquiry (manage_enquiries, on the roles screen) sees everyone's.
Personal data: WordPress's Export Personal Data gives an address's leads and enquiries, and what its account saved; Erase Personal Data deletes them.
Not read: Listeo's private messages between visitors and owners (listeo_core_conversations, listeo_core_messages) stay in the database untouched; the module's enquiries start afresh in Houzez's CRM tables.
For the visitor
- Saved on the account page: the listings they marked with the heart, newest first — on a site that moved, what they saved with Houzez or bookmarked with Listeo. Every card knows whether its viewer saved it.
The agents' and agencies' archives (/agent/, /agency/) list them in the admin's order — the order field, then by name.
- Enquiries on the account page of whoever is asked.
For the theme
| What | Where |
|---|---|
| A listing page's results, filters, facets, price range, map style | the page part listingSearch (Site\ListingSearchPart documents the keys); on any other page — the front page's search form — the first kind's form without results |
| A property's or listing's page | listingDetails: the card, gallery, facts, features, floor plans, location, contacts (agents, agency or owner), hours and open now, reviews, the enquiry form, the favorite, similar listings |
| An agent's or agency's page | agentProfile: the card, the agency's agents, their listings, a form to ask them |
| A listing's review stars | reviewStars: comment id => stars, for the comments the theme draws |
| The map's points | GET /wp-json/listings/v1/markers?kind=property&… with the page's own parameters — each with its title, link, price, type, place, rating and a small picture for a pop-up |
| An enquiry | POST /wp-json/listings/v1/enquiries — listing, name, email, phone, message, type, agent, the protection's fields |
| A review | POST /wp-json/listings/v1/listings/{id}/reviews — review, rating, author, email |
| The heart | POST / DELETE /wp-json/listings/v1/favorites/{id} |
| An enquiry's status | POST /wp-json/listings/v1/enquiries/{id}/status — status, note |
The map is MapLibre GL with the style from map.style; load it only on the pages that draw a map. Templates: archive-property, single-property, taxonomy-property_city, single-houzez_agent, archive-listing, single-listing… as WordPress's hierarchy names them.
Where things are kept
| What | Where |
|---|---|
| Properties, agents, agencies, listings | posts of the types property, houzez_agent, houzez_agency, listing, their meta under Houzez's and Listeo's keys |
| A key with many values (a property's gallery, its agents) | a meta row per value, as WordPress keeps it |
| A listing's gallery and hours | Listeo's shapes: _gallery a map of attachment id => address; _monday_opening_hour and _monday_closing_hour one time each, or lists side by side for a day open twice |
| The search index | listing_index: price, rooms, area, coordinates, featured — made again whenever a listing is saved, once the request is over, whoever wrote its meta; rebuilt from the meta with php artisan listings:reindex (after an import that wrote the meta past the writers) |
| Enquiries | houzez_crm_leads and houzez_crm_enquiries, Houzez's columns; created when missing, left in place when the module is removed |
| Saved listings | the user meta houzez_favorites (JSON). Until the module writes it for a user, Houzez's option houzez_favorites-{user id} and Listeo's user meta listeo_core-bookmarked-posts are read; the first change copies them over |
| Reviews | comments on the listing, the stars in listeo-rating, the average in listeo-avg-rating |
| Settings | options kitloom_listings_*; the currency and area unit fall back to Houzez's houzez_options (default_multi_currency, currency_symbol, measurement_unit, area_prefix_default) and Listeo's listeo_currency |
| Configuration | config/kitloom-listings.php: the addresses' bases, the throttle, the map's credit line, the geocoder |
What the application overrides
- The addresses:
kitloom-listings.slugs— a site whose Houzez said/homes/sets'property' => 'homes'. - The geocoder: bind
Kitloom\Listings\Geo\Geocoderto a provider of your own (Google, Mapbox, your own Nominatim atgeocoder.url). - The models:
listing_property,listing_listing,listing_agent,listing_agency,listing_lead,listing_enquiryin the model registry. - What is on:
kitloom-listings.routes.register,notifications.register,account.register,ai.register,privacy.register,filament.register,nova.register.
AI agents (kitloom/ai): listings_search, listings_get, listings_save (as the caller, who needs edit_properties or edit_listings — the post writer checks the rest), listings_enquiries, listings_enquiry_status, and the kinds, currency and enquiries in describe_site.
Tests
From the application that installs the package: php vendor/bin/phpunit --testsuite Listings. A theme's test creates listings with Kitloom\Listings\Testing\CreatesListings.