That single limitation explains almost every failed attempt. Someone imports 3,000 property listings with price, bedrooms, and city stored as custom fields, then discovers the site search returns nothing when a visitor types “Lahore” or “3 bedroom”. The data is in the database. WordPress is simply not looking at it.
This guide covers the decision you make before installing anything, the four routes that actually work, what each one costs, and where each one breaks.
Table of Contents
Rows or Records: The Decision That Comes First
Before choosing a plugin, decide whether your data is a table or a set of records.
A table is data people scan and sort. A price list, a fixture schedule, a comparison of processor specifications. Nobody needs a dedicated page for row 47. The whole point is seeing many rows at once.
A set of records is data where each entry deserves its own page. A property listing with photos and a contact form. A staff member with a biography. A school with admission details. Each one can rank in Google, be shared, and collect its own comments.
Picking wrong costs weeks. Build 2,000 school profiles as table rows and you have thrown away 2,000 potential landing pages and any chance of ranking for “best schools in DHA Lahore”. Build 2,000 currency exchange rates as individual posts and you have polluted your sitemap with 2,000 pages of near-identical thin content, which is exactly the pattern Google’s guidance on scaled content flags.
| Question | Table route | Records route |
|---|---|---|
| Does each entry need its own URL? | No | Yes |
| Do visitors compare many entries at once? | Yes | Sometimes |
| Does each entry have images, long text, or a form? | No | Yes |
| Should entries appear in Google results individually? | No | Yes |
| Typical row count | 50 to 50,000 | 20 to 10,000 |
| Example | Tax rate schedule | Property listings |
If you answer “yes” to the URL question, use the records route. Everything else in this guide follows from that choice.
Why WordPress Search Fails on Database Content
WordPress core search runs a query against three columns in the wp_posts table: post_title, post_excerpt, and post_content. That is the entire scope.
Everything below is invisible to it:
- Custom fields, including every ACF, Meta Box, or Pods field
- Taxonomy terms, including categories and tags on the record
- WooCommerce product attributes and SKUs
- Text inside uploaded PDFs, DOCX files, or spreadsheets
- Comments and author names
- Any data in a custom database table
You also cannot narrow core search by post type from the dashboard. If you want the search box to return only your database records and not blog posts and pages, that takes a query filter, which is covered separately in restricting search results to a single post type.
Results are also sorted by publish date, not relevance. A record that matches the search term once in a heading ranks the same as one that matches it fourteen times, and the newer one wins. There is no fuzzy matching, no partial word matching, and no handling of typos or plurals. Search “propertys” and you get nothing.
This is not a bug or a configuration you have missed. It is how core search is built, and no setting in the dashboard changes it. Fixing it means replacing the search engine, filtering the results, or both.

