<?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: Eduardo Villão</title>
    <description>The latest articles on DEV Community by Eduardo Villão (@edu_villao).</description>
    <link>https://dev.to/edu_villao</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3900619%2F6107a4b2-168c-4794-a838-97bb3689d596.jpg</url>
      <title>DEV Community: Eduardo Villão</title>
      <link>https://dev.to/edu_villao</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/edu_villao"/>
    <language>en</language>
    <item>
      <title>AI Without Standards Is Just Faster Chaos</title>
      <dc:creator>Eduardo Villão</dc:creator>
      <pubDate>Mon, 27 Apr 2026 14:46:28 +0000</pubDate>
      <link>https://dev.to/edu_villao/ai-without-standards-is-just-faster-chaos-2nie</link>
      <guid>https://dev.to/edu_villao/ai-without-standards-is-just-faster-chaos-2nie</guid>
      <description>&lt;p&gt;Every engineering team I talk to is using AI. Nobody's debating that anymore.&lt;/p&gt;

&lt;p&gt;What nobody's talking about is that every developer on the same team is using it differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Nobody Admits
&lt;/h2&gt;

&lt;p&gt;Ten developers. Ten different setups. Ten different rules files. Ten different prompting habits. Same codebase, same tickets, same sprint - ten different approaches to how AI touches the code.&lt;/p&gt;

&lt;p&gt;One dev has a meticulous CLAUDE.md with architecture decisions, testing conventions, and code style rules. The dev next to them has nothing - just vibes and a blank context window. Both ship code. Both pass review. The inconsistency is invisible until it isn't.&lt;/p&gt;

&lt;p&gt;AI didn't create this problem. It amplified what was already broken: the lack of shared engineering standards that actually get enforced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Things That Break
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No visibility.&lt;/strong&gt; You don't know how your team uses AI. Who follows best practices. Who doesn't. What gets reviewed. What ships blind. Engineering managers have dashboards for deployment frequency, test coverage, PR cycle time - but zero visibility into how AI is shaping the code their team writes every day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No consistency.&lt;/strong&gt; Same ticket, ten different approaches. One dev asks the AI to write tests first. Another skips tests entirely and asks for the implementation. A third gives the AI the full architectural context. A fourth gives it nothing. The output varies wildly - not because the AI is inconsistent, but because the humans are.&lt;/p&gt;

&lt;p&gt;And this isn't just about different code styles. It's about where in the codebase the change happens, what the scope should be, why this approach and not another. There are a thousand ways to solve the same problem. The AI will confidently execute any of them. Without a shared standard for how your team makes these decisions, every AI-generated PR becomes a review burden. Someone has to undo, redo, or reshape work that was technically correct but architecturally wrong. That's rework at scale, and it compounds fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowledge walks out.&lt;/strong&gt; When a developer leaves, their context, patterns, and shortcuts leave with them. The rules they built, the prompts they refined, the architectural decisions they encoded in their local setup - gone. The next hire starts from zero. This was already a problem before AI. Now it's worse, because the amount of implicit knowledge in a developer's AI setup is massive and completely undocumented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Harder Than It Looks
&lt;/h2&gt;

&lt;p&gt;The obvious reaction is "just create a shared rules file." That's where most teams start. It's also where most teams stop.&lt;/p&gt;

&lt;p&gt;A shared rules file in a repo solves the format problem but not the enforcement problem. Nobody checks if developers actually use it. Nobody knows if it's up to date. Nobody knows if the dev who joined last month even knows it exists.&lt;/p&gt;

&lt;p&gt;And rules are just the beginning. What about which models are approved? What about which phases of work should use AI and which shouldn't? What about cost? What about the architectural decisions that should constrain how AI generates code in your specific codebase?&lt;/p&gt;

&lt;p&gt;Standardizing AI in an engineering team isn't a file. It's a system. And most teams don't have one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Needs to Happen
&lt;/h2&gt;

&lt;p&gt;I think about this in three layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: Shared context.&lt;/strong&gt; Every developer working on the same codebase should start with the same foundational context. Architecture decisions, code conventions, testing strategy, dependency rules - this isn't optional, and it shouldn't depend on individual setup. If your ADRs exist but only two people know where they are, they don't exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: Guardrails.&lt;/strong&gt; Not everything should be delegated to AI. Some decisions require human judgment. Some code paths are too critical for unsupervised generation. The team needs to define where AI adds value and where it adds risk - and enforce that distinction, not just document it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: Visibility.&lt;/strong&gt; You need to know what's happening. Not surveillance - signal. Which parts of the codebase are AI-touched. What patterns are emerging. Where the inconsistencies are. Without this, you're managing a process you can't see.&lt;/p&gt;

