<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Webequipe</title>
    <description>The latest articles on DEV Community by Webequipe (@web_equipe_6ce39cc961f26b).</description>
    <link>https://dev.to/web_equipe_6ce39cc961f26b</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4002199%2Feb610c38-359c-4438-b6ac-9604d45f8211.png</url>
      <title>DEV Community: Webequipe</title>
      <link>https://dev.to/web_equipe_6ce39cc961f26b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/web_equipe_6ce39cc961f26b"/>
    <language>en</language>
    <item>
      <title>Indexing Large PDFs Without Timing Out: Background Processing with WP-Cron</title>
      <dc:creator>Webequipe</dc:creator>
      <pubDate>Fri, 31 Jul 2026 11:05:41 +0000</pubDate>
      <link>https://dev.to/webequipe/indexing-large-pdfs-without-timing-out-background-processing-with-wp-cron-published-1cg7</link>
      <guid>https://dev.to/webequipe/indexing-large-pdfs-without-timing-out-background-processing-with-wp-cron-published-1cg7</guid>
      <description>&lt;p&gt;The first version of our indexing code did exactly what you'd expect: a PDF gets uploaded, a hook fires, we parse the whole file, we store the text. Simple, and it worked fine — right up until someone uploaded a 300-page scanned course catalog and the request just… never finished.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we started with
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'add_attachment'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'webequipe_index_pdf_on_upload'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;webequipe_index_pdf_on_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attached_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;pathinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PATHINFO_EXTENSION&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'pdf'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Smalot\PdfParser\Parser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$pdf&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$parser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;parseFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$text&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pdf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;getText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// fine for a 5-page PDF, not for a 300-page one&lt;/span&gt;

    &lt;span class="nf"&gt;webequipe_store_pdf_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a handful of pages this runs in under a second. For a large, text-heavy file it can easily blow past PHP's max_execution_time — 30 seconds by default, and often less on shared hosting. When that happens, the request just gets killed mid-parse. Worst part: this was firing synchronously on the upload request itself, so the admin uploading the file was the one sitting there watching it hang.&lt;/p&gt;

&lt;p&gt;Bumping max_execution_time isn't a real fix. A lot of hosts don't let you override it at all, and even where you can, you're still tying up one PHP worker for the entire parse — which gets worse, not better, the moment two people upload large PDFs at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  The actual fix: stop trying to do it all at once
&lt;/h3&gt;

&lt;p&gt;Instead of parsing the whole file in one request, we split the work into small chunks and let WP-Cron pick them up one at a time in the background.&lt;/p&gt;

&lt;p&gt;Step one — on upload, just queue it. Don't parse anything yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'add_attachment'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'webequipe_queue_pdf_for_indexing'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;webequipe_queue_pdf_for_indexing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attached_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;pathinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PATHINFO_EXTENSION&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'pdf'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;webequipe_set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'scheduled'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;wp_next_scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'webequipe_process_pdf_chunk'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;wp_schedule_single_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'webequipe_process_pdf_chunk'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The upload request now finishes instantly. The actual parsing hasn't started — it's just been scheduled.&lt;/p&gt;

&lt;p&gt;Step two — the cron job processes a fixed number of pages, then reschedules itself if there's more to do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WEBEQUIPE_PAGES_PER_RUN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'webequipe_process_pdf_chunk'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'webequipe_process_pdf_chunk_handler'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;webequipe_process_pdf_chunk_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$file&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attached_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;get_post_meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_webequipe_page_offset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;webequipe_set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'processing'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Smalot\PdfParser\Parser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$pdf&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$parser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;parseFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$pages&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pdf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$total&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$pages&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;WEBEQUIPE_PAGES_PER_RUN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;webequipe_store_page_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;getText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;update_post_meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_webequipe_page_offset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$end&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$end&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;wp_schedule_single_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'webequipe_process_pdf_chunk'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;webequipe_set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'indexed'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;delete_post_meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_webequipe_page_offset'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty pages at a time, every few seconds, until the whole document is indexed. No single request ever does enough work to risk a timeout, no matter how large the file is.&lt;/p&gt;

&lt;p&gt;One honest caveat on the snippet above: re-parsing the entire PDF on every chunk just to reach the next offset is wasteful for very large files — in practice you'd want to cache the parsed object or extract page references once rather than re-running parseFile() on every cron tick. I simplified that part here to keep the example readable; it's the kind of optimization that matters more once you're indexing hundreds of large files a day than it does for a single one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking progress so the admin isn't guessing
&lt;/h3&gt;

&lt;p&gt;None of this is useful if nobody can tell what's actually happening to their file. We track status per attachment in a dedicated table rather than relying on post meta alone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;wpdb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;webequipe_pdf_search_files&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;attachment_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'not_indexed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_pages&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;indexed_pages&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;attachment_id&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attachment_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That status column is what powers the Manage PDFs screen — every file sits in one of: Not Indexed, Scheduled, Processing, Indexed, Stalled, Error, or Excluded. "Stalled" specifically exists to catch the case where a scheduled cron event never actually ran — which brings up the one WP-Cron quirk worth knowing about.&lt;/p&gt;

&lt;h3&gt;
  
  
  WP-Cron isn't a real cron
&lt;/h3&gt;

&lt;p&gt;WordPress's cron system doesn't run on a timer in the background the way a system cron job does — it only fires when someone visits the site. On a low-traffic site, that means a scheduled indexing job can sit waiting for a while if nobody happens to load a page.&lt;/p&gt;

&lt;p&gt;For anything beyond small personal sites, we point people toward disabling WP-Cron's default trigger and hitting wp-cron.php on a real system cron instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// wp-config.php&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'DISABLE_WP_CRON'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;bash&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# real system cron, every 5 minutes
&lt;/span&gt;*/&lt;span class="m"&gt;5&lt;/span&gt; * * * * &lt;span class="n"&gt;curl&lt;/span&gt; -&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;/&lt;span class="n"&gt;wp&lt;/span&gt;-&lt;span class="n"&gt;cron&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt; &amp;gt; /&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;null&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&amp;amp;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a small config change, but it's the difference between "indexing happens promptly" and "indexing happens whenever someone next visits the site."&lt;/p&gt;