The Trap Most Tutorials Skip
Here is a failure that costs people a full weekend, so it is worth stating before the plugin comparison.
A filter plugin and a search engine plugin are not the same thing, and one very popular filter plugin cannot search custom fields at all on its own.
FacetWP is a filtering plugin. Its own documentation states plainly that FacetWP does not influence the returned search results in any way. It takes whatever set of post IDs the underlying query produces and narrows that set down using your facets. If the underlying query is WordPress default search, then the underlying query still only reads title, excerpt, and content.
There is a second limit stacked on top. When a FacetWP Search facet runs against the WordPress Default search engine, it returns a maximum of 200 results. If your database has 4,000 records and a search term matches 900 of them, the facets calculate against 200 of those. The counts next to each filter option will be wrong, and records the visitor is looking for will be missing with no error message.
The fix is to pair FacetWP with a real search engine: SearchWP, Relevanssi, or ElasticPress. FacetWP has built-in integrations for all three. Once the engine handles the search, the 200-result cap lifts and custom fields become searchable.
Budget for two plugins, not one. This is the single most common reason a searchable database project stalls.
Route 1: Custom Post Type Plus a Search Plugin
This is the route to choose for most record-style databases. It uses WordPress the way WordPress is designed to work, so everything else in the ecosystem keeps working: permalinks, sitemaps, schema plugins, caching, the REST API.
Register the post type
Add this to a small custom plugin, or to your child theme’s functions.php. Every argument available is documented in the register_post_type reference:
php
add_action( 'init', function () {
register_post_type( 'school', array(
'labels' => array(
'name' => 'Schools',
'singular_name' => 'School',
),
'public' => true,
'has_archive' => true,
'show_in_rest' => true,
'supports' => array( 'title', 'editor', 'thumbnail', 'custom-fields' ),
'rewrite' => array( 'slug' => 'schools' ),
) );
} );
Put this in a plugin rather than the theme. If it lives in the theme and you later switch themes, every record becomes inaccessible in the dashboard. The posts stay in the database, but WordPress no longer knows the post type exists.
After adding this, visit Settings then Permalinks and click Save once. Without that, single record URLs return 404.
Each record will render through single-school.php, or the block theme equivalent. If you have not built a custom template before, the process is the same one covered in creating a WordPress post template, applied to your post type instead of standard posts.
Add the fields
Use Advanced Custom Fields, Meta Box, or Pods to attach the fields each record needs: city, fee range, curriculum, phone, coordinates. Keep numeric fields numeric. Storing a fee as “Rs. 45,000” as text means you can never filter by “under 50,000” without messy conversion.
Pick your field names once and keep them consistent. Renaming a field later does not move the existing data; it orphans it under the old key in wp_postmeta.
Wire up search
Install SearchWP or Relevanssi and tell it to index your custom fields. In SearchWP, you build an engine, add the school post type as a source, and set weights: title 10, a key field like curriculum 5, body content 1. Relevanssi works similarly through its settings page. Both then rebuild an index, which for a few thousand records takes a few minutes.
Relevanssi has a free version that covers custom field indexing and relevance sorting. SearchWP has no free tier.
Add filters
FacetWP, Search & Filter, or the free filter block in some directory themes gives visitors dropdowns and checkboxes. Point the filter plugin at your search engine rather than the default query, and the two work together.
What this costs
| Component | Free option | Paid option | Approximate price |
|---|---|---|---|
| Post type registration | Code, or CPT UI | Included in Pods | Free |
| Custom fields | ACF free, Meta Box free, Pods | ACF Pro, Meta Box AIO | Free to around $60 per year |
| Search engine | Relevanssi free, ElasticPress | SearchWP | Around $99 per year for one site |
| Filter interface | Search & Filter free | FacetWP | $99 for three sites |
A workable free stack is CPT UI plus ACF free plus Relevanssi free plus Search & Filter free. It is slower to configure and less polished, but it runs.
Route 2: A Table Plugin
Choose this when the data is genuinely tabular and no entry needs its own page.
TablePress is the usual starting point. The core plugin is free and handles CSV import, sorting, and a search box above the table. Its premium bundle adds filtering, row highlighting, and responsive column handling, starting around $89 per year.
The scaling limit matters here. TablePress and most table plugins render using DataTables, which by default is client-side: the entire table is sent to the browser, and the browser does the searching and sorting. A 500-row table is fine. A 5,000-row table means a multi-megabyte page load and a browser that freezes while sorting. Mobile visitors suffer worst, and no amount of caching or DNS and hosting tuning fixes a page that is simply too large to begin with.
Server-side processing solves this by sending only the visible page of rows, but in TablePress it sits in the highest paid tier. wpDataTables also offers server-side processing on paid plans. Check this specific feature against the plan you are buying, because published pricing across these plugins changes often and tier names do not always make the feature obvious.
Rough guidance: under 1,000 rows, client-side is fine. Over 1,000, you need server-side or you need Route 1.
Route 3: A Directory or Listing Plugin
Plugins like Directorist, Business Directory, and GeoDirectory ship the whole stack in one package: a listing post type, a field builder, a search form with filters, map integration, and often submission forms and paid listing tiers.
This is the fastest route to something that looks finished. It is also the least flexible. Your data model has to fit the plugin’s idea of a listing, and moving away later means exporting from a structure that plugin invented. Fields stored by the plugin may not map cleanly to a plain ACF setup.
Property data is the one case where it is worth checking for a niche plugin before a general directory one, because listings usually arrive from a feed rather than being typed in. The options and the licensing involved are covered in choosing a WordPress MLS plugin.
Pick this when your database really is a directory of businesses, venues, or people with locations, and when submission workflow or paid listings are part of the plan. Avoid it when your records are something the plugin was never shaped for, like chemical compounds or legislative records.
Route 4: Custom Database Tables
For very large or very structured datasets, the post table is the wrong home.
Every custom field on a post is a separate row in wp_postmeta. A record with 15 fields is 15 rows. 50,000 records with 15 fields each is 750,000 rows in one table, and every filtered query becomes a JOIN against wp_postmeta for each filtered field. Three filters means three JOINs. Queries that returned in 40 milliseconds at 2,000 records take several seconds at 50,000.
A custom table gives each field its own column and each column its own index. A filtered query becomes a simple WHERE clause. The cost is that you lose everything WordPress gives you for free: no dashboard editor, no permalinks, no sitemap entries, no revisions, no REST API, no caching plugin awareness. You build all of it or you do without it.
Choose this when you have well over 50,000 records, when the data is read-only reference material rather than content, or when the records genuinely do not need individual pages. Otherwise the maintenance burden outweighs the speed.
A middle path exists: keep records as a custom post type for the pages and the URLs, and maintain a separate flat lookup table that the filter query hits first, returning post IDs. It is more code, but it keeps the WordPress benefits and fixes the query speed.
Getting Your Data In
Typing 2,000 records by hand is not a plan. Three import methods cover almost every case.
WP All Import handles CSV, XML, and JSON, and maps columns to custom fields through a drag-and-drop interface. The free version covers posts and pages; custom post type and custom field import needs the Pro version plus its ACF add-on. It also supports scheduled re-imports, which is what you want if the source data updates weekly.
The plugin’s own importer. Directory plugins and table plugins usually ship one. TablePress imports CSV directly. Directorist has a listing importer. These are simpler but locked to that plugin’s structure.
WP-CLI for large jobs. A script using wp post create with --meta_input runs thousands of records faster than any browser-based importer and does not time out.
Before importing anything at scale, import ten rows first and check that every field landed in the right place. Fixing ten bad records takes a minute. Fixing 3,000 takes a database query you will be nervous about running.
Designing the Search Interface
Getting the data searchable is half the job. The other half is whether visitors can actually find what they want.
Show filters before results, not after. A visitor landing on a page of 4,000 records with filters hidden in a sidebar below the fold will assume the site is broken.
Show result counts on each filter option. “Lahore (312)” tells a visitor the filter will work. A filter that returns zero results after a click is a dead end.
Keep filter combinations sane. Eight filters with six options each is 1.6 million combinations. Most return nothing. Start with three or four filters that match how people actually narrow down, and add more only when analytics show searches failing. If you are unsure which filters people reach for first, running a short usability test on five visitors will tell you more than a month of guessing.
Handle the empty state. When filters return nothing, say so and offer a way back: “No schools match all your filters. Try removing the fee filter.” An empty white space reads as an error.
Make filtered views linkable. If filters update the URL, visitors can bookmark and share a filtered view, and you can link to useful combinations from your own content. FacetWP does this by default. Some AJAX filter plugins do not.
Decide what search engines should see. Filtered URLs multiply fast. Let Google index a handful of genuinely useful ones, such as city-level pages, and add a canonical or noindex to the rest. Indexing 40,000 filter permutations of the same 4,000 records is the definition of thin, duplicated content.
Where Each Route Breaks
| Route | Breaks at | Symptom | Fix |
|---|---|---|---|
| CPT plus search plugin | 30,000 to 50,000 records with many filters | Filtered queries take seconds; admin list screen times out | Object caching, fewer indexed fields, or move to Route 4 |
| Table plugin, client-side | Around 1,000 rows | Slow page load, browser freeze on sort, mobile crashes | Server-side processing tier, or switch routes |
| Directory plugin | When your data stops fitting the plugin’s model | Fields shoehorned into wrong types, awkward templates | Migrate to Route 1 while the record count is still small |
| Custom tables | When you need a dashboard editor or public pages | Nothing is editable without writing an interface | Hybrid: posts for pages, custom table for queries |
| FacetWP on default search | 200 matching results | Silently missing results, wrong facet counts | Add SearchWP, Relevanssi, or ElasticPress |
If Your Database Holds Personal Information
Staff directories, member lists, patient registries, and student records all carry obligations that a product catalogue does not.
A public WordPress custom post type is public. Records are reachable by direct URL, by sitemap, by the REST API at /wp-json/wp/v2/, and by anyone guessing the ID. Setting 'public' => false blocks the front end but you still need to restrict the REST endpoint and confirm the records are not being written to your sitemap. An open REST endpoint on a records database is one of the quieter routes to data exfiltration, because nothing looks broken while it is happening.
Before publishing, check three things: whether every person in the dataset agreed to their details being public, whether there is a documented way for someone to have their record removed, and whether any field would be considered sensitive under the privacy law that applies to you. Home addresses and personal phone numbers in a public searchable database are worth a second look, and a law-firm answer rather than a plugin answer.
Troubleshooting Common Problems
| Problem | Likely cause | Fix |
|---|---|---|
| Single record URLs return 404 | Permalinks not flushed after registering the post type | Settings, Permalinks, Save |
| Search returns nothing for known field values | Custom fields not indexed | Configure SearchWP or Relevanssi to index those fields, then rebuild the index |
| Results stop at 200 | FacetWP Search facet on default engine | Switch the facet to a real search engine |
| Filter counts are wrong | Same 200-result cap, or caching serving stale counts | Fix the engine first, then exclude filtered pages from page cache |
| Records vanished after a theme change | Post type registered in the theme | Move registration into a plugin |
| Some records missing from search | Index built before those records were imported | Rebuild the index |
| Filtering is fast, then slow at scale | wp_postmeta JOINs | Reduce filtered fields, add object caching, or move to custom tables |
| Imported numbers will not sort | Stored as text with symbols or commas | Re-import as clean numeric values |
| Table freezes the browser on sort | Client-side DataTables at high row count | Server-side processing |
| Search works logged in, not logged out | Records set to private, or a caching layer serving a cached empty result | Check post status, then exclude the search page from cache |
Frequently Asked Questions
Can I create a searchable database in WordPress without any plugins?
Only for small, simple datasets. You can register a post type and write a custom WP_Query with meta_query arguments in code. That gives you filtering. It does not give you relevance-ranked search across custom fields, which needs an alternative search index. For anything beyond a few hundred records with two or three filters, a plugin is less work and faster.
Does WordPress search look at custom fields?
No. Core search queries only post_title, post_excerpt, and post_content. Custom fields, taxonomy terms, and file contents are all excluded. This is the root cause of most searchable-database problems.
What is the difference between SearchWP and FacetWP?
SearchWP replaces the search engine, so custom fields, PDFs, and taxonomy terms become searchable and results are ranked by relevance. FacetWP builds the filter interface and narrows whatever results the underlying query already returned. They solve different problems and are commonly used together.
Do I need both a search plugin and a filter plugin?
If visitors will both type a search term and use filters, yes. If they will only use dropdown filters and never type, a filter plugin with custom field support can be enough. If they only type and never filter, a search plugin alone works.
How many records can WordPress handle?
Tens of thousands of posts are fine for storage. The limit is query complexity, not row count. 50,000 records with two filters is manageable on decent hosting with object caching. 20,000 records with eight filters may already be slow, because each filtered field adds a JOIN against wp_postmeta.
Is Relevanssi good enough, or do I need SearchWP?
Relevanssi’s free version indexes custom fields, sorts by relevance, and handles partial matching, which covers most needs. SearchWP is easier to configure for multi-source setups, has better documentation for weighting, and includes supported integrations. Start free and upgrade if configuration becomes the bottleneck.
Can I import my data from Excel or Google Sheets?
Yes. Export to CSV, then import with WP All Import Pro for custom post types and custom fields, with the plugin’s own importer for table and directory plugins, or with a WP-CLI script for very large files. Always test with ten rows before running the full import.
Should each record have its own page?
Only if the page is worth visiting on its own. A property listing, a school profile, or a staff biography is. A single currency exchange rate or a single row of a tax table is not, and creating thousands of near-identical pages is the pattern Google treats as scaled low-value content.
Why is my filtered search so slow?
Almost certainly wp_postmeta JOINs. Every custom field you filter on adds a JOIN to the query. Three filters means three JOINs against a table that may hold hundreds of thousands of rows. Object caching helps, reducing the number of filterable fields helps more, and at large scale a custom table or a lookup table is the real fix.
Can visitors submit records themselves?
Yes. Directory plugins include submission forms with moderation. For a custom post type build, a form plugin that supports front-end post submission covers it. Always moderate submissions before they appear, or you are running an open publishing platform.
Will a searchable database help my SEO?
It can, if each record page carries genuinely useful, distinct information and the filtered URLs are controlled. It will hurt if you publish thousands of thin pages or let Google index every filter combination. Index the record pages and a small number of high-value filtered views; canonical or noindex the rest.
Can I make PDF contents searchable?
Yes, with SearchWP or Relevanssi Premium, both of which extract text from uploaded documents during indexing. Core WordPress search never sees inside a file.
How do I search WooCommerce product attributes?
Product attributes are taxonomy terms and product data is partly in custom fields, so core search misses both. SearchWP, Relevanssi, or a WooCommerce-specific search plugin will index them. FacetWP has WooCommerce integration for the filter side.
Do I need Elasticsearch?
Almost certainly not. ElasticPress with a hosted Elasticsearch service makes sense above roughly 100,000 records or when you need multi-site federated search. Below that, SearchWP or Relevanssi on normal hosting handles it, and you avoid running a second service.
Where to Start
Answer the rows-or-records question first, because it determines everything else. Then match the route to your data:
- Records that need their own pages, under 50,000 entries: Route 1, custom post type plus a search plugin plus a filter plugin.
- Tabular data nobody needs a page for, under 1,000 rows: Route 2, TablePress.
- A genuine business or venue directory with submissions: Route 3.
- Over 50,000 read-only records with heavy filtering: Route 4, or the hybrid.
Build with 50 records before importing 5,000. Every structural mistake is cheap to fix at 50 and expensive at 5,000. If the site is already live, do the build on a staging copy rather than the production site, using the approach in cloning a website to work on it safely.
If you want a searchable database built and maintained properly, Webzeto’s WordPress development team handles custom post type architecture, data imports, and search configuration for sites at this scale.