&lt;p&gt;Most teams have none of these layers. Some have a partial Layer 1. Almost nobody has Layer 2 or 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Competitive Advantage
&lt;/h2&gt;

&lt;p&gt;Here's what I think most people miss: the competitive advantage of AI in engineering isn't speed. Speed is table stakes. Everyone gets faster.&lt;/p&gt;

&lt;p&gt;The advantage is in how consistently and reliably you use it across the entire team. The team that ships fast with shared standards will outperform the team that ships fast with ten different approaches - because the second team is accumulating invisible inconsistency that compounds over time.&lt;/p&gt;

&lt;p&gt;Faster chaos is still chaos. It just takes longer to notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Isn't About Tools
&lt;/h2&gt;

&lt;p&gt;I'm not pitching a solution here. I'm describing a problem I see everywhere and that I think is underexplored.&lt;/p&gt;

&lt;p&gt;The industry spent the last two years talking about adopting AI. The next conversation needs to be about governing it. Not in a bureaucratic, compliance-heavy way - in a practical, engineering-first way. Shared context, clear guardrails, basic visibility.&lt;/p&gt;

&lt;p&gt;Every team that adopted AI without standardizing it is running an experiment with no controls. Some of those experiments will work out fine. Some won't. The ones that don't will be very expensive to fix - because the technical debt from inconsistent AI usage is invisible until it's systemic.&lt;/p&gt;

&lt;p&gt;The question isn't whether your team should use AI. It's whether they should all use it the same way.&lt;/p&gt;

&lt;p&gt;I think the answer is obvious.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Self-Host WordPress Plugins on GitHub and Deliver Updates</title>
      <dc:creator>Eduardo Villão</dc:creator>
      <pubDate>Mon, 27 Apr 2026 14:21:37 +0000</pubDate>
      <link>https://dev.to/edu_villao/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates-4745</link>
      <guid>https://dev.to/edu_villao/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates-4745</guid>
      <description>&lt;p&gt;Managing plugin updates can be a challenge, especially if you're not relying on the WordPress Plugin Repository. But what if you could self-host your plugins on GitHub and deliver updates seamlessly, without the need for complex libraries or third-party services?&lt;/p&gt;

&lt;p&gt;In this guide, I'll walk you through a simple yet powerful solution that adheres to WordPress's standard for updates, making the process completely transparent for your users. To make this possible, I've developed a custom GitHub Action and a PHP script that work together to handle updates effortlessly. This is the same solution I use for some of my own plugins, and now I'm sharing it with the community so you can benefit from it too.&lt;/p&gt;

&lt;p&gt;No extra dependencies, no fuss — just GitHub and a bit of PHP magic. Let's get started! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Self-Hosting Plugins?
&lt;/h2&gt;

&lt;p&gt;Before diving in, let's take a step back: self-hosting plugins means managing your plugin's storage and updates independently, outside the WordPress.org repository. You take control of where your WordPress plugins are stored and how updates are delivered, without relying on the official WordPress Plugin Repository. Instead of hosting your plugin on WordPress.org, you use your own infrastructure — such as GitHub, a private server, or any other file host — to manage your plugin's lifecycle.&lt;/p&gt;

&lt;p&gt;In essence, self-hosting empowers developers to build, distribute, and update plugins on their own terms, while still providing users with a seamless and familiar update experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Required?
&lt;/h2&gt;

&lt;p&gt;At a high level, you'll need two things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A server (in our case GitHub):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host metadata about your plugin, including the latest version, download URL, and other required information.&lt;/li&gt;
&lt;li&gt;Store the &lt;code&gt;.zip&lt;/code&gt; distribution file, which will be downloaded during the update process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. An Update Checker Script in Your Plugin:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch data from the server (GitHub) to retrieve the latest plugin details.&lt;/li&gt;
&lt;li&gt;Compare versions to check whether the installed version is outdated.&lt;/li&gt;
&lt;li&gt;Forward updates seamlessly into WordPress's default update system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these two components working together, you can deliver a smooth and automated update experience for your plugins, all while retaining full control over the distribution process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Server Side on GitHub
&lt;/h2&gt;

&lt;p&gt;The GitHub Action automates the creation of everything needed for the plugin update process. Here's the high-level flow:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Generate JSON Metadata
&lt;/h3&gt;

&lt;p&gt;The action parses your &lt;code&gt;readme.txt&lt;/code&gt; and other relevant files to create a JSON file containing metadata about your plugin — version, download URL, description, and more. This metadata is essential for the PHP script on the plugin side to query the server and verify if the plugin is up to date.&lt;/p&gt;

