WordPress

Building a Directory Website With WordPress: Listings, Search, Maps

How to plan a directory on WordPress so it still works with thousands of listings: the data model, fast filters, location queries, map costs, claims, paid listings and when to go custom.

Building a Directory Website With WordPress: Listings, Search, Maps

A WordPress directory website looks like a blog with a map attached, and for the first few hundred listings it behaves like one. The trouble comes with growth: filters that take seconds, a map bill nobody budgeted for, and thousands of near-empty pages that search engines ignore. Nearly all of it traces back to decisions made in the first week.

Those decisions, in the order you meet them when building: how a listing is stored, how search and filters query it, how location works, which map provider you pay, and how owners claim and pay for listings.

Model the listing before you pick a plugin

A listing is a custom post type. What matters is where each piece of information about it is kept, and one rule sorts most fields:

  • Things visitors browse or filter by are taxonomies: category, city or region, features such as "wheelchair access". Taxonomies have indexed tables of their own and give you archive pages like "Dentists in Leeds" without extra work.
  • Things you only display are post meta: phone number, website, a description of parking.
  • Things you compare or measure, such as price, rating, latitude and longitude, belong in a table of your own with numeric columns and indexes. Post meta stores every value as text and has no index on the value, so range and distance queries on it get slow.

Registering the post type and a first taxonomy takes a few lines in a plugin:

add_action( 'init', function () {
    register_post_type( 'listing', [
        'label'        => 'Listings',
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true,
        'supports'     => [ 'title', 'editor', 'excerpt', 'thumbnail', 'author' ],
        'rewrite'      => [ 'slug' => 'listings' ],
    ] );
    register_taxonomy( 'listing_category', 'listing', [
        'label'        => 'Listing categories',
        'hierarchical' => true,
        'show_in_rest' => true,
    ] );
} );

Author support is there on purpose: the post author becomes the owner once a business claims its listing. Decide two more things now. What makes a listing unique (usually name plus address), so imports and submissions cannot create duplicates. And whether opening hours are structured per day, which you need for an "open now" filter.

Search and filters that stay fast

Filtering by taxonomy through tax_query is quick. Filtering by custom fields through meta_query adds a join on the meta table for every condition, and four or five combined filters on a large directory will be felt by every visitor.

The dependable fix is a flat index table: one row per listing, one typed column per filterable field, written whenever a listing is saved (the save_post_listing hook). Filters query that table for matching IDs and hand them to WP_Query through post__in. Some directory plugins keep listing fields in tables of their own for this reason, which is worth checking before you choose one.

Three further points:

  • Counts beside filters, such as "Italian (42)", are a grouped query on the index table. Cache them, because they are requested on every search.
  • Keyword search in WordPress matches titles, excerpts and content. It does not look in custom fields or term names. Add a full-text index on your own table, or a search engine through a plugin once you need typo tolerance and relevance tuning.
  • Filter addresses should use query strings and point their canonical tag at the unfiltered page. Only chosen combinations, typically category plus city, deserve an indexable page.

The indexing principles behind this are in our guide to database query optimization.

Location search without a slow query

Convert each address to coordinates once, when the listing is saved, and store them. Never geocode during a page view. Then keep the coordinates in a small table and search it in two stages: a bounding box that uses the index, followed by an exact distance on the few rows that remain.

CREATE TABLE wp_listing_geo (
  post_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  lat DECIMAL(9,6) NOT NULL,
  lng DECIMAL(9,6) NOT NULL,
  KEY lat_lng (lat, lng)
);

SELECT post_id,
       ST_Distance_Sphere(POINT(lng, lat), POINT(%f, %f)) AS meters
FROM wp_listing_geo
WHERE lat BETWEEN %f AND %f
  AND lng BETWEEN %f AND %f
ORDER BY meters
LIMIT 20;

The placeholders are filled by $wpdb->prepare(): the search point as longitude then latitude, then the edges of the box. ST_Distance_Sphere is available in MySQL and in current MariaDB releases. The same box, without the distance, returns the markers for the visible part of a map.

Maps and what they cost

OptionHow you payKeep in mind
Google Maps PlatformPer request, with map loads, geocoding and address autocomplete billed as separate products and a free monthly allowance on eachNeeds a billing account. Familiar to visitors and strong on place data
OpenStreetMap data with the Leaflet library and a commercial tile hostThe tile host's planOpenStreetMap's own tile servers are a best-effort service with a usage policy and no guarantee, so do not build a business on them
Tiles you host yourselfServers and the time to run themSensible only at high volume with operations skills in the team