&lt;h3&gt;
  
  
  What this actually bought us
&lt;/h3&gt;

&lt;p&gt;Visitors never notice any of this — search queries run against whatever's already been indexed, and indexing itself never touches a front-end page load. On the admin side, even a huge scanned archive just ticks along in the background instead of hanging a browser tab or silently failing halfway through.&lt;/p&gt;

&lt;p&gt;The bigger lesson generalizes past PDFs, honestly: if a task's runtime scales with user-supplied input — file size, record count, whatever — assuming one request can always finish it is asking for a timeout eventually. Chunking with an explicit, inspectable progress state turned out to be a lot more robust than just hoping the PHP process would have enough time.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>wordpress</category>
      <category>php</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why Testing Our WordPress Plugin on One Website Wasn’t Enough</title>
      <dc:creator>Webequipe</dc:creator>
      <pubDate>Thu, 23 Jul 2026 11:58:32 +0000</pubDate>
      <link>https://dev.to/web_equipe_6ce39cc961f26b/why-testing-our-wordpress-plugin-on-one-website-wasnt-enough-5e30</link>
      <guid>https://dev.to/web_equipe_6ce39cc961f26b/why-testing-our-wordpress-plugin-on-one-website-wasnt-enough-5e30</guid>
      <description>&lt;p&gt;When we first tested our WordPress PDF search plugin, everything worked as expected.&lt;/p&gt;

&lt;p&gt;We installed it on a clean WordPress website, added the search form using a shortcode, uploaded several PDFs, and tested the full process.&lt;/p&gt;

&lt;p&gt;The form looked good.&lt;/p&gt;

&lt;p&gt;The search button worked.&lt;/p&gt;

&lt;p&gt;The results appeared correctly.&lt;/p&gt;

&lt;p&gt;From our side, the feature looked ready.&lt;/p&gt;

&lt;p&gt;Then we tested the plugin on another WordPress website using the Avada theme.&lt;/p&gt;

&lt;p&gt;The search form appeared, but the layout was broken. The search button moved below the input field, some styles were overwritten, and in certain layouts the form width became much smaller than expected.&lt;/p&gt;

&lt;p&gt;The plugin was working, but it did not feel like a working product.&lt;/p&gt;

&lt;p&gt;That was when we learned an important lesson:&lt;/p&gt;

&lt;p&gt;A WordPress plugin is not really tested until it has been tested outside the environment where it was developed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Issue We Found
&lt;/h2&gt;

&lt;p&gt;Our PDF search form was added using a shortcode.&lt;/p&gt;

&lt;p&gt;A website administrator could place something like this on any page:&lt;br&gt;
&lt;code&gt;[webequipe_pdf_search_form]&lt;/code&gt;&lt;br&gt;
The shortcode generated a simple search form with:&lt;/p&gt;

&lt;p&gt;A search input&lt;br&gt;
A search button&lt;br&gt;
A results container&lt;/p&gt;

&lt;p&gt;On our test website, the input and button appeared in one row.&lt;/p&gt;

&lt;p&gt;But on the Avada website, the theme’s global form styles affected our plugin.&lt;/p&gt;

&lt;p&gt;Avada already included CSS rules for form inputs, buttons, containers, and responsive layouts. Some of those styles were more specific than our plugin styles, so the theme rules took priority.&lt;br&gt;
The result looked something like this:&lt;/p&gt;

&lt;p&gt;The input inherited an unexpected height&lt;br&gt;
The button received theme padding&lt;br&gt;
The button width changed&lt;br&gt;
The form container inherited a maximum width&lt;br&gt;
Mobile styles were triggered earlier than expected&lt;/p&gt;

&lt;p&gt;This was not a PHP error or a fatal WordPress error.&lt;/p&gt;

&lt;p&gt;It was a compatibility issue.&lt;/p&gt;

&lt;p&gt;And from a customer’s point of view, a broken layout is still a plugin problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Biggest Mistake in Our Code
&lt;/h2&gt;

&lt;p&gt;Our original CSS was too general.&lt;/p&gt;

&lt;p&gt;We used selectors like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.pdf-search-form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="nc"&gt;.pdf-search-form&lt;/span&gt; &lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="nc"&gt;.pdf-search-form&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked on a basic WordPress theme.&lt;/p&gt;

&lt;p&gt;But it created two problems.&lt;/p&gt;

&lt;p&gt;First, the class name was not specific enough. Another theme or plugin could use a similar class name.&lt;/p&gt;

&lt;p&gt;Second, our selectors were weaker than some theme selectors.&lt;/p&gt;

&lt;p&gt;A theme might use a rule like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.fusion-body&lt;/span&gt; &lt;span class="nc"&gt;.post-content&lt;/span&gt; &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"search"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
   &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because that selector was more specific, it could override our plugin styling.&lt;/p&gt;

&lt;p&gt;Our code was not technically incorrect.&lt;/p&gt;

&lt;p&gt;But it assumed that the plugin would control the page styling.&lt;/p&gt;

&lt;p&gt;In WordPress, that assumption is risky.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Investigated the Problem
&lt;/h2&gt;

&lt;p&gt;At first, we checked whether the shortcode output was different.&lt;/p&gt;

&lt;p&gt;It was not.&lt;/p&gt;

&lt;p&gt;The same HTML was generated on both websites.&lt;/p&gt;

&lt;p&gt;Then we checked whether JavaScript was failing.&lt;/p&gt;

&lt;p&gt;There were no console errors.&lt;/p&gt;

&lt;p&gt;Next, we inspected the form using the browser developer tools.&lt;/p&gt;

&lt;p&gt;That showed us the real issue.&lt;/p&gt;

&lt;p&gt;Several styles were coming from the Avada theme instead of our plugin stylesheet.&lt;/p&gt;

&lt;p&gt;We tested the same plugin with a default WordPress theme, and the form worked correctly again.&lt;/p&gt;

&lt;p&gt;That confirmed that the problem was not the shortcode or search logic. It was the CSS interaction between the plugin and the theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Fixed It
&lt;/h2&gt;