&lt;p&gt;The download URL is automatically generated to point directly to the &lt;code&gt;.zip&lt;/code&gt; file in the release created by the action, ensuring WordPress fetches updates directly from GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Prepare the Distribution Package
&lt;/h3&gt;

&lt;p&gt;The action compiles your plugin files into a &lt;code&gt;.zip&lt;/code&gt; package, ready for distribution. During this process, specific rules can be defined to exclude unnecessary folders or files — such as development directories, test cases, or build artifacts. This ensures a clean and optimized distribution file.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Release Creation
&lt;/h3&gt;

&lt;p&gt;Once the metadata and distribution package are prepared, the action automates the creation of a new GitHub Release. The release is tagged with the corresponding plugin version, ensuring WordPress and the PHP script can correctly fetch the latest version.&lt;/p&gt;

&lt;p&gt;This streamlined process ensures that every time you create a new tag/version of your plugin, all required files and metadata are automatically prepared and hosted, ready to deliver updates to your users.&lt;/p&gt;

&lt;p&gt;👉 Check the full implementation: &lt;a href="https://github.com/eduardovillao/wp-self-host-updater-generator" rel="noopener noreferrer"&gt;wp-self-host-updater-generator&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plugin Side — Update Checker
&lt;/h2&gt;

&lt;p&gt;The PHP script serves as the "bridge" between the plugin installed on the WordPress site and the server-side metadata hosted on GitHub. Here's how it operates:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Add a Simple PHP File to Manage Updates
&lt;/h3&gt;

&lt;p&gt;The update checker script is integrated directly into your plugin. It hooks into WordPress's native update events via filters like &lt;code&gt;plugins_api&lt;/code&gt; and &lt;code&gt;site_transient_update_plugins&lt;/code&gt; to manage and provide update information dynamically.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Request JSON Data from GitHub
&lt;/h3&gt;

&lt;p&gt;The script queries the GitHub-hosted JSON metadata file for the latest information about your plugin — current version, description, download URL, and other details. This ensures the site always has access to accurate, up-to-date plugin information.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Compare Current Version vs. Latest Version
&lt;/h3&gt;

&lt;p&gt;Once the JSON data is retrieved, the script compares the currently installed version with the version available in the metadata. If the versions match, no action is taken. If a newer version exists, the script forwards the update information to WordPress's built-in update system.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Follow the Default WordPress Flow to Upgrade
&lt;/h3&gt;

&lt;p&gt;If a new version is available, WordPress takes over using its default upgrade mechanism. This ensures a seamless and familiar experience for end users, who can update the plugin just like they would with any other WordPress plugin.&lt;/p&gt;

&lt;p&gt;👉 Check the full implementation: &lt;a href="https://github.com/eduardovillao/wp-self-host-updater-checker" rel="noopener noreferrer"&gt;wp-self-host-updater-checker&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;And that's it! 🎉&lt;/p&gt;

&lt;p&gt;If you have any questions, need help with implementation, or have tested this solution and want to share feedback, feel free to drop a comment below. I'd love to hear from you!&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Will this work with mu-plugins?&lt;/strong&gt;&lt;br&gt;
Partially! The server side on GitHub is fully capable of managing MU-Plugins. However, since MU-Plugins don't follow the same update flow as regular WordPress plugins, the PHP script will require some adjustments. Coming soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Will this work with themes?&lt;/strong&gt;&lt;br&gt;
Not yet. Some modifications are needed to support themes. For now, this solution works exclusively with plugins — theme support is in progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Can I validate the user license before delivering updates?&lt;/strong&gt;&lt;br&gt;
This flow is designed for "free" plugins, so license validation isn't currently supported. I'm exploring ways to incorporate this feature and will share updates soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Will this work with private repositories?&lt;/strong&gt;&lt;br&gt;
Almost! The action works as-is, but the PHP script needs changes because a token is required to authenticate requests for the JSON data from private repos. Details coming soon.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>github</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Handle Elementor Popup Events Without jQuery</title>
      <dc:creator>Eduardo Villão</dc:creator>
      <pubDate>Mon, 27 Apr 2026 14:20:15 +0000</pubDate>
      <link>https://dev.to/edu_villao/handle-elementor-popup-events-without-jquery-5736</link>
      <guid>https://dev.to/edu_villao/handle-elementor-popup-events-without-jquery-5736</guid>
      <description>&lt;p&gt;If you've ever worked with Elementor and tried to manipulate its popups programmatically, you've probably noticed that the official documentation provides event handling examples only with jQuery. However, if you prefer a modern and lightweight solution using Vanilla JavaScript, this guide is for you.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;MutationObserver&lt;/code&gt; API, you can monitor changes to the DOM and detect when an Elementor popup modal is injected into the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. This allows you to execute custom actions without relying on additional libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use MutationObserver?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MutationObserver&lt;/code&gt; API is a native JavaScript feature that lets you observe DOM changes, such as adding or removing elements, and modifications to attributes of existing elements.&lt;/p&gt;