Google explains its model in the Maps Platform billing overview. Whichever provider you use, the bill follows the number of requests, and a directory generates them quickly. Keep them down:

  • Do not load an interactive map on every page view. Show a static image or a "Show map" button and load the script on demand.
  • Fetch markers for the visible area from your own REST endpoint, and cluster them.
  • Delay autocomplete requests until the visitor pauses typing, and limit them to the countries you cover.
  • Restrict the API key to your domain and to the services you use, then set a budget alert and a daily quota in the provider's console.

Read each provider's terms before combining one company's geocoding with another's map. Some restrict that.

Claims and paid listings

Most directories start with listings the operator created and then invite owners to take them over. The claim flow needs more care than it first appears, because a false claim hands a business's page to a competitor.

  1. The claimant creates an account and submits the claim.
  2. They prove the connection: an email address at the business's domain, a code sent to the listed phone number, or a document.
  3. An administrator approves, and the listing's author changes to the claimant.
  4. Later edits by the owner go through moderation before they appear.

Paid listings are plans with a duration: more photos, a website link, a "Featured" position in the category. Store the plan and its expiry in the index table so that featured results sort first without another query. When a plan lapses, downgrade the listing and keep its address alive. Payments can run through the directory plugin's own add-on or through WooCommerce when you also want invoices, coupons and renewals. Label paid positions clearly, and check the advertising disclosure rules where you operate.

What changes at thousands of listings

A small directory can ignore the following. A large one cannot.

  • Thin pages. A listing with a name and an address is not much of a page, and an empty "Plumbers in a village" archive is worse. Require a minimum of content, and set empty combinations to noindex.
  • Structured data. Output LocalBusiness markup on each listing. Name and address are required, and coordinates, opening hours and phone number are recommended.
  • Caching. Serve listing and archive pages to anonymous visitors from a page cache, and add a persistent object cache. Our explanation of WordPress caching covers the layers.
  • Maps and exports. Never request every listing at once. A query with no limit works in testing and takes the site down later.
  • Imports. Load data with WP-CLI in batches, with term counting deferred through wp_defer_term_counting( true ) and switched back on afterward, and rebuild the index table at the end.
  • Stale data. Show a "last confirmed" date and give visitors a way to report a closed business.

Plugin or custom build for a WordPress directory website

Directory plugins such as GeoDirectory, Directorist, HivePress and Business Directory Plugin give you the post type, custom fields, front-end submission and maps, with claims and paid plans as features or add-ons. For a conventional business directory that is the right start. Before committing, import a realistic number of dummy listings and time the searches, because a demo with fifty listings proves nothing.

Custom development is justified when the data model is unusual (several locations per business, listings that relate to each other), when search quality is the product, when listings sync with an outside system, or when the directory is one part of a larger platform. A common middle path keeps the plugin and adds a custom plugin for the index table and search. We build all three variants as WordPress directory websites, and take on existing directories that have slowed down as performance optimization projects.

Frequently asked questions

Which WordPress directory plugin should I choose?

There is no single right answer. Compare how each one stores listing data, which map providers it supports, whether claims and payments are included or sold separately, and how it performs with your expected number of listings. Test with imported data before you decide.

Can WordPress handle a very large directory?

Yes, if the data model is built for it. The limit is filtering through post meta, not WordPress itself. With an index table, page and object caching, and a search engine where needed, large directories run well on it.

Is OpenStreetMap enough, or do I need Google Maps?

For showing listings on a map and searching by distance, OpenStreetMap data with a paid tile host works well. Google adds address autocomplete and detailed place data on the same platform. Budget, audience and the quality of local map data in your region should decide.

Where do the first listings come from?

Usually from your own research, from open data sets and from businesses you invite directly. Check the license of any data you import, and do not copy another directory's database. Then let owners claim and improve what you started.

Add WorldWin Coder to your preferred sources

Add worldwincoder.com as a preferred source on Google, or open this article in your AI assistant to use it as a source.

Swati Priyadrshni
· Founder & CEO at WorldWin Coder

I'm Swati Priyadarshani, Founder & CEO of LMS Advisor and Co-Founder of WorldWin Coder Pvt. Ltd. (est. 2019). Over the last 10+ years, I've helped enterprises build learning platforms that actually get used - not just deployed.

Keep reading

Have something to build, fix or move?

Tell us where you are. A developer reads every inquiry, and you get next steps and a free quote within one business day.