&lt;p&gt;We updated the plugin to use a unique wrapper class.&lt;/p&gt;

&lt;p&gt;Instead of relying only on a general form class, we wrapped the full shortcode output inside a plugin-specific container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;function webequipe_render_pdf_search_form() {
    ob_start();
    ?&amp;gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"webequipe-pdf-search"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"webequipe-pdf-search__form"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"get"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
                &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt;
                &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"pdf_search"&lt;/span&gt;
                &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"webequipe-pdf-search__input"&lt;/span&gt;
                &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Search PDFs..."&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
                &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;
                &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"webequipe-pdf-search__button"&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                Search
            &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"webequipe-pdf-search__results"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;ob_get_clean&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;add_shortcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'webequipe_pdf_search_form'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'webequipe_render_pdf_search_form'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we updated the CSS to target only elements inside our plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.webequipe-pdf-search&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.webequipe-pdf-search&lt;/span&gt; &lt;span class="nc"&gt;.webequipe-pdf-search__form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.webequipe-pdf-search&lt;/span&gt; &lt;span class="nc"&gt;.webequipe-pdf-search__input&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;48px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.webequipe-pdf-search&lt;/span&gt; &lt;span class="nc"&gt;.webequipe-pdf-search__button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;110px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;48px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;18px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;.webequipe-pdf-search&lt;/span&gt; &lt;span class="nc"&gt;.webequipe-pdf-search__form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.webequipe-pdf-search&lt;/span&gt; &lt;span class="nc"&gt;.webequipe-pdf-search__button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This made the styles more specific without depending on Avada itself.&lt;/p&gt;

&lt;p&gt;We did not add code such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.avada-theme&lt;/span&gt; &lt;span class="nc"&gt;.pdf-search-form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* Avada-specific fix */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That would have solved only one theme problem.&lt;br&gt;
Instead, we improved the plugin’s own CSS structure so it would be safer across many themes.&lt;/p&gt;
&lt;h2&gt;
  
  
  We Also Changed How the Styles Were Loaded
&lt;/h2&gt;

&lt;p&gt;Previously, the stylesheet was loaded everywhere.&lt;/p&gt;

&lt;p&gt;We improved this by loading the required assets when the shortcode was used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;webequipe_pdf_search_shortcode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;wp_enqueue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'webequipe-pdf-search'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;plugin_dir_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'assets/css/pdf-search.css'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s1"&gt;'1.0.1'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;webequipe_render_pdf_search_form&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;add_shortcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'webequipe_pdf_search_form'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'webequipe_pdf_search_shortcode'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helped us keep the plugin assets connected to the feature that needed them.&lt;/p&gt;

&lt;p&gt;It also reduced the chance of unnecessary styles affecting other parts of the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Tested the Fix
&lt;/h2&gt;

&lt;p&gt;After updating the code, we did not test it only on the same development website.&lt;/p&gt;

&lt;p&gt;We checked the shortcode on:&lt;/p&gt;

&lt;p&gt;A clean WordPress installation&lt;br&gt;
A website using Avada&lt;br&gt;
A website using Elementor&lt;br&gt;
A default WordPress block theme&lt;br&gt;
Desktop, tablet, and mobile screen sizes&lt;br&gt;
Pages with narrow and full-width containers&lt;/p&gt;

&lt;p&gt;We also tested the form inside different page sections.&lt;/p&gt;

&lt;p&gt;That was important because the same theme can behave differently depending on whether the shortcode is placed inside a full-width section, column, widget area, or normal content area.&lt;/p&gt;

&lt;p&gt;What We Learned&lt;/p&gt;

&lt;p&gt;The biggest lesson was not that Avada caused a problem.&lt;/p&gt;

&lt;p&gt;Avada was doing what a WordPress theme normally does: applying consistent styles across the website.&lt;/p&gt;

&lt;p&gt;The real issue was that our plugin was not isolated enough from the surrounding website.&lt;/p&gt;

&lt;p&gt;We learned a few important things from this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Use unique names
&lt;/h3&gt;

&lt;p&gt;Plugin classes, CSS selectors, functions, hooks, and database keys should use a unique prefix.&lt;/p&gt;

&lt;p&gt;A general name may work today but conflict with another theme or plugin later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test inside real page builders
&lt;/h3&gt;

&lt;p&gt;A shortcode should not only be tested inside the standard WordPress editor.&lt;/p&gt;

&lt;p&gt;It should also be tested inside popular themes and page builders where users are likely to place it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not test only functionality
&lt;/h3&gt;

&lt;p&gt;The search feature technically worked on the Avada website.&lt;/p&gt;

&lt;p&gt;But the layout problem still affected the customer experience.&lt;/p&gt;

&lt;p&gt;Plugin QA should include both functionality and visual behaviour.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid theme-specific fixes when possible
&lt;/h3&gt;

&lt;p&gt;Adding separate fixes for every theme can become difficult to maintain.&lt;/p&gt;

&lt;p&gt;It is usually better to strengthen the plugin’s own structure first.&lt;/p&gt;

&lt;h3&gt;
  
  
  One WordPress website is only one environment
&lt;/h3&gt;

&lt;p&gt;A successful test on one website does not prove that a plugin is ready for every WordPress website.&lt;/p&gt;

&lt;p&gt;Each site may have a different theme, page builder, CSS structure, plugin combination, PHP version, and hosting setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://webequipe.com/pdf-search/" rel="noopener noreferrer"&gt;WebEquipe PDF Search&lt;/a&gt; did not fail because its PDF indexing or search logic was broken.&lt;/p&gt;

&lt;p&gt;The issue appeared because we had tested the search form in an environment that was too controlled. Once we placed the same shortcode on a website using the Avada theme, we saw how easily theme styles could affect the plugin’s layout.&lt;/p&gt;

&lt;p&gt;This experience changed how we test &lt;a href="https://webequipe.com/pdf-search/" rel="noopener noreferrer"&gt;WebEquipe PDF Search&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We now understand that building a WordPress plugin is not only about making its main feature work. It is also about making sure that feature continues to work across themes, page builders, screen sizes, and website configurations we do not control.&lt;/p&gt;

&lt;p&gt;A successful test on our development website is no longer the end of QA.&lt;/p&gt;

&lt;p&gt;For WebEquipe PDF Search, it is only the beginning.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>css</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Audit PDF Content on a WordPress Site Before Improving Search</title>
      <dc:creator>Webequipe</dc:creator>
      <pubDate>Thu, 16 Jul 2026 10:28:16 +0000</pubDate>
      <link>https://dev.to/web_equipe_6ce39cc961f26b/how-to-audit-pdf-content-on-a-wordpress-site-before-improving-search-3385</link>
      <guid>https://dev.to/web_equipe_6ce39cc961f26b/how-to-audit-pdf-content-on-a-wordpress-site-before-improving-search-3385</guid>
      <description>&lt;p&gt;Sure, this topic is better for dev.to. Here is the blog draft:&lt;/p&gt;

&lt;p&gt;How to Audit PDF Content on a WordPress Site Before Improving Search&lt;/p&gt;

&lt;p&gt;Many WordPress sites have PDF files.&lt;/p&gt;

&lt;p&gt;Some have only a few brochures or forms.&lt;br&gt;
Some have hundreds of documents, reports, manuals, policies, notices, or guides.&lt;/p&gt;

&lt;p&gt;But before improving PDF search, one important step is often skipped:&lt;/p&gt;

&lt;p&gt;Auditing the PDF content first.&lt;/p&gt;

&lt;p&gt;Because not every PDF problem is a search problem.&lt;/p&gt;

&lt;p&gt;Sometimes the issue is poor file naming.&lt;br&gt;
Sometimes PDFs are scanned and need OCR.&lt;br&gt;
Sometimes important documents are uploaded but not linked from any page.&lt;br&gt;
Sometimes private files are accidentally public.&lt;/p&gt;

&lt;p&gt;A simple audit helps you understand what you actually need.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Check how many PDFs exist
&lt;/h2&gt;

&lt;p&gt;Start with the basic question:&lt;/p&gt;

&lt;p&gt;How many PDFs are uploaded to the WordPress media library?&lt;/p&gt;

&lt;p&gt;If you are a developer, you can quickly check PDF attachments like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$pdfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_posts&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'post_type'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'attachment'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'post_mime_type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'application/pdf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'post_status'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'inherit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'posts_per_page'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Total PDFs found: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pdfs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the number is small, you may only need better page links and titles.&lt;/p&gt;

&lt;p&gt;If the number is large, you probably need a proper document search experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Review file names
&lt;/h2&gt;

&lt;p&gt;Bad file names make PDFs harder to manage.&lt;/p&gt;

&lt;p&gt;Examples of weak names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file-final.pdf
document-new.pdf
scan-123.pdf
updated-copy.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;employee-handbook-2026.pdf
product-safety-manual.pdf
annual-report-2025.pdf
refund-policy.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good file names help admins, developers, and users understand the document faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Check if PDFs are text-based or scanned
&lt;/h2&gt;

&lt;p&gt;This is one of the most important audit steps.&lt;/p&gt;

&lt;p&gt;Open a PDF and try to select the text.&lt;/p&gt;

&lt;p&gt;If you can select and copy the words, it is probably a text-based PDF.&lt;/p&gt;

&lt;p&gt;If you cannot select the words, it may be a scanned PDF.&lt;/p&gt;

&lt;p&gt;Simple rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text can be copied → text-based PDF
Text cannot be copied → scanned PDF, OCR may be needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because text-based PDFs are easier to index.&lt;br&gt;
Scanned PDFs usually need OCR before they become searchable.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find important but hidden PDFs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A PDF can be uploaded to WordPress but still be hard to find.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;p&gt;Is this PDF linked from any page?&lt;br&gt;
Is it part of a document library?&lt;br&gt;
Does the page explain what the PDF contains?&lt;br&gt;
Can users find it without knowing the exact file name?&lt;/p&gt;

&lt;p&gt;A PDF should not only be a download link. It should have context.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Product Safety Manual&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Includes setup instructions, safety rules, and maintenance notes.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/uploads/product-safety-manual.pdf"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;View PDF&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is better than only saying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/uploads/file.pdf"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Download&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Check private or restricted PDFs
&lt;/h2&gt;

&lt;p&gt;Not every PDF should be public.&lt;/p&gt;

&lt;p&gt;Some websites have private documents for members, staff, students, or customers.&lt;/p&gt;

&lt;p&gt;During the audit, check:&lt;/p&gt;

&lt;p&gt;Which PDFs should be public?&lt;br&gt;
Which PDFs should be private?&lt;br&gt;
Are private PDFs appearing in public search results?&lt;br&gt;
Are restricted PDFs protected properly?&lt;/p&gt;

&lt;p&gt;This is very important for membership sites, internal portals, schools, healthcare sites, and organizations with private documents.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Check what users are actually searching for
&lt;/h2&gt;

&lt;p&gt;Before changing search, try to understand user needs.&lt;/p&gt;

&lt;p&gt;Look at:&lt;/p&gt;

&lt;p&gt;common support questions&lt;br&gt;
site search logs, if available&lt;br&gt;
popular document pages&lt;br&gt;
frequently downloaded PDFs&lt;br&gt;
repeated questions from users&lt;/p&gt;

&lt;p&gt;This helps you decide which PDFs are most important to make searchable first.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Decide what kind of PDF search you need
&lt;/h2&gt;

&lt;p&gt;After the audit, you can make a better decision.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovu67h2zq9qwmadm4k66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovu67h2zq9qwmadm4k66.png" alt=" " width="619" height="288"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Where WebEquipe PDF Search fits
&lt;/h2&gt;

&lt;p&gt;For WordPress sites with many PDFs, &lt;a href="https://webequipe.com/pdf-search/" rel="noopener noreferrer"&gt;WebEquipe PDF Search&lt;/a&gt; can help make PDF content searchable.&lt;/p&gt;

&lt;p&gt;It can index text-based PDFs and let users search inside PDF content using a shortcode search form.&lt;/p&gt;

&lt;p&gt;For scanned PDFs, OCR support is needed, which is part of the Pro workflow.&lt;/p&gt;

&lt;p&gt;The main goal is simple:&lt;/p&gt;

&lt;p&gt;Help users find the information inside PDFs, not just the PDF file itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  Final checklist
&lt;/h2&gt;

&lt;p&gt;Before improving PDF search on a WordPress site, check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ How many PDFs are uploaded?
✅ Are file names clear?
✅ Are PDFs text-based or scanned?
✅ Are important PDFs linked from pages?
✅ Are private PDFs protected?
✅ What are users searching for?
✅ Do you need normal indexing, OCR, or private search?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Improving PDF search is not only about adding a search box.&lt;/p&gt;

&lt;p&gt;First, you need to understand your PDF content.&lt;/p&gt;

&lt;p&gt;A simple audit can show whether your site needs better organization, OCR, private search, or full PDF indexing.&lt;/p&gt;

&lt;p&gt;Once you know that, improving search becomes much easier — and much more useful for your users.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>seo</category>
    </item>
    <item>
      <title>AI Agents Can Read Your Website — But Can They Read Your PDF</title>
      <dc:creator>Webequipe</dc:creator>
      <pubDate>Thu, 09 Jul 2026 07:02:31 +0000</pubDate>
      <link>https://dev.to/web_equipe_6ce39cc961f26b/ai-agents-can-read-your-website-but-can-they-read-your-pdf-1k2b</link>
      <guid>https://dev.to/web_equipe_6ce39cc961f26b/ai-agents-can-read-your-website-but-can-they-read-your-pdf-1k2b</guid>
      <description>&lt;p&gt;Websites are changing.&lt;/p&gt;

&lt;p&gt;A few years ago, we mainly cared about human visitors and Google search. Now we also need to think about AI search tools, chatbots, RAG systems, and AI agents that read website content to answer questions.&lt;/p&gt;

&lt;p&gt;But there is one problem many WordPress sites still ignore:&lt;/p&gt;

&lt;p&gt;A lot of important content is locked inside PDFs.&lt;/p&gt;

&lt;p&gt;And if that PDF content is not searchable or properly indexed, users may never find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  PDFs Are Not Just Attachments
&lt;/h2&gt;

&lt;p&gt;Many websites upload PDFs for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manuals&lt;/li&gt;
&lt;li&gt;reports&lt;/li&gt;
&lt;li&gt;policy documents&lt;/li&gt;
&lt;li&gt;forms&lt;/li&gt;
&lt;li&gt;product guides&lt;/li&gt;
&lt;li&gt;public notices&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;brochures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem is that most site owners treat these PDFs like simple downloadable files.&lt;/p&gt;

&lt;p&gt;But if the PDF contains useful information, then it is not just a file.&lt;/p&gt;

&lt;p&gt;It is part of your website content.&lt;/p&gt;

&lt;p&gt;For example, a visitor may search: refund policy for international orders&lt;/p&gt;

&lt;p&gt;Maybe the answer exists inside a PDF. But if your WordPress search cannot read inside that PDF, the visitor gets no result.&lt;/p&gt;

&lt;p&gt;From the user’s point of view, the content does not exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Agents Have the Same Problem
&lt;/h2&gt;

&lt;p&gt;AI tools are becoming better at reading web pages. But a random PDF link is not always enough.&lt;/p&gt;

&lt;p&gt;An AI agent or chatbot needs clear, accessible, searchable content.&lt;/p&gt;

&lt;p&gt;If your important information is inside a PDF but not extracted or indexed, then your site creates a gap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Website pages = readable
PDF files = often hidden
Search result = incomplete
AI answer = missing context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a bigger issue for websites that depend on documents, such as universities, government websites, nonprofits, healthcare sites, legal sites, SaaS documentation, and product manual libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text PDFs vs Scanned PDFs
&lt;/h2&gt;

&lt;p&gt;Not all PDFs are the same.&lt;/p&gt;

&lt;p&gt;There are usually three types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Text-based PDFs&lt;br&gt;
These PDFs contain selectable text. The text can usually be extracted and indexed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scanned PDFs&lt;br&gt;
These are basically images inside a PDF. You cannot properly search them unless OCR is used.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mixed PDFs&lt;br&gt;
Some pages have text, and some pages are scanned images.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This matters because your search system must understand what kind of PDF it is handling.&lt;/p&gt;

&lt;p&gt;A simple text PDF may only need text extraction.&lt;br&gt;
A scanned PDF needs OCR.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Quick WordPress Check
&lt;/h2&gt;

&lt;p&gt;If you are a developer, you can quickly check how many PDFs exist on a WordPress site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$pdfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_posts&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'post_type'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'attachment'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'post_mime_type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'application/pdf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'post_status'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'inherit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'posts_per_page'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Total PDFs found: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pdfs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the number is high, then your site probably has a lot of hidden content.&lt;/p&gt;

&lt;p&gt;The next question is:&lt;/p&gt;

&lt;p&gt;Can users actually search inside those PDFs?&lt;/p&gt;

&lt;h2&gt;
  
  
  Make PDFs More Useful
&lt;/h2&gt;

&lt;p&gt;A better document structure can help both users and AI systems.&lt;/p&gt;

&lt;p&gt;Instead of only adding a plain PDF link like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/files/safety-manual.pdf"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Download PDF&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add context around it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"document-card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Product Safety Manual&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    Includes installation steps, safety warnings,
    and maintenance instructions.
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/files/safety-manual.pdf"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;View PDF&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps users understand the document before opening it.&lt;br&gt;
It also gives search engines and AI tools better context.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Simple PDF Search Workflow
&lt;/h2&gt;

&lt;p&gt;A useful PDF search system should work like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PDF uploaded&lt;br&gt;
↓&lt;br&gt;
Text extracted or OCR processed&lt;br&gt;
↓&lt;br&gt;
Content indexed&lt;br&gt;
↓&lt;br&gt;
User searches from WordPress&lt;br&gt;
↓&lt;br&gt;
Relevant PDF result appears&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That is the missing layer in many WordPress websites.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where WebEquipe PDF Search Fits
&lt;/h2&gt;

&lt;p&gt;For WordPress sites, a plugin like &lt;a href="https://webequipe.com/pdf-search/" rel="noopener noreferrer"&gt;WebEquipe PDF Search&lt;/a&gt; can help make PDF content searchable.&lt;/p&gt;

&lt;p&gt;You can add a PDF search form using a shortcode like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[webequipe_pdf_search_form placeholder="Search PDFs..." button_text="Search" results_per_page="10"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The free version is useful for text-based PDFs.&lt;br&gt;
For scanned PDFs, OCR support is needed, which is where a Pro/OCR workflow becomes important.&lt;/p&gt;

&lt;p&gt;The goal is not only to “search files.”&lt;/p&gt;

&lt;p&gt;The real goal is to make PDF content part of the website experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;AI agents, chatbots, and modern search tools are changing how people find information.&lt;/p&gt;

&lt;p&gt;But if your most valuable content is hidden inside PDFs, your website may look complete while still being difficult to search.&lt;/p&gt;

&lt;p&gt;So before adding another AI tool to your WordPress site, ask one simple question:&lt;/p&gt;

&lt;p&gt;Can users — and AI systems — actually read what is inside your PDFs?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>wordpress</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why PDF Indexing in WordPress Needs Background Processing</title>
      <dc:creator>Webequipe</dc:creator>
      <pubDate>Fri, 03 Jul 2026 06:20:24 +0000</pubDate>
      <link>https://dev.to/web_equipe_6ce39cc961f26b/why-pdf-indexing-in-wordpress-needs-background-processing-3em</link>
      <guid>https://dev.to/web_equipe_6ce39cc961f26b/why-pdf-indexing-in-wordpress-needs-background-processing-3em</guid>
      <description>&lt;p&gt;Uploading a PDF to WordPress feels simple.&lt;/p&gt;

&lt;p&gt;You add the file to the Media Library, WordPress stores it, and the file is ready to use.&lt;/p&gt;

&lt;p&gt;But if you want that PDF to become searchable, the work does not stop at upload.&lt;/p&gt;

&lt;p&gt;The plugin has to read the PDF, extract the text, clean the content, store it in a searchable index, and later match that content with user search queries.&lt;/p&gt;

&lt;p&gt;That sounds fine for one small PDF.&lt;/p&gt;

&lt;p&gt;But what happens when the file has 100 pages?&lt;/p&gt;

&lt;p&gt;Or when the website has hundreds of PDFs?&lt;/p&gt;

&lt;p&gt;Or when the PDF is scanned and needs OCR?&lt;/p&gt;

&lt;p&gt;That is where background processing becomes important.&lt;/p&gt;

&lt;h2&gt;
  
  
  PDF Indexing Is Heavier Than Normal WordPress Tasks
&lt;/h2&gt;

&lt;p&gt;Most WordPress content is already stored in the database.&lt;/p&gt;

&lt;p&gt;When you publish a post, the title and body content are saved in fields like &lt;code&gt;post_title&lt;/code&gt; and &lt;code&gt;post_content&lt;/code&gt;. Search can query that data directly.&lt;/p&gt;

&lt;p&gt;PDFs are different.&lt;/p&gt;

&lt;p&gt;A PDF is a file. WordPress stores the file information, but it does not automatically read the actual text inside that file.&lt;/p&gt;

&lt;p&gt;So a PDF search plugin needs to do extra work:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb6elz4ix7h5p4qw4qoby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb6elz4ix7h5p4qw4qoby.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a small text-based PDF, this may happen quickly.&lt;/p&gt;

&lt;p&gt;But large files can take more time. Some PDFs have complex layouts, repeated headers, tables, images, or broken text structure. Scanned PDFs are even heavier because they need OCR before any text can be indexed.&lt;/p&gt;

&lt;p&gt;Trying to do all of this instantly during upload is risky.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Processing During Upload
&lt;/h2&gt;

&lt;p&gt;When a user uploads a PDF, WordPress is handling that request in real time.&lt;/p&gt;

&lt;p&gt;If your plugin starts parsing a large PDF immediately, several things can go wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The upload request may become slow&lt;/li&gt;
&lt;li&gt;PHP execution time may run out&lt;/li&gt;
&lt;li&gt;Memory usage may increase&lt;/li&gt;
&lt;li&gt;The admin screen may freeze&lt;/li&gt;
&lt;li&gt;OCR processing may take too long&lt;/li&gt;
&lt;li&gt;The user may not know whether indexing succeeded or failed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a bad experience.&lt;/p&gt;

&lt;p&gt;The user only wanted to upload a document. They should not have to wait while the system tries to fully process a 200-page PDF in the same request.&lt;/p&gt;

&lt;p&gt;That is why PDF indexing should be treated as a background job, not an upload-time task.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Flow: Queue the Work
&lt;/h2&gt;

&lt;p&gt;A more reliable approach is to separate file upload from file processing.&lt;/p&gt;

&lt;p&gt;The upload should stay fast. After upload, the plugin creates an indexing job and processes it later.&lt;/p&gt;

&lt;p&gt;A simple flow looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7011w3l8ow248bozwcgr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7011w3l8ow248bozwcgr.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This makes the system much easier to manage.&lt;/p&gt;

&lt;p&gt;Instead of forcing everything to happen immediately, the plugin can process PDFs one by one in the background.&lt;/p&gt;

&lt;p&gt;For example, the plugin can store a job like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'attachment_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;245&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'status'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'type'&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text_extraction'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'attempts'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'created_at'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;current_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a background worker can pick up pending jobs and process them safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status Tracking Matters
&lt;/h2&gt;

&lt;p&gt;Background processing also makes status tracking possible.&lt;/p&gt;

&lt;p&gt;A PDF can move through states like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pending
processing
indexed
ocr_required
failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for admins.&lt;/p&gt;

&lt;p&gt;Without status tracking, users have no idea what happened. A PDF may be uploaded, but not searchable. Or OCR may fail silently. Or the file may be too large to process.&lt;/p&gt;

&lt;p&gt;A clear status helps users understand the situation.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Pending” means the file is waiting to be processed&lt;/li&gt;
&lt;li&gt;“Processing” means indexing is running&lt;/li&gt;
&lt;li&gt;“Indexed” means the PDF is searchable&lt;/li&gt;
&lt;li&gt;“OCR required” means normal text extraction found little or no text&lt;/li&gt;
&lt;li&gt;“Failed” means the job needs attention or retry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the plugin feel more predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  OCR Makes Background Processing Even More Important
&lt;/h2&gt;

&lt;p&gt;Text-based PDFs can often be processed with a parser.&lt;/p&gt;

&lt;p&gt;Scanned PDFs are different.&lt;/p&gt;

&lt;p&gt;A scanned PDF is basically an image inside a PDF file. A normal parser may return empty content because there is no text layer.&lt;/p&gt;

&lt;p&gt;In that case, OCR is needed.&lt;/p&gt;

&lt;p&gt;OCR usually means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Convert PDF page to image
↓
Send image to OCR engine
↓
Receive detected text
↓
Store text in search index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much heavier than normal text extraction.&lt;/p&gt;

&lt;p&gt;If the plugin uses an external OCR service like Google Vision API, the processing time also depends on API response time, page count, network conditions, and retry handling.&lt;/p&gt;

&lt;p&gt;Running this during upload is not a good idea.&lt;/p&gt;

&lt;p&gt;A queue-based system gives the plugin room to process OCR safely without breaking the admin experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retry Logic Helps With Real-World Failures
&lt;/h2&gt;

&lt;p&gt;Background jobs can fail for many reasons.&lt;/p&gt;

&lt;p&gt;The file may be corrupted.&lt;br&gt;
The server may run out of memory.&lt;br&gt;
The OCR API may timeout.&lt;br&gt;
The PDF parser may fail on a strange file structure.&lt;/p&gt;

&lt;p&gt;Instead of failing permanently, the plugin can retry the job.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'attempts'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;retry_indexing_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;mark_job_as_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the system more resilient.&lt;/p&gt;

&lt;p&gt;Not every failure needs to become a support issue. Some failures can be retried automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;PDF search looks simple from the outside.&lt;/p&gt;

&lt;p&gt;A visitor searches, and a PDF appears in the result.&lt;/p&gt;

&lt;p&gt;But behind that simple experience, the plugin has to extract content, detect scanned files, run OCR when needed, store searchable text, and handle failures.&lt;/p&gt;

&lt;p&gt;That is why background processing matters.&lt;/p&gt;

&lt;p&gt;It keeps uploads fast.&lt;br&gt;
It avoids PHP timeout issues.&lt;br&gt;
It makes OCR manageable.&lt;br&gt;
It allows status tracking.&lt;br&gt;
It gives the system a way to retry failed jobs.&lt;/p&gt;

&lt;p&gt;For WebEquipe PDF Search, this kind of thinking is important because PDF indexing is not just a search feature. It is a processing pipeline.&lt;/p&gt;

&lt;p&gt;And when a WordPress plugin handles heavy file processing, the best experience is usually the one users do not have to wait for.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>performance</category>
    </item>
    <item>
      <title>Why WordPress Search Ignores Your PDFs — and How to Fix It</title>
      <dc:creator>Webequipe</dc:creator>
      <pubDate>Thu, 25 Jun 2026 11:41:45 +0000</pubDate>
      <link>https://dev.to/web_equipe_6ce39cc961f26b/why-wordpress-search-ignores-your-pdfs-and-how-to-fix-it-3c45</link>
      <guid>https://dev.to/web_equipe_6ce39cc961f26b/why-wordpress-search-ignores-your-pdfs-and-how-to-fix-it-3c45</guid>
      <description>&lt;p&gt;WordPress search works well when your content lives inside posts and pages.&lt;/p&gt;

&lt;p&gt;But if important information is inside PDF files, WordPress often misses it completely.&lt;/p&gt;

&lt;p&gt;A visitor may search for a phrase that clearly exists inside a PDF manual, report, form, or policy document. But WordPress returns no result.&lt;/p&gt;

&lt;p&gt;The PDF is uploaded.&lt;br&gt;
The content exists.&lt;br&gt;
But search still cannot find it.&lt;/p&gt;

&lt;p&gt;So why does this happen?&lt;/p&gt;
&lt;h2&gt;
  
  
  WordPress Search Does Not Read PDF Content
&lt;/h2&gt;

&lt;p&gt;Default WordPress search mainly checks database fields like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;post_title
post_content
post_excerpt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you publish a post, the content is stored in &lt;code&gt;post_content&lt;/code&gt;, so WordPress can search it.&lt;/p&gt;

&lt;p&gt;But when you upload a PDF, WordPress treats it as a media attachment. It stores the file title, URL, MIME type, upload date, and some metadata.&lt;/p&gt;

&lt;p&gt;What it does not do by default is open the PDF, read the text inside it, and save that text into &lt;code&gt;post_content&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So this kind of situation is common:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PDF file: employee-handbook.pdf
Text inside PDF: "Remote work requests must be approved by HR"
WordPress searchable content: filename and attachment data only
Search query: "remote work requests"
Result: no match
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The text is inside the file, but not inside the database fields WordPress normally searches.&lt;/p&gt;

&lt;p&gt;That is the main reason PDF search fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not All PDFs Are the Same
&lt;/h2&gt;

&lt;p&gt;Before fixing PDF search, you need to understand one important difference.&lt;/p&gt;

&lt;p&gt;There are two common types of PDFs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Text-based PDFs&lt;/li&gt;
&lt;li&gt;Scanned PDFs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;They may look the same in the browser, but technically they behave very differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text-Based PDFs
&lt;/h2&gt;

&lt;p&gt;A text-based PDF contains real selectable text.&lt;/p&gt;

&lt;p&gt;If you can open a PDF, select a sentence, copy it, and paste it into a text editor, it probably has a text layer.&lt;/p&gt;

&lt;p&gt;These PDFs are usually exported from tools like Word, Google Docs, InDesign, or reporting software.&lt;/p&gt;

&lt;p&gt;For these files, a plugin can use a PDF parser to extract the text.&lt;br&gt;
In PHP, one common library is &lt;code&gt;smalot/pdfparser&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Smalot\PdfParser\Parser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="nv"&gt;$parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
&lt;span class="nv"&gt;$pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$parser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;parseFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pdf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;getText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
&lt;span class="c1"&gt;// Store extracted text for search &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After extraction, the text needs to be stored somewhere searchable.&lt;/p&gt;

&lt;p&gt;For small use cases, post meta may work. But for better control and performance, a custom database table is usually better.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;wp_pdf_search_index&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; 
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;attachment_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;extracted_text&lt;/span&gt; &lt;span class="nb"&gt;LONGTEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;indexed_at&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;FULLTEXT&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;pdf_text_index&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extracted_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the plugin can search the extracted PDF content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;attachment_id&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_pdf_search_index&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extracted_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;AGAINST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'remote work requests'&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;NATURAL&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="k"&gt;MODE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the PDF itself is not searched directly every time.&lt;/p&gt;

&lt;p&gt;Instead, the plugin extracts the PDF text once, stores it, and searches the stored index.&lt;/p&gt;

&lt;h2&gt;
  
  
  Showing PDF Results in WordPress Search
&lt;/h2&gt;

&lt;p&gt;Once PDF content is indexed, the next step is showing matching PDF files in the search results.&lt;/p&gt;

&lt;p&gt;A plugin can hook into WordPress search using &lt;code&gt;pre_get_posts&lt;/code&gt;, or it can provide a separate PDF search form.&lt;/p&gt;

&lt;p&gt;For example, a shortcode-based PDF search form can be placed on a resource page:&lt;/p&gt;

&lt;p&gt;[webequipe_pdf_search_form]&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[webequipe_pdf_search_form placeholder="Search PDFs…" button_text="Search" results_per_page="10"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for documentation pages, school notice sections, report libraries, member portals, or product manual pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scanned PDF Problem
&lt;/h2&gt;

&lt;p&gt;Text extraction works only when the PDF contains real text.&lt;/p&gt;

&lt;p&gt;But many PDFs are scanned documents.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;p&gt;Old reports&lt;br&gt;
Signed forms&lt;br&gt;
Scanned notices&lt;br&gt;
Paper manuals&lt;br&gt;
Archive documents&lt;br&gt;
Government or legal documents&lt;/p&gt;

&lt;p&gt;A scanned PDF may look readable to a human, but technically each page is just an image.&lt;br&gt;
So when a parser runs this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pdf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;getText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may return:&lt;/p&gt;

&lt;p&gt;''&lt;/p&gt;

&lt;p&gt;There are visible words on the page, but no machine-readable text layer.&lt;/p&gt;

&lt;p&gt;This is where normal PDF parsing stops.&lt;/p&gt;

&lt;h2&gt;
  
  
  OCR Is Needed for Scanned PDFs
&lt;/h2&gt;

&lt;p&gt;For scanned PDFs, the solution is OCR.&lt;/p&gt;

&lt;p&gt;OCR means Optical Character Recognition. It reads text from images and converts it into machine-readable text.&lt;/p&gt;

&lt;p&gt;The flow looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvfr8t6xdfat91pkigt1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvfr8t6xdfat91pkigt1z.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;br&gt;
Google Vision API is one common OCR option. It can read pixel-based document images and return detected text.&lt;/p&gt;

&lt;p&gt;A simplified flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$image_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/tmp/page-1.png'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="nv"&gt;$ocr_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_google_vision_ocr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ocr_text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nf"&gt;index_pdf_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$ocr_text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea is simple:&lt;/p&gt;

&lt;p&gt;Text-based PDFs need parsing.&lt;br&gt;
Scanned PDFs need OCR first.&lt;/p&gt;

&lt;p&gt;If a search plugin treats both the same way, scanned PDFs will usually fail.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Simple Detection Strategy
&lt;/h2&gt;

&lt;p&gt;Most users do not know whether their PDF is text-based or scanned.&lt;/p&gt;

&lt;p&gt;So the plugin should decide automatically.&lt;br&gt;
A practical approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_pdf_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nf"&gt;index_pdf_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nf"&gt;queue_pdf_for_ocr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attachment_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, try normal extraction.&lt;/p&gt;

&lt;p&gt;If enough text is found, index it.&lt;/p&gt;

&lt;p&gt;If the result is empty or too short, send the file to OCR.&lt;/p&gt;

&lt;p&gt;For large PDFs or OCR processing, this should run in the background. Otherwise, uploads can become slow and PHP timeouts can happen.&lt;/p&gt;

&lt;p&gt;A better flow is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1slbi88et2zsyjlijjxo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1slbi88et2zsyjlijjxo.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;br&gt;
In WordPress, this can be handled with WP-Cron, Action Scheduler, or a custom queue system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;WordPress search ignores PDFs because PDF content is not stored in the database fields WordPress normally searches.&lt;/p&gt;

&lt;p&gt;To fix it, you need a separate indexing pipeline.&lt;/p&gt;

&lt;p&gt;For text-based PDFs, extract the text and store it in a searchable index.&lt;/p&gt;

&lt;p&gt;For scanned PDFs, run OCR first, then store the detected text.&lt;/p&gt;

&lt;p&gt;That is the technical foundation of PDF search in WordPress.&lt;/p&gt;

&lt;p&gt;We built this into &lt;a href="https://webequipe.com/pdf-search/" rel="noopener noreferrer"&gt;WebEquipe PDF Search&lt;/a&gt;. The free version helps index text-based PDFs, and the Pro version adds OCR support for scanned PDFs.&lt;/p&gt;

&lt;p&gt;But the bigger idea is simple:&lt;/p&gt;

&lt;p&gt;Uploading a PDF does not automatically make its content searchable.&lt;/p&gt;

&lt;p&gt;If your WordPress site depends on PDFs, those files need to be treated as part of your search index.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