&lt;p&gt;In the case of Elementor popups, it's perfect for detecting when the popup modal is dynamically added to the DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code to Detect Elementor Popups
&lt;/h2&gt;

&lt;p&gt;Here's a complete code snippet that you can use in your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Select the &amp;lt;body&amp;gt; element&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Create a MutationObserver&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&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;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Check if new nodes were added&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;childList&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addedNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Check if the added node is an Elementor popup modal&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;elementor-popup-modal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Elementor popup detected:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="c1"&gt;// Add your custom logic here&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="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Configure the observer to monitor the &amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;childList&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="c1"&gt;// Stop observing when no longer needed (optional)&lt;/span&gt;
&lt;span class="c1"&gt;// observer.disconnect();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Monitoring &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;:&lt;/strong&gt; The &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element is observed because Elementor injects its popups as child nodes of &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MutationObserver:&lt;/strong&gt; We use the &lt;code&gt;MutationObserver&lt;/code&gt; to watch for changes in the child nodes of &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; by setting &lt;code&gt;{ childList: true }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filtering Added Nodes:&lt;/strong&gt; For each added node, we check if it has the class &lt;code&gt;elementor-popup-modal&lt;/code&gt;, which identifies Elementor's popups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom Actions:&lt;/strong&gt; When the modal is detected, you can execute any logic in the block where the &lt;code&gt;console.log&lt;/code&gt; statement is located.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Use Case
&lt;/h2&gt;

&lt;p&gt;Suppose you want to apply a custom animation when an Elementor popup appears. You can modify the code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;elementor-popup-modal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Elementor popup detected:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Start invisible&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;opacity 0.5s&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&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="c1"&gt;// Fade-in effect&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&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;h2&gt;
  
  
  Why Choose Vanilla JS?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No Dependencies:&lt;/strong&gt; Reduces the overall project size by eliminating libraries like jQuery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improved Performance:&lt;/strong&gt; Vanilla JavaScript is generally faster since it doesn't have the overhead of handling selectors and events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modern Compatibility:&lt;/strong&gt; &lt;code&gt;MutationObserver&lt;/code&gt; is supported in all modern browsers, including Edge and Safari.&lt;/p&gt;




&lt;p&gt;Using &lt;code&gt;MutationObserver&lt;/code&gt; to detect Elementor popups is an elegant and efficient solution, especially for developers who prefer not to rely on jQuery. With this code, you can fully customize how you interact with Elementor popups, whether it's adding animations, tracking events, or implementing other custom logic.&lt;/p&gt;

&lt;p&gt;If you found this tip helpful, share it with other developers and leave your ideas for popup customization in the comments!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>wordpress</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>In the AI Era, Soft Skills Are the Hard Skills</title>
      <dc:creator>Eduardo Villão</dc:creator>
      <pubDate>Mon, 27 Apr 2026 14:15:00 +0000</pubDate>
      <link>https://dev.to/edu_villao/in-the-ai-era-soft-skills-are-the-hard-skills-3ilh</link>
      <guid>https://dev.to/edu_villao/in-the-ai-era-soft-skills-are-the-hard-skills-3ilh</guid>
      <description>&lt;p&gt;The more powerful AI gets, the more human the bottleneck becomes. You open a new chat, type a vague prompt, get a mediocre response, and blame the model. The model isn't the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Changed
&lt;/h2&gt;

&lt;p&gt;For the past decade, the most valuable thing a developer could do was execute. Write the code, ship the feature, fix the bug. Speed and technical precision were the differentiators.&lt;/p&gt;

&lt;p&gt;AI didn't just speed that up. It changed who does it.&lt;/p&gt;

&lt;p&gt;A well-prompted AI agent can write a working feature in minutes. It can refactor a module, generate tests, handle edge cases, and document the result. All before you've finished your second coffee. The execution layer is no longer the bottleneck.&lt;/p&gt;

&lt;p&gt;What's left? The part AI can't do on its own: figuring out what to build, why it matters, and how to frame it clearly enough that something else can execute it well.&lt;/p&gt;

&lt;p&gt;The bottleneck shifted from execution to clarity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Clarity Actually Means
&lt;/h2&gt;

&lt;p&gt;Clarity isn't just "communicate better." That's too vague to be useful.&lt;/p&gt;

