Category: WordPress

  • AI isn’t just agents: my talk at WordPress Day for AI 2026

    On October 24, 2026, Portugal hosted its first WordPress event dedicated entirely to AI. I was one of six featured speakers and had a 15-minute slot on the technical track to argue a quiet point: agents are not the only shape AI takes in WordPress — and vector embeddings deserve a lot more airtime than they get. Here’s the talk, the slides, the recording, and why I think this is where most WordPress site owners should start.

    The event

    WordPress Day for AI Faro 2026 ran on Saturday, October 24, 2026 at the AP Eva Senses hotel in Faro, Portugal. It was organised by Ruben Martins, Alvaro Gois, Marco Almeida, Fellyph Cintra and Jose Freitas — and, as The Repository reported, it was the first WordPress event in Portugal built entirely around artificial intelligence. Two parallel tracks, around a hundred attendees, one day.

    I was one of six featured speakers on the WPFolks lineup, alongside Sofia Correia, Lucas Catao de Moraes, Marcel Schmitz, Filipa Freitas and Jamie Marsland — a mix of WordPress agency perspective, developer tooling, and the broader “what does AI actually mean for publishing” conversation.

    My talk: AI isn’t just agents

    My session ran from 12:30 to 12:45 on the technical track. Fifteen minutes is tight, but it was enough to make one argument: the “AI in WordPress” conversation has become dominated by chatbots and agents, and that’s leaving a huge, boring, extremely useful piece of the stack out of the picture.

    Vector embeddings are the opposite of a chatbot. There’s no persona, no streaming reply, no “please wait while I think”. You take a piece of text, turn it into a 1,536-number vector that encodes its meaning, and from then on you can compare meanings mathematically. That’s it. From that one primitive, you unlock a lot:

    • Semantic search on your WordPress site. A visitor types “cheap trainers for the gym” and finds your “Nike Pegasus running shoes under 90€” page — not just posts that happen to contain the exact word trainers.
    • Content discovery that actually works. “Related posts” that are related by meaning, not just by category.
    • Smarter product recommendations. Suggestions that follow intent instead of SKU codes.
    • Multilingual retrieval for free. The same vector space understands “running shoes” and “sapatilhas de corrida” are the same thing.

    None of this needs an agent. None of it streams. It costs a fraction of a cent per piece of content indexed, and it runs sub-30 milliseconds once it’s wired up.

    Why I keep coming back to this

    Agents are impressive. They’re also hard to make reliable, expensive to run at scale, and overkill for most of the places WordPress site owners actually want “a bit of AI”. A vector-embeddings pipeline sits quietly in that 90%: a one-time index of your content, and from then on it just answers queries fast.

    I spent the last year building AI Search — the plugin I demoed on stage — to make this accessible to WordPress site owners without needing to touch an embedding API or a vector database. If you’re curious about the engineering behind it, I’ve written about it in depth at AI Search: WordPress Search with AI Embeddings.

    If you’re a WordPress developer or a shop owner reading this: this is where I’d point your attention first, before you build an agent.

    Resources

  • Do the slow work after the page is sent: WordPress’s shutdown hook

    Last month I added a search log to my WordPress plugin: every time the AI layer surfaced a product that plain keyword matching had missed, I wanted a row in a table saying so. Useful feature. Also a great way to make every single search on someone’s store slower, if you write it the obvious way.

    The fix is a WordPress hook that does not get talked about enough: shutdown.

    The problem with the obvious version

    You are already inside the search filter, you have the data, so you write the row there:

    It works. It is also bookkeeping that the shopper is paying for with their time. One INSERT is a millisecond or two, which sounds like nothing until you remember that this runs on every search, that the database might be on another host, and that your “one small write” has a way of becoming three writes and a lookup six months later.

    The visitor needs the search results. They do not need your analytics.

    What the shutdown hook actually is

    shutdown is the last action WordPress fires. Core registers it as a PHP shutdown function, so it runs at the very end of the request – after the template has rendered, after the response has been handed off. It fires on normal page loads, on AJAX, on REST requests, and even when something calls wp_die().

    That makes it the natural home for work that must happen during a request but that nobody is waiting for.

    Why it feels instant: fastcgi_finish_request()

    On PHP-FPM – which is what most decent hosting runs – PHP can tell the web server “I am done, send it” and keep executing. WordPress core and various plugins call fastcgi_finish_request() at the end of a request, so by the time your shutdown callback runs, the browser has already received the page and started rendering it.

    Be honest with yourself about what that means, though. The visitor is not waiting, but the PHP worker still is. You have moved the cost off the perceived page load and onto your pool of workers. That is a very good trade for a single INSERT. It is a terrible trade for something that takes two seconds, because under traffic you will run out of workers and the queue will bite you in a much less obvious way.

    On old mod_php or CGI setups the connection may not be closed early at all, so the technique degrades to “same total time, tidier code”. Nothing breaks – it just stops being a performance win.

    The pattern, in three moves

    This is the actual code from AI Search 1.31.0, trimmed for reading. First, register the hook where you register the others:

    Second, during the request you only collect. No queries, no writes – just put what you know on the object and carry on:

    Third, the callback that actually writes. Note the early return and the null: this thing must be safe to call when there is nothing to do, and safe to call twice.

    That is the whole trick. The search path does zero database work for logging; the row still lands on every relevant request; and the shopper never waits for it.

    When you should not reach for it

    • Anything the response depends on. Obvious when written down, easy to forget at 11pm. If the page needs the result, it cannot happen after the page.
    • Anything slow. Calling an external API, resizing images, sending mail through a slow SMTP server – all of these hold a worker hostage. That is what WP-Cron, or a real queue, is for.
    • Anything that needs to output. Headers are long gone. No redirects, no cookies, no printing. If you echo something here you will either see it glued to the end of your page or nowhere at all.
    • Anything that must not be lost. A fatal error, a killed worker or an aggressive timeout can end the request before your callback runs. For a search log that is fine. For an order, it is not.

    Small gotchas worth knowing

    $wpdb is still perfectly alive at this point, so queries work normally. Object cache writes, on the other hand, are mostly pointless: nothing else in this request will read them. And if you use a persistent object cache, remember that other requests may already be serving cached output that does not know about your write – so do not use shutdown to fix up something the current page displayed incorrectly.

    Also, keep the callback idempotent. In unusual flows (some AJAX handlers, some error paths) you can end up with the shutdown sequence running more than once. Nulling the pending payload after reading it, as above, is the cheapest possible insurance.

    The result in practice

    In AI Search this powers the Insights tab: store owners can see which searches the AI actually rescued – the queries where semantic matching surfaced products that keyword matching had missed – without the logging costing their shoppers anything. Same feature, same data, moved a few milliseconds to the right in the request lifecycle.

    It is a small technique. But “does this actually need to block the response?” is a question worth asking about half the code in a typical plugin, and shutdown is the tool for the times when the answer is no.

  • Your Search Bar Is Your Back Door: And Here’s How to Lock It

    Last Saturday I spoke at WordCamp Europe 2026 in Kraków about a topic that’s been on my mind for a while: the WordPress search endpoint as a DDoS attack vector.

    The talk was called “The Hidden DDoS Threat in WordPress: Abusing the Search Endpoint“ and the response was incredible (thank you very much!!). However, some feedback came after the talk, and it pushed me to build something better.

    You can see the session here: https://europe.wordcamp.org/2026/session/the-hidden-ddos-threat-in-wordpress-abusing-the-search-endpoint/

    What the talk covered

    The core idea is simple. Every WordPress site has a search endpoint (/?s=). Unlike your regular pages, search queries bypass CDN and cache entirely. Every single search request hits your database directly with a LIKE ‘%query%’ full table scan.

    An attacker doesn’t need a botnet. Just flood /?s= with random strings and your database is on its knees.

    During the talk, I showed a live benchmark comparing DDoS attacks on cached pages versus the search endpoint. Same number of requests. Completely different results. The cached pages didn’t even make the server sweat. The search endpoint brought it down.

    Photo by Claudio Schwarz

    The solution approach and the feedback

    In the talk, I proposed using wp_create_nonce() as a first line of defense. The idea was straightforward: generate a nonce on page load, require it for search requests, and block anything without it. Bots don’t load pages, so they never get the nonce.

    The point I was making is that WordPress has had tools to mitigate this since 2006. We just don’t use them, however some people raised a valid point:

    I can JUST USE the nonce and use it in my DDoS LIKE IN THE BROWSER!
    random hacker saying correct things

    They’re right. And I appreciate that feedback. The nonce approach raises the bar and stops the simplest and automated attacks. But it’s not bulletproof. A determined attacker can work around it. So let me prepare something and bring a solution that will meet the expectations. I was so frustrated with myself that I basically started to think about a solution in Krakow yet.

    Introducing WP Search Shield

    WP Search Shield is a WordPress plugin that protects your search endpoint with two layers of defense that work together.

    Layer 1: Single-use tokens

    Instead of a WordPress nonce that lasts hours, WP Search Shield generates a unique token on every page load. This token can only be used once. The moment it’s used for a search request, it’s deleted from the database and a new one is issued.

    This means:

    • An attacker extracts the token from your HTML? It works once. That’s it.
    • They want to search again? They need to load the full page again to get a new token.
    • At that point, they’re just a regular user. DDoS over.

    Layer 2: IP rate limiting

    Even if someone finds a way to rotate tokens quickly, they’re still limited to a configurable number of searches per time window. Default is 10 searches per 60 seconds per IP.

    After that? 429 Too Many Requests.

    How they work together

    AttackWithout pluginWith plugin
    Bot hits /?s=random directlyHits database403 — No token
    Bot extracts token, sends 1000 requestsAll hit databaseFirst works, rest get 403
    Bot refreshes token each timeAll hit databaseRate limited after 10
    Real user searches normallyWorksWorks seamlessly

    The plugin handles everything automatically. It injects tokens into search forms, patches fetch() and XMLHttpRequest for REST API calls, and rotates tokens after each search. Zero configuration needed.

    It works with your existing stack

    WP Search Shield is built on WordPress transients. If you’re running Redis or Memcached (which you should be), token validation and rate limiting happen in memory. No extra database queries. No external services.

    It protects both traditional search (/?s=) and REST API search (/wp-json/wp/v2/search).

    Try it yourself

    The plugin is open source and available on GitHub:

    check it here: github.com/samuelsilvapt/wp-search-shield

    Install it, activate it, done. It works out of the box with sensible defaults.

    If you want to customize it, everything is configurable via filters:

    You can also hook into blocked requests for monitoring:

    The bigger picture

    This plugin isn’t a replacement for a CDN or a WAF. It’s a targeted fix for a specific problem that those tools can’t solve.

    Your CDN caches static content beautifully. Your object cache speeds up repeated queries. But search queries are unique by nature. They can’t be cached. And that’s exactly what attackers exploit.

    WP Search Shield closes that gap.

    chilling in a random cafe in kraków while working on the plugin

    Thank you, WCEU

    To everyone who came to the talk, asked questions, and challenged the nonce approach: thank you. You made this better. But I want to say something beyond the technical stuff.

    I reconnected with old friends I hadn’t seen in years. I made new ones over coffee, over beers, over late-night conversations about everything but WordPress. I created memories that I know I’ll carry for a long time.

    There’s something special about this community. It’s not just about work, code or plugins or themes. It’s about people who genuinely care about each other and push each other to grow. I felt that in every conversation, every handshake, every “great talk” from someone in the hallway.

    And kraków, you were unforgettable.

  • Everyday Efficiency: PHP 8 Techniques for WordPress Engineers

    As a software engineer navigating the daily challenges of WordPress development, embracing the latest tools and tricks is essential to staying efficient and effective. In this post, I want to share three PHP 8 tricks I incorporate into my everyday tasks as a software engineer working on WordPress projects.

    When PHP 8.0 was released, I wrote about my favourite improvements here, and you’ll see some of them in this post – however, this time those techniques are part of my day as an engineer.

    These tricks aren’t just about staying up-to-date; they’re practical enhancements that make my coding life easier and my WordPress development more streamlined. Let’s dive in!

    Easier Null Handling with “?->” Operator


    In PHP 8, there’s a cool trick called the Nullsafe Operator (?->). It helps when you’re dealing with nested objects and saves you from writing lots of null-checks. Check out this example:

    // classic one
    if ($user !== null && $user->getProfile() !== null) {
       $avatar = $user->getProfile()->getAvatar();
    } else {
       $avatar = 'default.jpg';
    }
    // With PHP 8 Nullsafe Operator
    $avatar = $user?->getProfile()?->getAvatar() ?? 'default.jpg';

    This makes your code shorter and easier to read, especially when working with WordPress objects.

    Cleaner Conditionals with “match” Instead of “switch”


    PHP 8 has a new way to handle conditions using the match expression. It’s like the old switch statement but more concise. Look at this:

    
    // Traditional switch
    switch ($status) {
    case 'published':
       $message = 'Post is live!';
       break;
    case 'draft':
       $message = 'Post is still in draft.';
       break;
    default:
       $message = 'Post status unknown.';
    }
    // PHP 8 match expression
    $message = match( $status ) {
       'published' => 'Post is live!',
       'draft'     => 'Post is still in draft.',
       'default'   => 'Post status unknown.',
    };

    Personally, I love this one! When you avoid overusing If/else statements in your code, matching labels with indexes is always a great solution – even better when you can match the index with the actual status.

    Constructor Property Promotion for Cleaner Class Definitions:

    PHP 8 introduces a streamlined way to define classes with the Constructor Property Promotion feature. This feature is a game-changer for WordPress developers, allowing for more concise and readable class definitions. It’s particularly useful when creating custom components like widgets or API responses. Instead of declaring class properties and then assigning them in the constructor, PHP 8 allows you to do both simultaneously, thereby reducing boilerplate code.

    For example, consider a custom widget in a WordPress theme:

    // PHP 7 style
    class CustomWidget {
        private $title;
        private $content;
    
        public function __construct($title, $content) {
            $this->title = $title;
            $this->content = $content;
        }
    }
    
    // PHP 8 Constructor Property Promotion
    class CustomWidget {
        public function __construct(
            private $title, 
            private $content
        ) {}
    }

    This approach makes the code significantly more concise and enhances readability, which is essential in complex WordPress projects.

    Named Arguments for More Readable Function Calls

    PHP 8’s Named Arguments feature significantly enhances the readability of function calls, especially in WordPress where functions often come with multiple parameters. This feature allows developers to pass arguments to a function based on their names, rather than their position, making the code more understandable and less prone to errors related to the ordering of arguments.

    Again, considering a WordPress function call scenario:

    // PHP 7 positional arguments
    add_theme_support('custom-background', array(
        'default-color' => '000000',
        'default-image' => '',
    ));
    
    // PHP 8 named arguments
    add_theme_support('custom-background', default-color: '000000', default-image: '');
    
    // Another example with WP_Query
    $query = new WP_Query(array(
        'post_type' => 'post',
        'posts_per_page' => 10,
        'orderby' => 'title',
        'order' => 'ASC'
    ));
    
    // With PHP 8 named arguments
    $query = new WP_Query(
        post_type: 'post',
        posts_per_page: 10,
        orderby: 'title',
        order: 'ASC'
    );

    Using named arguments not only clarifies the purpose of each parameter but also makes the code more flexible for future changes, an invaluable asset in the evolving landscape of WordPress development.

    More Control with Union Types

    PHP 8 introduces something called union types. It’s a way to say a variable can be one of several types. This is handy when working with functions that can take different kinds of data:


    // Without union types
    function processUserData($data) {
    // Function logic here
    }
    // With union types
    function processUserData( array|string $data ) {
    // Function logic here
    }


    By using union types, you can be clearer about what kind of data your functions expect, making your WordPress code more robust.

    Is WordPress core already prepared for PHP 8?

    It’s very important that you run your local PHP environment as similar as possible to your project’s final server, because although WP core is compatible with PHP, you’ll see that officially the stable version continues to be 7.4. But my advice: run at least version 8.1 🙂

    Conclusion

    These PHP 8 tricks might seem small, but they can make a big difference in your WordPress development. The Nullsafe Operator, Match Expression, and Union Types are easy to understand and implement. By using these tricks, you’ll not only stay current but also write code that’s simpler, cleaner, and more effective in your WordPress projects.

    All these details will reveal that you’re proud of your work. In a world where >40% of the Internet potentially can run your code, keep in mind you have the mission to create a web that is being designed to last.

  • PHP 8.0 – 4 new features in this latest version

    PHP 8.0 – 4 new features in this latest version

    New PHP major versions are always very expected by PHP developers, we already know good things coming when a new release is announced. This time we have a lot of performance improvements and new features that improve our code, using modern programming methods in a badass old-school language.

    Besides those new features and recommendations, I strongly recommend we use them only for environments that we can expect and have sure that 8.0 is running. In some cases, we can use polyfills instead. If you’re a plugin developer, it’s too soon to start using the new features of this version.

    Nullsafe operator

    I really love clean and efficient code, so I’m not a fan of the classic if/else statements (why we shouldn’t use them? Read more here). However, there is always a time when we need to use a simple if/then, and the best way to do that is using an inline if statement Like this:

     $today_exists = time() ? true : false;

    Clean, useful and works like a charm. I can’t know how many times I’ve tried to check a null value of a class method (for example), giving a “then” statement in the same line. Simple – it wasn’t possible, and the solution was based into split the statement into two or more lines.

    The good news is called Nullsafe operator. PHP 8 now provides a null validation without any errors, so the following example now is pretty possible:

    $today_exists = $this->get_time()?->$this->is_true();

    str_contains() function

    With this new function, we no longer need to use the strpos() function. This last one is more recognized by developers to be used to verify if a string contains a word, like this:

    $today_exists = ( false !== strpos( 'Today is the day', 'Today' ) );

    Yes, that’s what we had to do to check if a string contains X word 🙂

    Checking the documentation, it’s really strange to see str_contains() only is available from 8.0. In this link, you can also find a pollyfill alternative.

    New match() expression

    That’s a good one for clean code. Imagine we have a switch case expression, but optimized to be used without breaks, and organized as a class sintax. I’m particullary interested into starting to use this new approach, check some examples in the documentation.

    $food = 'cake';
    
    $return_value = match ($food) {
        'apple' => 'This food is an apple',
        'bar' => 'This food is a bar',
        'cake' => 'This food is a cake',
    };
    
    var_dump($return_value);

    Enums

    Exactly like other languages (e.g. javascript), enums provides us to call a function and have a definite number of cases on the args. The result is a more stable code. This one will be available in the 8.1 release only, but it’s a great example of what it’s happening with PHP – giving to the developers great native features we can use like a modern programming language.

    I’m looking forward to use these features on my code, and I’m confident that will be possible soon – PHP 8.0 has been launched in November 2020, so please read more about WordPress and 8.0 compatibility before starting to code.

  • MySQL and WordPress – How to use native queries in a secure and right way

    WordPress requires a MySQL instance to run and store all the data. Although the relational system is a classic and, sometimes, obsolete data structure, MySQL works like a charm on a WordPress environment.

    Keeping this on the WordPress core, gave us better compatibility with our custom development over time and quick and easy access to the data. However, rare times developers access the data using MySQL native queries – normally, the WP methods are enough for a straightforward development (New WP_Query, get_posts, etc…). But if you need deeper access to the data, SQL Queries need to enter on your WP package-skills. High-level SQL knowledge isn’t needed, but knowing some methods will be great for your future developments.

    Consider you already have some skills using SQL statements, I would like to show you the best secure way to work with it using WordPress Best Practices. Please follow me:

    WordPress Database Class

    WordPress core already has a class to interact with the database, it’s a global object: $wpdb. Don’t ignore it, this class is the safer and better way to access the database directly. If you need to extend the class for your specific needs, you can replace it in wp-content/db.php. Anyway, I strongly suggest only use this alternative if the existing methods don’t be enough.

    Sanitize and Escape everything

    2021 and SQL injection still exists. Using $wpdb, we just need to use the “prepare” method to prevent possible SQL attacks.

    $sql = $wpdb->prepare( 'SELECT * from %s' , $table_name );

    Pretty simple and supports placeholders! After sanitizing and escaping our SQL query, we’re able to run the query.

    Query Methods

    I recommend you three main methods to run queries, depending on what you need to do with the data: consult, write, delete…

    1. get_results()
      One of my favourites, this function is used for getting an array with query results. Perfect for “SELECT” statements.
    2. get_var()
      Same as get_results(), but better for receiving a single value – a custom meta, for example. The returned value is a string, instead of an array.
    3. query()
      Use this when you need to write on the database. The function will return a true/false.

    A Complete Example

    Imagine you want to list all the WP users on an array. The best way to do that is:

    // first, call the object to be accessible
    global $wpdb;
    
    // don't forget to use the ->prefix for a dynamic table name
    $users_table = $wpdb->prefix . 'users';
    
    // your safe query
    $query      = $wpdb->prepare( 'SELECT * FROM %s', $users_table );
    $users_list = $wpdb->get_results( $query );
    

    That’s it. I recommend you read more about the $wpdb object and be prepared to write safer and more performative SQL queries on WP. Use the comments box for additional questions or suggestions!

  • WooCommerce – Save the Products’ Stock Status for each Order

    Stock Management is a default feature on all Online Store CMS, it’s useful when you manage your stock using WooCommerce, preventing out-of-stock Purchases and of course, preventing your clients’ complaints.

    Sometimes, beyond this obvious feature, we need to know some more about the stock status. For example: occasionally, for business rules’ reasons, it could be useful if we could know the stock status of the product at the date of a specific order.

    (more…)
  • Cancel WooCommerce not-paid Orders automatically

    One way to keep your online store clean and organized is by using the order status. Complete, Cancelled, Awaiting Payment are statuses which helps you to pay attention to the most important orders.

    But when you have a lot of “Awaiting payment” orders and you don’t know until when you need to consider that order? Well, I’m writing today because of that. A client of mine gave me this problem: I want to cancel my pending orders (awaiting payment) after 4 days.

    Before the solution we discuss about:

    • Should we need to consider all the payment methods?
    • The client should receive any type of email confirmation?
    • Which order status we need to consider?

    But after that, I started to code. Follow me, step by step:

    (more…)
  • Are you a Blogger? Show your old posts in your WordPress blog

    “One year ago you posted that …”, “See what you said on this day, in 2012 …”. You already saw something like that on your social media accounts, didn’t you? Remember your own content on the Internet is a really trendy Throwback-Thursday Post.

    What interesting could be, if we could highlight our old posts, like “One year ago I wrote that ..”, “Three years ago I went in Tibet – read what I wrote about it”.

    (more…)
  • Security and Health Status – New feature on WordPress 5.2

    There are a mount of posts around the Internet saying “Top 10 things” that you need to do on your WordPress website. But this time is different, new WordPress Core released (5.2) gave to us a new important feature.

    (more…)
  • How to add a Full-Screen Menu to your WordPress website

    If you have a personal/commercial website and if you want to improve that website with an additional menu without replacing the current menu, just keep reading.

    (more…)
  • Are you thinking of selling WordPress Plugins / Themes at Envato Market?

    Envato is an attractive marketplace for plugin/theme developers who want to start their careers selling WordPress material. Envato is a nice solution – it gives visibility, marketing and there are a lot of people searching things there every day.

    On this post, I will talk a little about my first experience at Evanto.

    (more…)