&lt;p&gt;In practice, clarity means:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowing what to build.&lt;/strong&gt; Not every feature request deserves to be built. Not every bug deserves to be fixed right now. The ability to evaluate, prioritize, and say no is a skill. It becomes more valuable when execution is cheap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowing when to build it.&lt;/strong&gt; Sequencing matters. Building the right thing in the wrong order creates rework. AI amplifies this problem: it can generate a lot of code very fast, and if the direction is wrong, you have a lot of wrong code very fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowing what to delegate.&lt;/strong&gt; Not everything should go to AI. Some decisions require human judgment, context, or accountability. The developer who throws everything at the model and hopes for the best will produce unstable, expensive, hard-to-maintain systems. Knowing the limits of delegation is a skill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowing how to frame it.&lt;/strong&gt; This is where most people underestimate the work. A vague brief produces a vague result. The better you can define the problem — constraints, expected output, edge cases, context — the better the output. That's not prompting as a trick. That's communication as a discipline.&lt;/p&gt;

&lt;p&gt;None of this is technical in the traditional sense. All of it determines whether the technical output is any good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing AI Is Managing People
&lt;/h2&gt;

&lt;p&gt;Here's the thing: none of this is new.&lt;/p&gt;

&lt;p&gt;The best practices for working with AI are basically textbook management principles. Break work into smaller tasks. Give clear scope before delegating. Review output before shipping. Don't overload with context. One thing at a time.&lt;/p&gt;

&lt;p&gt;Experienced managers figured this out with humans. The difference is that most developers never had to manage anyone. They just had to code. Now they're managing an AI that can out-execute them technically, and they're discovering management the hard way: by getting bad results and wondering why.&lt;/p&gt;

&lt;p&gt;The developer who spent years ignoring team dynamics, documentation, and clear communication now has a gap. The developer who built those habits, even informally, has an advantage they didn't expect.&lt;/p&gt;

&lt;p&gt;Prompting well is leading well. Scoping a task for AI is the same cognitive work as scoping a task for a junior engineer. The feedback loop is just faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Skills That Were Always There
&lt;/h2&gt;

&lt;p&gt;Soft skills were never actually soft. They were just undervalued because the market paid well for execution alone.&lt;/p&gt;

&lt;p&gt;Communication. Structured thinking. Prioritization. Breaking down complex problems. The ability to zoom out from the code and see the product. These were nice-to-haves when shipping code was hard. Now that shipping code is easy, they're the core competency.&lt;/p&gt;

&lt;p&gt;The developers who will thrive aren't necessarily the best at writing code. They're the best at knowing what code to write, why, and how to make sure it gets done right. Whether by themselves, a team, or a model.&lt;/p&gt;

&lt;p&gt;That's not a soft skill. That's the job now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do With This
&lt;/h2&gt;

&lt;p&gt;If you're a developer reading this, the question isn't whether AI will replace you. It's whether you're building the skills that AI can't replace.&lt;/p&gt;

&lt;p&gt;Start small: next time you open a chat with an AI, write the prompt as if you were briefing a capable but context-free junior engineer. Define the goal, the constraints, what done looks like, and what to avoid. See if the output changes.&lt;/p&gt;

&lt;p&gt;It will.&lt;/p&gt;

&lt;p&gt;That gap — between a poorly thought-out request and a well-structured one — is exactly where the value is now. Not just how you communicate it, but how clearly you've thought it through before you type anything.&lt;/p&gt;

&lt;p&gt;Starting there is the first step. But it goes deeper: spec-driven development, harness engineering, structured AI workflows, how teams are rethinking the entire dev process around these tools. That's what I'll keep writing about here.&lt;/p&gt;

&lt;p&gt;Subscribe if you want to follow along.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your 404 Logs Are a Security Report You're Ignoring</title>
      <dc:creator>Eduardo Villão</dc:creator>
      <pubDate>Mon, 27 Apr 2026 14:13:18 +0000</pubDate>
      <link>https://dev.to/edu_villao/your-404-logs-are-a-security-report-youre-ignoring-2c25</link>
      <guid>https://dev.to/edu_villao/your-404-logs-are-a-security-report-youre-ignoring-2c25</guid>
      <description>&lt;p&gt;Most WordPress developers install a redirect plugin, set up a few 301s, and never look at their 404 logs again. If they do, it's for SEO, fixing broken links, cleaning up crawl errors. That's the obvious use.&lt;/p&gt;

&lt;p&gt;But there's something else in those logs that almost everyone ignores.&lt;/p&gt;

&lt;p&gt;Your 404 logs are a real-time map of who's probing your site and what they're looking for. Bots scanning for exposed environment files, credential leaks, config files, path traversal exploits — all of it shows up as 404s before it shows up as a breach.&lt;/p&gt;

&lt;p&gt;I pulled the 404 logs from two of my own sites over the past week. Here's what I found, and what I blocked at the CDN level so these requests never hit my server again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;p&gt;Two WordPress sites. One week of data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Site A:&lt;/strong&gt; 1,388 total 404 requests across 1,118 unique URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Site B:&lt;/strong&gt; 2,693 total 404 requests across 1,877 unique URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over 4,000 requests that aren't real users, aren't search engines, and aren't broken links. They're automated scans looking for vulnerabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Attack Patterns
&lt;/h2&gt;

&lt;p&gt;Every 404 log tells a story. Here are the five categories I found in mine.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Environment File Scanning (.env)
&lt;/h3&gt;

&lt;p&gt;This is by far the most common pattern. Bots systematically scan every possible path where a &lt;code&gt;.env&lt;/code&gt; file might exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/.env                    → 49 hits
/staging/.env            → 20 hits
/backend/.env            → 20 hits
/react/.env              → 15 hits
/.env.local              → 15 hits
/shared/.env             → 13 hits
/api/.env                → 12 hits
/.env.production         → 12 hits
/app/.env                → 10 hits
/server/.env             → 10 hits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it doesn't stop there. I found requests for &lt;code&gt;.env.backup&lt;/code&gt;, &lt;code&gt;.env.bak&lt;/code&gt;, &lt;code&gt;.env.save&lt;/code&gt;, &lt;code&gt;.env.old&lt;/code&gt;, &lt;code&gt;.env_copy&lt;/code&gt;, &lt;code&gt;.env_secret&lt;/code&gt;, &lt;code&gt;.env~&lt;/code&gt;, &lt;code&gt;.env.swp&lt;/code&gt; — over 80 different &lt;code&gt;.env&lt;/code&gt; variations in total.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What they're looking for:&lt;/strong&gt; Database credentials, API keys, SMTP passwords, Stripe keys, AWS secrets. One exposed &lt;code&gt;.env&lt;/code&gt; file = full access to your infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cloud Credential Harvesting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/.aws/credentials        → 17 hits
/.aws/config             → 5 hits
/.AwS/CrEdEnTiAlS        → 5 hits (case variation to bypass rules)
/.terraform/terraform.tfstate → 6 hits
/serviceAccountKey.json  → 4 hits
/credentials.json        → 5 hits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the case variation on &lt;code&gt;.AwS/CrEdEnTiAlS&lt;/code&gt; — that's specifically designed to bypass naive pattern matching rules that only check lowercase. They're hunting for AWS keys, GCP service accounts, and Terraform state files that might contain infrastructure secrets.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Config &amp;amp; Secret File Probing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/config/initializers/secret_token.rb  → 7 hits (Rails)
/config/storage.yml                   → 6 hits (Rails)
/application.yml                      → 5 hits (Spring Boot)
/docker-compose.yml                   → 4 hits
/sftp-config.json                     → 5 hits (Sublime SFTP)
/.vscode/sftp.json                    → 3 hits (VS Code SFTP)
/config.php.bak                       → 5 hits
/secrets.json                         → 6 hits
/sendgrid.env                         → 7 hits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bots don't care what framework you use. They scan for Rails, Laravel, Spring Boot, Node.js, Django — all in the same sweep. If you accidentally deployed a config file, they'll find it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. WordPress-Specific Probing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/wp-config               → 3 hits
/wp-config~              → 1 hit
/wp-config.production    → 1 hit
/wp-configbak            → 1 hit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They're looking for backup copies of &lt;code&gt;wp-config.php&lt;/code&gt; — the file that contains your database credentials. Editor temp files (&lt;code&gt;wp-config~&lt;/code&gt;), manual backups (&lt;code&gt;wp-configbak&lt;/code&gt;), environment-specific copies. One careless &lt;code&gt;cp wp-config.php wp-config.bak&lt;/code&gt; and you've exposed everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Path Traversal &amp;amp; Code Execution Attempts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/passwd?raw??                                → 3 hits
/@fs/etc/passwd?import&amp;amp;raw??                     → 3 hits
/admin/config?cmd=cat /root/.aws/credentials     → 1 hit
/vendor/phpunit/phpunit/phpunit.xsd              → 2 hits
/pms?module=logging&amp;amp;file_name=../../~/.aws/credentials → 1 hit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are active exploitation attempts, not just scanning. They're trying to read system files, execute commands, and exploit known vulnerabilities in PHPUnit and other packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do About It: Block at the CDN
&lt;/h2&gt;

&lt;p&gt;Here's the key insight: every one of these requests hit your server. Your WordPress installation processed them, your PHP executed, your database was queried for a 404 page — all for a bot that's trying to hack you.&lt;/p&gt;

&lt;p&gt;The fix is simple: block these patterns at the CDN level (Cloudflare, Fastly, CloudFront...) so they never reach your server. Zero load on your infrastructure, zero PHP execution, zero risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudflare WAF Rules
&lt;/h2&gt;

&lt;p&gt;Here are the rules I set up based on my actual 404 data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; use &lt;code&gt;lower()&lt;/code&gt; in all of them — attackers use case variations like &lt;code&gt;.AwS/CrEdEnTiAlS&lt;/code&gt; to bypass naive rules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Rule 1: Block .env file access&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="py"&gt;Action:&lt;/span&gt; &lt;span class="n"&gt;Block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule 2: Block credential/config file access&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;".aws/credentials"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"terraform.tfstate"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"serviceaccountkey"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"docker-compose.yml"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"sftp-config"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"sftp.json"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"secrets.json"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"credentials.json"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"sendgrid"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"secret_token.rb"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"storage.yml"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"application.yml"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="py"&gt;Action:&lt;/span&gt; &lt;span class="n"&gt;Block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule 3: Block wp-config probing&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"wp-config"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;
 &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;http.request.uri.path&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"/wp-admin/"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="py"&gt;Action:&lt;/span&gt; &lt;span class="n"&gt;Block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule 4: Block path traversal attempts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"etc/passwd"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"phpunit"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"update.cgi"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="n"&gt;http.request.uri&lt;/span&gt; &lt;span class="ow"&gt;contains&lt;/span&gt; &lt;span class="s2"&gt;"cmd="&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="py"&gt;Action:&lt;/span&gt; &lt;span class="n"&gt;Block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule 5: Block common backup/install directory probing&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"/old/"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"/new/"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"/backup/"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"/wordpress/"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"/wp/"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
 &lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http.request.uri.path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"/test/"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="py"&gt;Action:&lt;/span&gt; &lt;span class="n"&gt;Block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloudflare string comparison is case-sensitive by default. The &lt;code&gt;lower()&lt;/code&gt; function converts the URI path to lowercase before comparison, so &lt;code&gt;/.AwS/CrEdEnTiAlS&lt;/code&gt; gets matched by a rule checking for &lt;code&gt;.aws/credentials&lt;/code&gt;. Without &lt;code&gt;lower()&lt;/code&gt;, that request slips through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CDN-Level Blocking Matters
&lt;/h2&gt;

&lt;p&gt;When you block at the CDN:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The request never reaches your server&lt;/li&gt;
&lt;li&gt;No PHP execution, no database query, no CPU usage&lt;/li&gt;
&lt;li&gt;Your server resources go to real users, not bots&lt;/li&gt;
&lt;li&gt;You reduce your attack surface to zero for known patterns&lt;/li&gt;
&lt;li&gt;Logs stay clean, making it easier to spot new patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Blocking at the application level (WordPress plugins, &lt;code&gt;.htaccess&lt;/code&gt;) still works, but the request already consumed server resources by the time it's blocked. CDN-level blocking is the difference between a bouncer at the door and a bouncer inside the bar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make This a Habit
&lt;/h2&gt;

&lt;p&gt;Pull your 404 logs once a month. Look for patterns. Add new Cloudflare rules.&lt;/p&gt;

&lt;p&gt;The bots evolve. New scanning patterns appear. The &lt;code&gt;.AwS/CrEdEnTiAlS&lt;/code&gt; case variation trick is a perfect example — someone specifically designed that to bypass lowercase-only rules.&lt;/p&gt;

&lt;p&gt;Your 404 logs aren't just broken links. They're a free security audit. Read them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The data in this post comes from real 404 logs collected over one week from two production WordPress sites. No IPs or identifying information from the attacking bots are included.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>wordpress</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Masky.js: A Lightweight Alternative to Inputmask, Cleave.js, and IMask</title>
      <dc:creator>Eduardo Villão</dc:creator>
      <pubDate>Mon, 27 Apr 2026 14:05:55 +0000</pubDate>
      <link>https://dev.to/edu_villao/maskyjs-a-lightweight-alternative-to-inputmask-cleavejs-and-imask-3pp5</link>
      <guid>https://dev.to/edu_villao/maskyjs-a-lightweight-alternative-to-inputmask-cleavejs-and-imask-3pp5</guid>
      <description>&lt;p&gt;Finding the right input masking library can be tricky. There are many options, each with its pros and cons. Some are feature-packed but heavy, while others are lightweight but miss critical functionalities like validation or mobile-friendly optimizations.&lt;/p&gt;

&lt;p&gt;In today's world, where performance and user experience are top priorities, selecting the right library is crucial. A solution that minimizes bundle size and enhances mobile usability while providing robust validation can make a huge difference in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Popular Libraries and Their Limitations
&lt;/h2&gt;

&lt;p&gt;If you've needed input masking for forms, you've likely come across libraries like Inputmask, Cleave.js, and IMask. These libraries are really great, but they come with trade-offs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inputmask:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Powerful and flexible, but significantly increases bundle size (~20 KB gzipped).&lt;/li&gt;
&lt;li&gt;Lacks features like &lt;code&gt;inputmode&lt;/code&gt; for better mobile experiences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cleave.js:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple and supports dynamic masks.&lt;/li&gt;
&lt;li&gt;However, it lacks built-in validation or automatic configurations like &lt;code&gt;minlength&lt;/code&gt; and &lt;code&gt;maxlength&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;IMask:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Offers great features with a moderate size (~5 KB gzipped).&lt;/li&gt;
&lt;li&gt;It's powerful but can be overkill for simpler use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While these are solid tools, I felt there was room for a solution that's lighter, flexible, and focused on mobile usability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Masky.js
&lt;/h2&gt;

&lt;p&gt;That's why I built &lt;strong&gt;Masky.js&lt;/strong&gt;: an ultra-lightweight (just 1.3 KB gzipped) alternative that prioritizes performance without sacrificing flexibility or essential features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Super Lightweight:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At just 1.3 KB gzipped, it's one of the smallest solutions on the market.&lt;/li&gt;
&lt;li&gt;Perfect for projects where bundle size is critical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mobile-Friendly:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatically sets the &lt;code&gt;inputmode&lt;/code&gt; attribute based on the mask, ensuring a better typing experience on mobile devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fully Customizable:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports custom masks with prefixes, suffixes, and even reverse masks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Built-In Validation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native support for CPF and CNPJ (Brazilian IDs) validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Automation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatically calculates and applies &lt;code&gt;minlength&lt;/code&gt; and &lt;code&gt;maxlength&lt;/code&gt; based on the mask.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Zero Dependencies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100% Vanilla JS, making it easy to integrate with any environment or framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Masky.js&lt;/th&gt;
&lt;th&gt;Inputmask&lt;/th&gt;
&lt;th&gt;Cleave.js&lt;/th&gt;
&lt;th&gt;IMask&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Size (Gzipped)&lt;/td&gt;
&lt;td&gt;1.6 KB&lt;/td&gt;
&lt;td&gt;20 KB&lt;/td&gt;
&lt;td&gt;8 KB&lt;/td&gt;
&lt;td&gt;5 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom Masks&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prefixes/Suffixes&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built-in CPF/CNPJ Validation&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;inputmode&lt;/code&gt; for Mobile&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic Min/Max Length&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reverse Masks&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Usage Example
&lt;/h2&gt;

&lt;p&gt;Simplicity is key. Just add the &lt;code&gt;data-mask&lt;/code&gt; attribute, and let Masky.js handle the rest — prefixes, suffixes, validations, and even automatic &lt;code&gt;inputmode&lt;/code&gt; and &lt;code&gt;minlength&lt;/code&gt; adjustments.&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="c"&gt;&amp;lt;!-- Simple Phone Mask --&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;"text"&lt;/span&gt; &lt;span class="na"&gt;data-mask=&lt;/span&gt;&lt;span class="s"&gt;"(00) 00000-0000"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Add Prefix and Suffix --&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;"text"&lt;/span&gt; &lt;span class="na"&gt;data-mask=&lt;/span&gt;&lt;span class="s"&gt;"000-000"&lt;/span&gt; &lt;span class="na"&gt;data-mask-prefix=&lt;/span&gt;&lt;span class="s"&gt;"+55 "&lt;/span&gt; &lt;span class="na"&gt;data-mask-suffix=&lt;/span&gt;&lt;span class="s"&gt;" ext"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Built-in CPF Validation --&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;"text"&lt;/span&gt; &lt;span class="na"&gt;data-mask=&lt;/span&gt;&lt;span class="s"&gt;"000.000.000-00"&lt;/span&gt; &lt;span class="na"&gt;data-mask-validation=&lt;/span&gt;&lt;span class="s"&gt;"cpf"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/masky-js/dist/masky.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check more details of how to use on the &lt;a href="https://github.com/eduardovillao/masky-js#readme" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it Out
&lt;/h2&gt;

&lt;p&gt;If you're looking for a fast, flexible, and performance-focused input masking solution, give Masky.js a shot!&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/eduardovillao/masky-js" rel="noopener noreferrer"&gt;https://github.com/eduardovillao/masky-js&lt;/a&gt;&lt;br&gt;&lt;br&gt;
👉 &lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/masky-js" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/masky-js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts or suggestions!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
