<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Shakil Alam on Medium]]></title>
        <description><![CDATA[Stories by Shakil Alam on Medium]]></description>
        <link>https://medium.com/@itxshakil?source=rss-78f39e6d28af------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*kI1mfEgvJQqas9IYgjhzdw.jpeg</url>
            <title>Stories by Shakil Alam on Medium</title>
            <link>https://medium.com/@itxshakil?source=rss-78f39e6d28af------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 24 May 2026 02:24:39 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@itxshakil/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How We Implemented Content Security Policy (CSP) in Our Laravel App]]></title>
            <link>https://medium.com/@itxshakil/how-we-implemented-content-security-policy-csp-in-our-laravel-app-5693a0be91dc?source=rss-78f39e6d28af------2</link>
            <guid isPermaLink="false">https://medium.com/p/5693a0be91dc</guid>
            <category><![CDATA[laravel]]></category>
            <category><![CDATA[csp]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[security]]></category>
            <dc:creator><![CDATA[Shakil Alam]]></dc:creator>
            <pubDate>Fri, 08 May 2026 13:25:35 GMT</pubDate>
            <atom:updated>2026-05-08T13:25:35.966Z</atom:updated>
            <content:encoded><![CDATA[<p>Our pentest report had one line that stopped us cold:</p><blockquote>“Application does not implement Content-Security-Policy headers. XSS payloads executed without restriction.”</blockquote><p>We had Sanctum, CSRF tokens, input validation — all the standard Laravel security checklist items. But we had no CSP. And without it, a single successful XSS attack could exfiltrate session cookies, inject malicious scripts, or silently redirect users to attacker-controlled pages — all from our own domain.</p><p>This is the story of how we added CSP to a production Laravel application without breaking anything, how we built a violation reporting pipeline, and the things we wish we’d known before starting.</p><p><strong>What’s in this guide</strong></p><p>This post is written to be useful regardless of where you’re starting from:</p><ul><li><strong>Never heard of CSP?</strong> Start from the top. The first two sections give you the mental model before any code appears.</li><li><strong>Know what CSP is but haven’t implemented it?</strong> Jump to <a href="https://dev.to/itxshakil/how-we-implemented-content-security-policy-csp-in-our-laravel-app-1h6o-temp-slug-5628215?preview=4b014bfad96d3942a161aa694870314db9062e1450f36477cbc9e16bb9a5e643577252d31bfe4835b802752adf7ba9008abcbc21033ff068816c1971#the-implementation">The Implementation</a>.</li><li><strong>Already running CSP and want to tighten it or add reporting?</strong> Jump to <a href="https://dev.to/itxshakil/how-we-implemented-content-security-policy-csp-in-our-laravel-app-1h6o-temp-slug-5628215?preview=4b014bfad96d3942a161aa694870314db9062e1450f36477cbc9e16bb9a5e643577252d31bfe4835b802752adf7ba9008abcbc21033ff068816c1971#csp-violation-reporting">CSP Violation Reporting</a> or the <a href="https://dev.to/itxshakil/how-we-implemented-content-security-policy-csp-in-our-laravel-app-1h6o-temp-slug-5628215?preview=4b014bfad96d3942a161aa694870314db9062e1450f36477cbc9e16bb9a5e643577252d31bfe4835b802752adf7ba9008abcbc21033ff068816c1971#pre-enforcement-checklist">Pre-Enforcement Checklist</a>.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yDhuMcHj3x4aFMFYhVdjYw.png" /></figure><h3>What Content Security Policy Actually Does</h3><p>When a browser loads a page, it executes whatever scripts, styles, fonts, and images are on it — regardless of where they came from. That’s what makes XSS dangerous: if an attacker manages to inject a &lt;script&gt; tag into your HTML, the browser runs it without question.</p><p>CSP is an HTTP response header that changes this. It tells the browser: <em>“Only trust resources from these specific sources. Anything else — block it.”</em></p><pre>Content-Security-Policy: default-src &#39;self&#39;; script-src &#39;self&#39; &#39;nonce-abc123&#39;; object-src &#39;none&#39;</pre><p>With that header in place, even if an attacker injects a &lt;script&gt; tag, the browser refuses to execute it. The tag has no valid <strong>nonce</strong> — a cryptographically random token generated fresh for each request. Scripts carrying the matching nonce run. Everything else is blocked.</p><blockquote><strong><em>New to this?</em></strong><em> MDN has the </em><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy"><em>definitive CSP reference</em></a><em> if you want to go deeper on any specific directive.</em></blockquote><h3>What Is a Nonce?</h3><p>You’ll see the word “nonce” throughout this post, so let’s settle it quickly before writing any code.</p><p>A <strong>nonce</strong> (short for <em>number used once</em>) is a random string generated fresh on every single page load. The server puts it in the CSP header, and also stamps it onto every &lt;script&gt; or &lt;style&gt; block it trusts:</p><pre>// In the HTTP header the server sends:<br>Content-Security-Policy: script-src &#39;nonce-k8Hv2mXpQ3&#39;<br><br>// On the script tag in your HTML:<br>&lt;script nonce=&quot;k8Hv2mXpQ3&quot;&gt;...&lt;/script&gt;</pre><p>The browser sees both, checks they match, and runs the script. If an attacker injects a &lt;script&gt; tag, it has no nonce — or the wrong one — so it gets blocked. Since the nonce changes on every request, an attacker who somehow saw yesterday&#39;s nonce can&#39;t reuse it today.</p><p><strong>Why not just use a static secret key?</strong> Because a static value could be cached, leaked in logs, or extracted from your source code. A nonce only needs to survive one request. After that, it’s worthless.</p><p><strong>What a nonce is not:</strong></p><ul><li>It is not a replacement for input sanitisation. A nonce stops injected scripts from <em>running</em>. It doesn’t stop them from being <em>stored</em> in your database.</li><li>It is not encryption. Anyone who can see the page source can see the nonce — that’s fine, because it expires the moment the request ends.</li><li>It is not a CSRF token. They look similar in concept but solve completely different problems.</li></ul><p>That’s it. When you see nonce=&quot;{{ $cspNonce }}&quot; in templates below, it simply means: <em>&quot;the server trusts this specific block.&quot;</em></p><h3>The Naive Approach (and Why It Fails)</h3><p>Most teams discover CSP when a security audit flags it, then reach for the quickest fix: a static header in nginx.conf or apache.conf.</p><p>This works for about five minutes. Then the inline JavaScript breaks. Alpine.js stops. Livewire stops. Any &lt;script&gt; block that doesn&#39;t come from a separate file gets blocked. The knee-jerk reaction is:</p><pre>script-src &#39;self&#39; &#39;unsafe-inline&#39;</pre><p>Done. Everything works again. And CSP is now completely useless — &#39;unsafe-inline&#39; means <em>any</em> inline script can run, including one an attacker injected.</p><p>The correct approach is a <strong>nonce-based CSP generated per request inside Laravel</strong>, where every trusted inline script carries a token only the server knows. That’s what we built.</p><h3>The Mindset Shift: Write CSP-Friendly Code</h3><p>Before writing a single line of middleware, you need to change how you write templates. CSP doesn’t just enforce a header — it enforces discipline about where your code lives.</p><p><strong>Move JavaScript out of your HTML.</strong> Event handler attributes are the main offender:</p><pre>&lt;!-- ❌ This is blocked by any sensible CSP --&gt;<br>&lt;button onclick=&quot;submitForm()&quot;&gt;Submit&lt;/button&gt;<br>&lt;script&gt;function submitForm() { ... }&lt;/script&gt;<br><br>&lt;!-- ✅ This is CSP-friendly — JS lives in an external file --&gt;<br>&lt;button id=&quot;submit-btn&quot;&gt;Submit&lt;/button&gt;<br>&lt;script src=&quot;/js/form.js&quot;&gt;&lt;/script&gt;</pre><p><strong>Move styles out of your HTML too.</strong> The style=&quot;&quot; attribute on elements cannot carry a nonce — there&#39;s no mechanism to whitelist individual instances. Your only options with a strict policy are &quot;allow all of them&quot; or &quot;block all of them.&quot; Build the habit of using CSS classes from the start.</p><p><strong>For inline code you genuinely can’t move</strong> — a config value JavaScript needs, critical above-the-fold CSS — the nonce is your escape hatch:</p><pre>&lt;script nonce=&quot;{{ $cspNonce }}&quot;&gt;<br>    window.APP_ENV = &quot;{{ config(&#39;app.env&#39;) }}&quot;;<br>&lt;/script&gt;</pre><p>A nonce is generated fresh on every request. An attacker can’t predict it. A script they inject has no nonce, so it gets blocked even if everything else fails.</p><blockquote><strong><em>Pro Tip:</em></strong><em> Never hardcode a nonce, never reuse one. Generate it with </em><em>random_bytes() — not </em><em>uniqid(), not </em><em>md5(time()).</em></blockquote><blockquote><strong><em>Just want the output without the reading?</em></strong><em> I built a free </em><a href="https://blog.shakiltech.com/tools/laravel-csp-generator"><em>Laravel CSP Generator</em></a><em> — toggle your CDNs and integrations, and it generates the full policy string, the PHP middleware method, and an adaptive pre-enforcement checklist live. Come back here for the why behind each decision.</em></blockquote><h3>The Implementation</h3><p>We put all security headers in one SecureHeadersMiddleware rather than spreading logic across nginx.conf, .htaccess, and multiple middleware files.</p><h3>Step 1: Generate the Nonce and Share It</h3><pre>php artisan make:middleware SecureHeadersMiddleware</pre><pre>&lt;?php<br><br>declare(strict_types=1);<br>namespace App\Http\Middleware;<br>use Closure;<br>use Illuminate\Http\Request;<br>use Illuminate\Support\Facades\Vite;<br>use Illuminate\Support\Facades\View;<br>use Symfony\Component\HttpFoundation\Response;<br>class SecureHeadersMiddleware<br>{<br>    // Disable in local/testing to avoid log noise from developer browsers<br>    // full of extensions. Automatically driven by the current environment.<br>    private bool $allowReporting;<br>    public function __construct()<br>    {<br>        $this-&gt;allowReporting = !app()-&gt;environment([&#39;local&#39;, &#39;testing&#39;]);<br>    }<br>    public function handle(Request $request, Closure $next): Response<br>    {<br>        $nonce = base64_encode(random_bytes(16));<br>        // Share with all Blade templates automatically - no manual passing needed<br>        View::share(&#39;cspNonce&#39;, $nonce);<br>        // Tell Vite to inject this nonce on its bootstrapping inline script.<br>        // Skip this and @vite() silently breaks under a strict CSP.<br>        Vite::useCspNonce($nonce);<br>        /** @var Response $response */<br>        $response = $next($request);<br>        $this-&gt;addContentSecurityPolicy($response, $nonce, $request);<br>        $this-&gt;addClickjackingProtection($response);<br>        $this-&gt;addStrictTransportSecurity($response, $request);<br>        $this-&gt;addMiscSecurityHeaders($response);<br>        $this-&gt;removeUnwantedHeaders($response);<br>        if ($this-&gt;allowReporting &amp;&amp; $this-&gt;supportsReportTo($request)) {<br>            $this-&gt;addCspReportingEndpoint($response);<br>        }<br>        return $response;<br>    }<br>}</pre><p>Two ordering rules matter here. The nonce must be generated <em>before</em>$next($request) because the view renders during that call. The headers are set <em>after</em>because they need the response object. Swap either and things break silently.</p><h3>Step 2: Define the CSP Policy</h3><pre>protected function addContentSecurityPolicy(Response $response, string $nonce, Request $request): void<br>{<br>    $isLocal = app()-&gt;environment(&#39;local&#39;);<br><br>    $policy = [<br>        &quot;default-src &#39;self&#39;&quot;,<br>        &quot;script-src &#39;self&#39; &#39;nonce-{$nonce}&#39; https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://embed.tawk.to&quot;,<br>        &quot;style-src &#39;self&#39; &#39;nonce-{$nonce}&#39; https://fonts.googleapis.com https://cdnjs.cloudflare.com&quot;,<br>        &quot;style-src-elem &#39;self&#39; &#39;nonce-{$nonce}&#39; https://fonts.googleapis.com https://cdnjs.cloudflare.com&quot;,<br>        &quot;style-src-attr &#39;none&#39;&quot;,<br>        &quot;font-src &#39;self&#39; data: https://fonts.gstatic.com https://cdnjs.cloudflare.com&quot;,<br>        &quot;img-src &#39;self&#39; data: https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://your-cdn.example.com&quot;,<br>        &quot;frame-src &#39;self&#39; https://your-video-host.example.com&quot;,<br>        // Vite&#39;s HMR uses a WebSocket on localhost - only needed in local dev.<br>        // Never ship localhost entries to production.<br>        &quot;connect-src &#39;self&#39;&quot; . ($isLocal ? &quot; ws://localhost:5173 wss://localhost:5173&quot; : &quot;&quot;),<br>        &quot;object-src &#39;none&#39;&quot;,<br>        &quot;base-uri &#39;self&#39;&quot;,<br>        &quot;frame-ancestors &#39;none&#39;&quot;,<br>    ];<br>    // upgrade-insecure-requests on a local HTTP server triggers confusing<br>    // mixed-content errors that have nothing to do with your code.<br>    if (!$isLocal) {<br>        $policy[] = &#39;upgrade-insecure-requests&#39;;<br>    }<br>    if ($this-&gt;allowReporting) {<br>        $policy[] = &#39;report-uri /api/csp-violation-report&#39;;<br>    }<br>    $response-&gt;headers-&gt;set(&#39;Content-Security-Policy&#39;, implode(&#39;; &#39;, $policy));<br>}</pre><p><strong>Directive reference — what each one actually does:</strong></p><p>DirectiveWhat it controlsOur valuedefault-srcFallback for any type not explicitly listed&#39;self&#39;script-srcJavaScript execution&#39;self&#39; + nonce + CDN allowliststyle-srcCSS @import rules within stylesheets; base fallback&#39;self&#39; + nonce + CDN allowliststyle-src-elem&lt;link rel=&quot;stylesheet&quot;&gt; and &lt;style&gt; blocks&#39;self&#39; + nonce + CDN allowliststyle-src-attrInline style=&quot;&quot; attributes&#39;none&#39; (blocked entirely)font-srcFont files&#39;self&#39; + data URIs + Google Fontsimg-srcImages&#39;self&#39; + data URIs + CDNframe-src&lt;iframe&gt; sources&#39;self&#39; + trusted video hostconnect-srcfetch(), XHR, WebSockets, SSE&#39;self&#39; + localhost WS in devobject-srcBrowser plugins (Flash, Java)&#39;none&#39;base-uriThe &lt;base&gt; tag&#39;s href&#39;self&#39;frame-ancestorsWho can embed this page in an iframe&#39;none&#39;</p><p><strong>default-src &#39;self&#39;</strong> means anything not covered by a specific directive falls back to same-origin only.</p><p><strong>script-src</strong> is where most of the work happens. The nonce is per-request, cryptographically random, and unpredictable. In CSP Level 3, the presence of a nonce automatically overrides &#39;unsafe-inline&#39; for supporting browsers — so even if you add it as a temporary fallback, modern browsers ignore it in favour of nonce validation.</p><p><strong>connect-src</strong> controls everything your JavaScript can talk to: fetch(), XMLHttpRequest, WebSockets, and Server-Sent Events. Vite&#39;s Hot Module Replacement uses a WebSocket on localhost:5173 to push changes to your browser during development. Without it in connect-src, HMR fails silently and you&#39;re doing hard refreshes all day. In production, that entry must not appear — hence the environment check.</p><p><strong>object-src &#39;none&#39;</strong> blocks Flash and all other browser plugins. There&#39;s no reason to allow these in 2026.</p><p><strong>base-uri &#39;self&#39;</strong> prevents an attacker from injecting &lt;base href=&quot;https://evil.com&quot;&gt;, which would silently redirect all relative URLs — links, form actions, script paths — to their server.</p><p><strong>frame-ancestors &#39;none&#39;</strong> is clickjacking protection at the CSP level: your pages cannot be embedded in iframes on other domains. Stronger than X-Frame-Optionsfor modern browsers; we keep X-Frame-Options too for legacy ones.</p><p><strong>upgrade-insecure-requests</strong> tells the browser to automatically upgrade HTTP sub-resource requests to HTTPS. We skip it in local development because it causes confusing mixed-content errors on a plain HTTP dev server.</p><h4>Deep Dive: style-src, style-src-elem, and style-src-attr</h4><p>Most developers assume these three are aliases for the same thing and only set style-src. They&#39;re not — and that misunderstanding is what causes hours of debugging when styles randomly break.</p><p><strong>The one-line summary:</strong> style-src is the fallback. style-src-elem controls &lt;style&gt; blocks and &lt;link&gt; tags. style-src-attr controls style=&quot;&quot; attributes on elements. Set all three explicitly, or the browser decides how to inherit, which is rarely what you want.</p><p>Here’s the breakdown:</p><p><strong>style-src</strong> is the base rule that covers everything CSS-related when the more specific directives aren&#39;t set. Even when you do set the others, style-src still governs one thing they don&#39;t: <strong>CSS </strong><strong>@import rules inside your own stylesheets</strong>. @import url(&#39;https://fonts.googleapis.com/...&#39;) is controlled by style-src, not style-src-elem.</p><p><strong>style-src-elem</strong> controls two things: &lt;link rel=&quot;stylesheet&quot;&gt; tags and &lt;style&gt;blocks. The important detail is that &lt;style&gt; blocks <em>can</em> carry a nonce, so you can precisely whitelist only the inline styles you wrote:</p><pre>{{-- Trusted — nonce matches the header --}}<br>&lt;style nonce=&quot;{{ $cspNonce }}&quot;&gt;<br>    .hero { background: url(&#39;/img/hero.jpg&#39;) center/cover; }<br>&lt;/style&gt;</pre><pre>{{-- Blocked — no nonce --}}<br>&lt;style&gt;.injected { color: red; }&lt;/style&gt;</pre><p>External stylesheets via &lt;link href=&quot;...&quot;&gt; don&#39;t need a nonce — they&#39;re controlled by the domain allowlist. Nonces only apply to inline content.</p><p><strong>style-src-attr</strong> controls only the inline style=&quot;&quot; attribute on HTML elements — and this is the problem child. style=&quot;&quot; attributes <strong>cannot carry a nonce</strong>. There is no way to whitelist specific ones. Your only two options are &#39;unsafe-inline&#39; (trusts all of them, including anything injected) or &#39;none&#39; (blocks all of them). We use &#39;none&#39;.</p><pre>style-src       → CSS @import in files + base fallback  → &#39;self&#39; + nonce + CDN<br>style-src-elem  → &lt;link&gt; tags + &lt;style&gt; blocks          → nonce-gated<br>style-src-attr  → style=&quot;&quot; attributes                   → &#39;none&#39;, blocked entirely</pre><p><strong>Before you enable this:</strong> audit your JavaScript-driven UI libraries. Drag-and-drop, tooltips, and modals commonly set style=&quot;top: 48px; left: 200px&quot; dynamically — every one of them will break under style-src-attr &#39;none&#39;. Run in report-only mode first and your logs will tell you exactly which libraries are the offenders before anything breaks in production.</p><h3>Step 3: Use the Nonce in Blade</h3><pre>{{-- Vite assets — no nonce attribute needed on the tag itself.<br>     Vite::useCspNonce() in the middleware handles the inline bootstrapper. --}}<br>@vite([&#39;resources/css/app.css&#39;, &#39;resources/js/app.js&#39;])<br>{{-- Inline script blocks you write manually --}}<br>&lt;script nonce=&quot;{{ $cspNonce }}&quot;&gt;<br>    const config = @json($appConfig);<br>&lt;/script&gt;<br>{{-- Inline style blocks --}}<br>&lt;style nonce=&quot;{{ $cspNonce }}&quot;&gt;<br>    .hero { background: url(&#39;/img/hero.jpg&#39;) center/cover; }<br>&lt;/style&gt;<br>{{-- Livewire v2 --}}<br>@livewireScripts([&#39;nonce&#39; =&gt; $cspNonce])<br>{{-- Livewire v3 --}}<br>@livewireScriptConfig([&#39;nonce&#39; =&gt; $cspNonce])</pre><p><strong>One thing to be clear on:</strong> &lt;script src=&quot;...&quot;&gt; and &lt;link rel=&quot;stylesheet&quot; href=&quot;...&quot;&gt; tags pointing to external files do <strong>not</strong> need a nonce attribute. The browser permits them if their domain is on the allowlist. Nonces exist purely for inline content that has no domain to verify against.</p><h3>Step 4: Add the Rest of the Security Header Stack</h3><p>CSP is one layer. The same middleware handles the remaining browser security headers — each in a focused method:</p><pre>protected function addClickjackingProtection(Response $response): void<br>{<br>    // Legacy browsers fall back to this; modern ones use frame-ancestors from CSP<br>    $response-&gt;headers-&gt;set(&#39;X-Frame-Options&#39;, &#39;DENY&#39;);<br>}<br><br>protected function addStrictTransportSecurity(Response $response, Request $request): void<br>{<br>    // Check X-Forwarded-Proto too - without this, HSTS is never sent when<br>    // you&#39;re behind a load balancer that terminates SSL before Laravel sees the request<br>    $isHttps = $request-&gt;isSecure()<br>        || strcasecmp((string) $request-&gt;header(&#39;X-Forwarded-Proto&#39;, &#39;&#39;), &#39;https&#39;) === 0;<br>    if (!$isHttps) {<br>        return;<br>    }<br>    $response-&gt;headers-&gt;set(&#39;Strict-Transport-Security&#39;, &#39;max-age=31536000; includeSubDomains; preload&#39;);<br>}<br>protected function addMiscSecurityHeaders(Response $response): void<br>{<br>    // Prevents browsers from MIME-sniffing a response away from the declared content-type<br>    $response-&gt;headers-&gt;set(&#39;X-Content-Type-Options&#39;, &#39;nosniff&#39;);<br>    // Controls what&#39;s sent in the Referer header on navigation<br>    $response-&gt;headers-&gt;set(&#39;Referrer-Policy&#39;, &#39;strict-origin-when-cross-origin&#39;);<br>    // Deprecated and ignored by modern browsers, but harmless to include<br>    $response-&gt;headers-&gt;set(&#39;X-XSS-Protection&#39;, &#39;1; mode=block&#39;);<br>    // Restrict access to sensitive browser APIs<br>    $response-&gt;headers-&gt;set(&#39;Permissions-Policy&#39;, &#39;geolocation=(), microphone=(self), camera=(self), payment=(), fullscreen=*&#39;);<br>}<br>protected function removeUnwantedHeaders(Response $response): void<br>{<br>    // Both calls are needed: Symfony&#39;s header management and PHP&#39;s header() function<br>    // operate at different levels. Without both, X-Powered-By: PHP/8.x can still leak.<br>    foreach ([&#39;X-Powered-By&#39;, &#39;Server&#39;] as $header) {<br>        $response-&gt;headers-&gt;remove($header);<br>        @header_remove($header);<br>    }<br>}</pre><h3>Step 5: Register the Middleware</h3><pre>// Laravel 11 — bootstrap/app.php<br>-&gt;withMiddleware(function (Middleware $middleware) {<br>    $middleware-&gt;web(append: [<br>        \App\Http\Middleware\SecureHeadersMiddleware::class,<br>    ]);<br>})<br><br>// Laravel 10 - app/Http/Kernel.php<br>protected $middlewareGroups = [<br>    &#39;web&#39; =&gt; [<br>        // ...<br>        \App\Http\Middleware\SecureHeadersMiddleware::class,<br>    ],<br>];</pre><p>Attaching it to the web group means your JSON API routes — which don&#39;t render Blade views — don&#39;t receive an unnecessary CSP header. For high-throughput APIs that&#39;s a meaningful separation.</p><h3>CSP Violation Reporting — Your Early Warning System 🚨</h3><p>Running CSP without a reporting endpoint is like setting a burglar alarm with no monitoring. You need to know when the policy blocks something — to catch misconfigurations before users do, and to detect real injection attempts.</p><h3>What a Violation Report Looks Like</h3><p>When a browser blocks something, it POSTs a JSON payload to your report endpoint. Here’s what that looks like:</p><pre>{<br>  &quot;csp-report&quot;: {<br>    &quot;document-uri&quot;: &quot;https://yourapp.com/dashboard&quot;,<br>    &quot;referrer&quot;: &quot;&quot;,<br>    &quot;violated-directive&quot;: &quot;script-src-elem&quot;,<br>    &quot;effective-directive&quot;: &quot;script-src-elem&quot;,<br>    &quot;original-policy&quot;: &quot;default-src &#39;self&#39;; script-src &#39;self&#39; &#39;nonce-abc123&#39;&quot;,<br>    &quot;blocked-uri&quot;: &quot;https://evil.com/injected.js&quot;,<br>    &quot;status-code&quot;: 200<br>  }<br>}</pre><p>The blocked-uri tells you what was blocked. The violated-directive tells you which rule caught it. Together they tell you immediately whether this is a legitimate resource you forgot to allowlist or something that should never have been there.</p><blockquote><strong><em>Hosted alternative:</em></strong><em> If you’d rather not build your own reporting pipeline, </em><a href="https://report-uri.com/"><em>report-uri.com</em></a><em> by Scott Helme is a dedicated CSP report collection and analysis service. It handles noise filtering, dashboards, and alerting for you.</em></blockquote><h3>The Route</h3><pre>// routes/api.php<br>Route::post(&#39;/csp-violation-report&#39;, [CspReportController::class, &#39;store&#39;])<br>    -&gt;middleware(&#39;throttle:5&#39;);</pre><p>throttle:5 limits to 5 reports per minute per IP. Without rate limiting, an attacker can flood your logging infrastructure by triggering violations in a loop — exhausting disk space or burying real threats in noise.</p><h3>The Controller</h3><pre>&lt;?php<br><br>declare(strict_types=1);<br>namespace App\Http\Controllers\Api;<br>use Illuminate\Http\Request;<br>use Illuminate\Support\Facades\Log;<br>use Illuminate\Support\Facades\RateLimiter;<br>class CspReportController<br>{<br>    public function store(Request $request)<br>    {<br>        $body = json_decode($request-&gt;getContent(), true);<br>        $report = $body[&#39;csp-report&#39;] ?? $body ?? [];<br>        if (empty($report) || !is_array($report)) {<br>            return response()-&gt;noContent();<br>        }<br>        $json = json_encode($report);<br>        $ip = $request-&gt;ip();<br>        $blockedUri = data_get($report, &#39;blocked-uri&#39;, &#39;&#39;);<br>        $violatedDirective = data_get($report, &#39;violated-directive&#39;, &#39;&#39;);<br>        // Browser extensions are the biggest source of CSP noise.<br>        // Ad blockers, password managers, and DevTools extensions all fire reports.<br>        // They&#39;re not actionable - filter them immediately.<br>        if (<br>            str_contains($json, &#39;chrome-extension://&#39;) ||<br>            str_contains($json, &#39;moz-extension://&#39;) ||<br>            str_contains($json, &#39;safari-extension://&#39;)<br>        ) {<br>            return response()-&gt;noContent();<br>        }<br>        // Low-risk: usually your own code or a library doing something non-standard.<br>        // Review weekly, not immediately.<br>        $lowRiskUris = [&#39;about:blank&#39;, &#39;blob:&#39;, &#39;data:&#39;, &#39;inline&#39;, &#39;eval&#39;];<br>        if (in_array($blockedUri, $lowRiskUris)) {<br>            Log::channel(&#39;csp_low&#39;)-&gt;info(&#39;Low-risk CSP violation&#39;, compact(&#39;ip&#39;, &#39;report&#39;));<br>            return response()-&gt;noContent();<br>        }<br>        // High-risk: an external domain, an unknown script, potentially injected content.<br>        // Log with full context. Alert if sustained.<br>        Log::channel(&#39;csp_high&#39;)-&gt;warning(&#39;High-risk CSP violation&#39;, compact(&#39;ip&#39;, &#39;report&#39;));<br>        $this-&gt;checkForThresholdAlert($ip, $blockedUri, $violatedDirective);<br>        return response()-&gt;noContent();<br>    }<br>    protected function checkForThresholdAlert(string $ip, string $uri, string $directive): void<br>    {<br>        // Same IP, same directive, 10+ times in 60 seconds = probe, not noise.<br>        $key = &quot;csp_alert:$ip:{$directive}:$uri&quot;;<br>        if (RateLimiter::tooManyAttempts($key, 10)) {<br>            Log::alert(&#39;CSP threshold exceeded - possible active attack&#39;, [<br>                &#39;ip&#39;        =&gt; $ip,<br>                &#39;uri&#39;       =&gt; $uri,<br>                &#39;directive&#39; =&gt; $directive,<br>            ]);<br>            return;<br>        }<br>        RateLimiter::hit($key, 60);<br>    }<br>}</pre><h3>Log Channels</h3><pre>// config/logging.php<br>&#39;csp_low&#39; =&gt; [<br>    &#39;driver&#39; =&gt; &#39;single&#39;,<br>    &#39;path&#39;   =&gt; storage_path(&#39;logs/csp-low-risk.log&#39;),<br>    &#39;level&#39;  =&gt; &#39;info&#39;,<br>],<br><br>&#39;csp_high&#39; =&gt; [<br>    &#39;driver&#39; =&gt; &#39;single&#39;,<br>    &#39;path&#39;   =&gt; storage_path(&#39;logs/csp-high-risk.log&#39;),<br>    &#39;level&#39;  =&gt; &#39;warning&#39;,<br>],</pre><p>Separate files let you set different retention policies — low-risk logs rotate weekly, high-risk logs stay for 90 days. In production, wire Log::alert() into Slack or PagerDuty so a spike in high-risk violations at 2 AM wakes someone up.</p><h3>The Modern Report-To Header</h3><p>For Chrome 70+, Edge 79+, and Firefox 60+, the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API">Reporting API</a> (Report-To header) is more efficient than report-uri — reports are batched, and the endpoint is cached so the browser sends reports even from pages that didn&#39;t include the header.</p><pre>protected function supportsReportTo(Request $request): bool<br>{<br>    $ua = $request-&gt;header(&#39;User-Agent&#39;, &#39;&#39;);<br><br>    return (bool) preg_match(<br>        &#39;/(Chrome\/([7-9][0-9]|[1-9][0-9]{2,}))|(Firefox\/(6[0-9]|[7-9][0-9]|[1-9][0-9]{2,}))|(Edg\/([7-9][0-9]|[1-9][0-9]{2,}))|(Version\/(1[3-9]|[2-9][0-9])) Safari\//&#39;,<br>        $ua<br>    );<br>}<br>protected function addCspReportingEndpoint(Response $response): void<br>{<br>    $reportTo = [<br>        &#39;group&#39;              =&gt; &#39;csp-endpoint&#39;,<br>        &#39;max_age&#39;            =&gt; 10886400, // 126 days - browser caches this endpoint<br>        &#39;endpoints&#39;          =&gt; [<br>            [&#39;url&#39; =&gt; url(&#39;/api/csp-violation-report&#39;)],<br>        ],<br>        &#39;include_subdomains&#39; =&gt; true,<br>    ];<br>    $response-&gt;headers-&gt;set(&#39;Report-To&#39;, json_encode($reportTo, JSON_UNESCAPED_SLASHES));<br>}</pre><p>We keep report-uri in the CSP policy as a fallback for browsers that don&#39;t support the Reporting API. Both can coexist.</p><h3>Pre-Enforcement Checklist</h3><p>Run through this before switching from report-only to enforcement mode:</p><ul><li>[ ] Vite::useCspNonce($nonce) is called <em>before</em> $next($request) in the middleware</li><li>[ ] @vite(), @livewireScripts(), and @livewireScriptConfig() all pass the nonce</li><li>[ ] Every hand-written inline &lt;script&gt; block has nonce=&quot;{{ $cspNonce }}&quot;</li><li>[ ] Every hand-written inline &lt;style&gt; block has nonce=&quot;{{ $cspNonce }}&quot;</li><li>[ ] onclick=&quot;&quot;, onsubmit=&quot;&quot;, and other event handler attributes are removed from templates</li><li>[ ] style=&quot;&quot; attributes are replaced with CSS classes (or the library using them is documented)</li><li>[ ] All CDN domains you load from are in the appropriate allowlist</li><li>[ ] connect-src includes your API endpoints, WebSocket hosts, and analytics targets</li><li>[ ] connect-src does <strong>not</strong> include localhost in your production policy</li><li>[ ] The violation report endpoint is live, throttled, and log channels are configured</li><li>[ ] Report-only mode has run in production for at least one week with no new high-risk violations</li></ul><h3>Deploy Safely: Start in Report-Only Mode</h3><p>If you’re adding CSP to an existing app, don’t jump straight to enforcement. Swap the header name first:</p><pre>// Change this:<br>$response-&gt;headers-&gt;set(&#39;Content-Security-Policy&#39;, implode(&#39;; &#39;, $policy));<br><br>// To this, temporarily:<br>$response-&gt;headers-&gt;set(&#39;Content-Security-Policy-Report-Only&#39;, implode(&#39;; &#39;, $policy));</pre><p>The browser reports violations but blocks nothing. Run it in production for one to two weeks and watch csp-high-risk.log. Every entry is either a resource to allowlist or evidence of something that should never have been there. Once the log goes quiet, flip back to Content-Security-Policy.</p><p>This step is what separates a smooth rollout from an angry support queue.</p><blockquote><strong><em>Verify your headers:</em></strong><em> Use </em><a href="https://securityheaders.com/"><em>securityheaders.com</em></a><em> to grade your response headers. Use Google’s </em><a href="https://csp-evaluator.withgoogle.com/"><em>CSP Evaluator</em></a><em> to check your policy for common weaknesses. Both are free.</em></blockquote><h3>What We’d Do Differently</h3><p><strong>Drop </strong><strong>X-XSS-Protection.</strong> It&#39;s deprecated and ignored by modern browsers. Older IE versions had a bug where it could be exploited. We kept it because it doesn&#39;t actively hurt anything, but new implementations shouldn&#39;t bother.</p><p><strong>Separate concerns if your team is large.</strong> Bundling all headers in one middleware works well for us, but a dedicated CspMiddleware is easier to unit test and simpler to replace later. <a href="https://github.com/spatie/laravel-csp">Spatie&#39;s laravel-csp</a> package gives you a structured, per-policy-class approach with first-class test support if you want to skip the custom build entirely.</p><p><strong>Use hashes for truly static inline content.</strong> If you have a small piece of inline CSS that can’t move to a stylesheet — a background colour from a database value, for example — CSP supports SHA-256 hashes as an alternative to nonces. You precompute the hash of the exact string and whitelist it. Surgical precision with no runtime overhead.</p><p><strong>Explicitly exclude the </strong><strong>api group from the middleware.</strong> Our implementation attaches the middleware to the web group, which already keeps it off pure API routes. If your app registers any routes globally — or if you ever move to a flat route file — API responses will silently start receiving a CSP header they don&#39;t need. Being explicit with a group exclusion is safer than relying on registration order:</p><pre>// Laravel 11 — bootstrap/app.php<br>-&gt;withMiddleware(function (Middleware $middleware) {<br>    $middleware-&gt;web(append: [<br>        \App\Http\Middleware\SecureHeadersMiddleware::class,<br>    ]);<br>    // api group intentionally excluded — JSON responses don&#39;t need CSP<br>})</pre><h3>The Result</h3><p>After two weeks in report-only mode and one afternoon of allowlist tuning, we flipped to enforcement. Our security headers score went from D to A+ on <a href="https://securityheaders.com/">securityheaders.com</a>.</p><p>Since then, csp-high-risk.log has caught two incidents where a third-party CDN attempted to inject tracking scripts we never authorised. The middleware caught what code review wouldn&#39;t have.</p><p>If you want to configure your own policy without writing it from scratch, the <a href="https://csp-generator.shakiltech.com">Laravel CSP Generator</a> lets you do it visually — toggle your CDNs, integrations, and environment settings, and it outputs the full policy string and PHP middleware method ready to paste. The pre-enforcement checklist is built in and adapts to whatever you’ve configured.</p><p>CSP isn’t a silver bullet — it’s one layer in a defence-in-depth approach alongside CSRF protection, input validation, prepared statements, and HTTPS. But unlike most defences, it actively stops an entire class of attacks at the browser level, rather than hoping your application-layer defences caught everything upstream.</p><p>Start in report-only mode today. Watch your logs for a week. Then enforce. 🚀</p><h3>Get the Quick Reference PDF</h3><p>If you want a single document to keep open while you’re building — the full directive reference, the pre-enforcement checklist, common mistakes, and quick-start snippets — I put it all in a 4-page PDF.</p><p>Drop your email below and it’ll land in your inbox immediately. No series, no spam — just the PDF.</p><blockquote><a href="https://csp-generator.shakiltech.com/#guide"><strong><em>→ Get the Laravel CSP Quick Reference PDF</em></strong></a></blockquote><blockquote><em>4 pages · All 13 directives · Pre-enforcement checklist · 5 common mistakes · Free</em></blockquote><h3>Frequently Asked Questions</h3><p><strong>Does CSP break existing Laravel apps?</strong></p><p>It can — specifically if you have inline scripts or styles without nonces. Use Content-Security-Policy-Report-Only first to surface everything that would be blocked, fix your templates, then switch to enforcement. This is the only safe migration path for an app that&#39;s already in production.</p><p><strong>Do I need CSP if I already have CSRF protection?</strong></p><p>Yes. They protect against different attacks. CSRF prevents forged requests originating from other domains. CSP prevents injected scripts from <em>running on your domain</em>. One does not substitute for the other.</p><p><strong>Do I need CSP if my app has no user-generated content?</strong></p><p>Yes. XSS doesn’t require users to submit anything. A vulnerable dependency, a compromised CDN script, or a DOM-based XSS through a URL parameter are all real attack vectors that have nothing to do with user uploads.</p><p><strong>My Vite HMR stopped working after I added CSP. What’s wrong?</strong></p><p>Two things to check. First, confirm Vite::useCspNonce($nonce) is called before $next($request). Second, confirm connect-src includes ws://localhost:5173 (or your Vite port) in your local environment. HMR uses a WebSocket and fails silently if that WebSocket connection is blocked.</p><p><strong>What’s the difference between </strong><strong>report-uri and </strong><strong>Report-To?</strong></p><p>report-uri is the original mechanism and is widely supported. Report-To is the newer Reporting API — reports are batched, the endpoint URL is cached by the browser for months, and it covers more than just CSP. Use both: report-uri as the universal fallback, Report-To for modern browsers. Alternatively, skip rolling your own and use <a href="https://report-uri.com/">report-uri.com</a>.</p><p><strong>How do I debug CSP violations locally?</strong></p><p>Open DevTools → Console. Every CSP violation logs the exact blocked resource and the violated directive. That’s your fastest debugging tool. If you’re in report-only mode, the same violations also appear without anything actually breaking.</p><p><strong>How do I handle CSP with Livewire?</strong></p><p>Livewire injects inline JavaScript that needs the nonce. For v2: @livewireScripts([&#39;nonce&#39; =&gt; $cspNonce]). For v3: @livewireScriptConfig([&#39;nonce&#39; =&gt; $cspNonce]). Both are shown in Step 3 above.</p><p><strong>What about Google Analytics or Tag Manager?</strong></p><p>Add https://www.googletagmanager.com to script-src and https://www.google-analytics.com to both script-src and connect-src. GTM&#39;s custom HTML tags require &#39;unsafe-inline&#39;, which weakens your policy — server-side GTM or direct GA4 integration are cleaner alternatives worth the migration cost.</p><p><strong>Can I use Spatie’s </strong><strong>laravel-csp package instead of a custom middleware?</strong></p><p>Absolutely. <a href="https://github.com/spatie/laravel-csp">Spatie’s package</a> provides a structured, per-policy-class approach with built-in nonce support and first-class test utilities. Our custom middleware made sense for us because we wanted every security header in one place without a package dependency. Either approach is valid — choose based on your team’s preference for control versus convention.</p><p><em>Have you implemented CSP on a production Laravel app? Whether you had a smooth rollout, hit a rough edge with a third-party library, or are still sitting in report-only mode wondering what to do next — drop a comment. I’d love to hear where you got stuck, or what blocked resources surprised you most when you first flipped it on.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5693a0be91dc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Send SMS & OTPs in Laravel with Fast2SMS]]></title>
            <link>https://medium.com/@itxshakil/send-sms-otps-in-laravel-with-fast2sms-606e1825bfab?source=rss-78f39e6d28af------2</link>
            <guid isPermaLink="false">https://medium.com/p/606e1825bfab</guid>
            <category><![CDATA[laravel]]></category>
            <category><![CDATA[sms]]></category>
            <category><![CDATA[fast2sms]]></category>
            <dc:creator><![CDATA[Shakil Alam]]></dc:creator>
            <pubDate>Wed, 20 Aug 2025 03:54:17 GMT</pubDate>
            <atom:updated>2025-08-20T03:54:17.633Z</atom:updated>
            <content:encoded><![CDATA[<h3>1. Why SMS Still Matters in 2025</h3><ul><li>OTPs for logins &amp; registrations</li><li>Transaction alerts (banking, e-commerce, logistics)</li><li>Marketing campaigns &amp; DLT-compliant bulk messages<br> 👉 If you’re building a Laravel app in India, SMS is still essential.</li></ul><h3>2. The Problem Developers Face</h3><ul><li>Most SMS providers offer only <strong>raw APIs</strong>.</li><li>Integration = repetitive boilerplate code.</li><li>Handling <strong>queueing, retries, and scheduling</strong> is manual work.</li></ul><h3>3. Introducing Laravel Fast2SMS</h3><p>An open-source Laravel package that integrates seamlessly with the <strong>Fast2SMS API</strong>.<br> Built to be <strong>developer-friendly</strong>, it gives you a <strong>fluent interface</strong> for sending SMS:</p><p>✅ Send quick messages<br> ✅ Trigger OTP flows<br> ✅ Use DLT-approved templates<br> ✅ Check SMS balance<br> ✅ Schedule or queue messages</p><h3>4. Key Features at a Glance</h3><ul><li>🔑 <strong>Simple &amp; Fluent API</strong> → Write clean, expressive code.</li><li>⚡ <strong>Laravel-Ready</strong> → Works with queues, jobs, config system.</li><li>📦 <strong>Lightweight</strong> → No external dependencies, just drop it in.</li><li>🛡️ <strong>DLT Support</strong> → Ensure your marketing SMS are compliant.</li><li>🔔 <strong>Balance Check</strong> → Useful for monitoring credits inside your app.</li></ul><h3>5. When to Use It?</h3><ul><li><strong>Authentication Systems</strong> → OTP-based login / 2FA.</li><li><strong>E-commerce Stores</strong> → Order confirmations, shipping updates.</li><li><strong>FinTech Apps</strong> → Transaction alerts.</li><li><strong>SaaS Apps</strong> → Scheduled reminders, queued notifications.</li></ul><h3>6. Quick Start (5 Steps)</h3><ol><li>Install via Composer</li></ol><pre>composer require itxshakil/laravel-fast2sms</pre><p>2. Add your <strong>Fast2SMS API key</strong> in .env</p><p>3. Use the package in your controller/service</p><p>4. Send SMS with <strong>one line of code</strong></p><p>5. (Optional) Enable <strong>queueing</strong> for high-volume apps</p><h3>7. Open Source &amp; Community</h3><p>This package is MIT-licensed and free to use.</p><ul><li>GitHub: <a href="https://github.com/itxshakil/laravel-fast2sms">Laravel Fast2SMS</a></li><li>Issues, PRs, and feature requests are welcome.</li></ul><h3>8. What’s Next?</h3><ul><li>PHP 8.5 support (when it drops)</li><li>Webhook handling (delivery receipts)</li><li>Pre-built notification channel</li></ul><h3>9. Final Thoughts</h3><p>If your app serves Indian users and needs <strong>SMS/OTP integration</strong>, this package saves hours of repetitive coding.</p><p>👉 Try it today:</p><pre>composer require itxshakil/laravel-fast2sms</pre><p>And if you like it, don’t forget to ⭐ star the repo on GitHub — it helps more Laravel devs discover it!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=606e1825bfab" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ Send SMS, OTP & DLT Messages in Laravel with Fast2SMS (Open Source Package)]]></title>
            <link>https://medium.com/@itxshakil/send-sms-otp-dlt-messages-in-laravel-with-fast2sms-open-source-package-85325aa91a17?source=rss-78f39e6d28af------2</link>
            <guid isPermaLink="false">https://medium.com/p/85325aa91a17</guid>
            <category><![CDATA[fast2sms]]></category>
            <category><![CDATA[sms]]></category>
            <category><![CDATA[laravel]]></category>
            <dc:creator><![CDATA[Shakil Alam]]></dc:creator>
            <pubDate>Wed, 20 Aug 2025 03:20:52 GMT</pubDate>
            <atom:updated>2025-08-20T03:20:52.179Z</atom:updated>
            <content:encoded><![CDATA[<p>In 2025, SMS is still a powerful way to reach users. Whether it’s for <strong>one-time passwords (OTPs), transaction alerts, delivery updates, or marketing campaigns</strong>, SMS remains one of the most reliable communication channels — especially in India, where mobile penetration is massive.</p><figure><img alt="🚀 Send SMS &amp; OTPs in Laravel the Laravel way! Laravel Fast2SMS is an open-source package for Fast2SMS API with queues, OTP &amp; DLT support." src="https://cdn-images-1.medium.com/max/1024/1*v2iYbYEjJL7cPsKRNeYgvg.png" /></figure><p>But as a Laravel developer, integrating SMS isn’t always straightforward. Most SMS providers only give you <strong>raw REST APIs</strong>, which means:</p><ul><li>Writing repetitive boilerplate code.</li><li>Manually handling <strong>queues, retries, and scheduling</strong>.</li><li>Struggling to keep things <strong>clean and Laravel-native</strong>.</li></ul><p>That’s where <strong>Laravel Fast2SMS</strong> comes in. 🎉</p><h3>⚡ Meet Laravel Fast2SMS</h3><p><a href="https://github.com/itxshakil/laravel-fast2sms">Laravel Fast2SMS</a> is an open-source Laravel package that integrates seamlessly with the <strong>Fast2SMS API</strong>.</p><p>It’s designed to make SMS sending <strong>simple, fluent, and Laravel-friendly</strong> — so you can focus on your app, not SMS plumbing.</p><h3>✨ Key Features</h3><ul><li>🔑 <strong>Simple &amp; Fluent API</strong> → Expressive, chainable syntax for clean code.</li><li>⚡ <strong>Laravel-Ready</strong> → Works out of the box with queues, jobs, and config.</li><li>📦 <strong>Lightweight</strong> → No unnecessary dependencies.</li><li>🛡️ <strong>DLT-Compliant</strong> → Send template-based SMS for marketing campaigns.</li><li>🔔 <strong>Balance Check</strong> → Track SMS credits inside your app.</li></ul><h3>🚀 What Can You Do with It?</h3><p>This package is ideal for:</p><ul><li><strong>Authentication Systems</strong> → OTP-based login and 2FA.</li><li><strong>E-commerce Stores</strong> → Send order confirmations, delivery updates.</li><li><strong>FinTech Apps</strong> → Push transaction alerts instantly.</li><li><strong>SaaS Platforms</strong> → Scheduled reminders, automated notifications.</li></ul><p>Basically, if your app targets <strong>Indian users</strong>, this package will save you hours of integration work.</p><h3>🛠️ Quick Start in 5 Steps</h3><ol><li><strong>Install via Composer</strong></li></ol><pre>composer require itxshakil/laravel-fast2sms</pre><p>2.<strong> Add your API key</strong> in .env</p><pre>FAST2SMS_API_KEY=&quot;YOUR_API_KEY&quot;<br>FAST2SMS_DEFAULT_SENDER_ID=&quot;FSTSMS&quot;<br>FAST2SMS_DEFAULT_ROUTE=&quot;dlt&quot;</pre><p>3. Use in your Controller/Service</p><pre>use Shakil\Fast2sms\Facades\Fast2sms;<br><br>Fast2sms::dlt(<br>    numbers: &#39;9999999999&#39;,<br>    templateId: &#39;YOUR_TEMPLATE_ID&#39;,<br>    variablesValues: [&#39;John Doe&#39;],<br>    senderId: &#39;YOUR_SENDER_ID&#39;<br>);</pre><p>4.<strong> Queue for scale</strong><br> Works perfectly with Laravel’s job + queue system for high-volume apps.</p><p>5.<strong> Done 🎉</strong><br> You’re now sending SMS in Laravel the Laravel way!</p><h3>🌍 Open Source &amp; Community</h3><p>This package is <strong>MIT-licensed and free</strong>.</p><ul><li>📖 GitHub Repo: <a href="https://github.com/itxshakil/laravel-fast2sms">Laravel Fast2SMS</a></li><li>🐛 Bug reports, feature requests, and PRs are always welcome.</li><li>⭐ Star the repo if you find it useful — this helps more Laravel devs discover it!</li></ul><h3>🔮 Roadmap</h3><p>Here’s what’s coming next:</p><ul><li>✅ PHP 8.5 support (once released)</li><li>✅ Webhook handling for delivery receipts</li><li>✅ Pre-built Laravel Notification channel</li></ul><h3>🎯 Final Thoughts</h3><p>If you’re building a Laravel app for Indian users, <strong>SMS is still mission-critical</strong>. Instead of reinventing the wheel with raw APIs, Laravel Fast2SMS lets you:</p><ul><li>Write less code</li><li>Stay compliant with DLT</li><li>Scale with queues</li></ul><p>👉 Give it a try today:</p><pre>composer require itxshakil/laravel-fast2sms</pre><p>And don’t forget to ⭐ the repo — it really helps support open-source development!</p><p>Would you like me to also <strong>create an SEO meta description + Twitter/LinkedIn post draft</strong> for this blog so you can promote it right after publishing?</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=85325aa91a17" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ Which AI Model Should You Use? A Super Simple Guide for Developers]]></title>
            <link>https://medium.com/@itxshakil/which-ai-model-should-you-use-a-super-simple-guide-for-developers-4d231b7cb410?source=rss-78f39e6d28af------2</link>
            <guid isPermaLink="false">https://medium.com/p/4d231b7cb410</guid>
            <category><![CDATA[github-copilot]]></category>
            <category><![CDATA[ai-model]]></category>
            <category><![CDATA[gemini]]></category>
            <category><![CDATA[claude]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Shakil Alam]]></dc:creator>
            <pubDate>Sat, 19 Apr 2025 09:01:45 GMT</pubDate>
            <atom:updated>2025-04-19T09:01:45.276Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong>Spoiler:</strong> You don’t need a PhD to pick the right AI model. Just a few clear pointers — and that’s what this post gives you.</h4><p>If you’ve ever opened GitHub Copilot, ChatGPT, or Claude and thought, <em>“Wait… which model should I even use for this?”</em> — you’re definitely not alone.</p><p>With so many AI options out there (some fast, some smart, some really good at just one thing), it’s easy to get stuck before you even start coding.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3O8_OlrgY7tE_TM84baKtw.png" /></figure><p>This guide breaks it down simply — no fluff, no jargon. Just a clear path to picking the right model for whatever you’re building today.</p><h3>🚀 TL;DR — Your Quick Model Cheat Sheet</h3><ul><li>💳 <strong>Balanced (good performance + cost):</strong> GPT-4o or Claude 3.5 Sonnet</li><li>🪙 <strong>Speed for light tasks:</strong> o3-mini or Claude 3.5 Sonnet</li><li>💎 <strong>Deep thinking + debugging:</strong> GPT-4.5, o1, or Claude 3.7 Sonnet</li><li>🖼️ <strong>Need visual support?</strong> Gemini 2.0 Flash or GPT-4o</li></ul><h3>🏎️ Need Speed? Choose o3-mini</h3><p><strong>Nickname:</strong> <em>The Speed Demon</em> 😈</p><p>Lightweight, fast, and budget-friendly — perfect for getting answers fast.</p><p><strong>✅ Use it for:</strong></p><ul><li>Quick prototyping</li><li>Explaining code snippets</li><li>Learning programming concepts</li><li>Generating boilerplate code</li></ul><p><strong>🚫 Skip it if:</strong><br>You’re working on complex, multi-file tasks or deep reasoning.</p><h3>⚖️ Want Balance? Use GPT-4o or GPT-4.1</h3><p><strong>Nickname:</strong> <em>The Swiss Army Knife</em> 🛠️</p><p>Solid all-around models — fast, flexible, and good with images too.</p><p><strong>✅ Use them for:</strong></p><ul><li>Code explanations</li><li>Writing comments &amp; documentation</li><li>Generating small reusable snippets</li><li>Multilingual prompts</li></ul><p><strong>🚫 Skip them if:</strong><br>You need to dig deep into architectural planning or logic-heavy bugs.</p><h3>😊 On a Budget? Try Claude 3.5 Sonnet</h3><p><strong>Nickname:</strong> <em>The Budget Buddy</em></p><p>Efficient and helpful without breaking the bank.</p><p><strong>✅ Use it for:</strong></p><ul><li>Documentation writing</li><li>Language-specific questions</li><li>General-purpose code snippets</li></ul><p><strong>🚫 Skip it if:</strong><br>You need multi-step reasoning or detailed system design.</p><h3>💭 Solving Tough Problems? GPT-4.5 Is Your Hero</h3><p><strong>Nickname:</strong> <em>The Thinker</em> 🧠</p><p>Great for complex debugging and full-feature planning.</p><p><strong>✅ Use it for:</strong></p><ul><li>Multi-file solutions</li><li>Detailed README generation</li><li>Complex error tracing</li><li>System architecture decisions</li></ul><p><strong>🚫 Skip it if:</strong><br>You’re moving fast and want to conserve credits.</p><h3>🥽 Need Precision? o1 Has Your Back</h3><p><strong>Nickname:</strong> <em>The Deep Diver</em> 🌊</p><p>This one excels at performance tuning and system-level tasks.</p><p><strong>✅ Use it for:</strong></p><ul><li>Code optimization</li><li>Refactoring messy codebases</li><li>Summarizing benchmarks</li><li>Structured function building</li></ul><p><strong>🚫 Skip it if:</strong><br>You’re working on something simple or need rapid feedback.</p><h3>🏠 Building Big? Claude 3.7 Sonnet Is for You</h3><p><strong>Nickname:</strong> <em>The Architect</em> 🏗️</p><p>Perfect for massive projects where context really matters.</p><p><strong>✅ Use it for:</strong></p><ul><li>Refactoring large codebases</li><li>Planning app architecture</li><li>Deep dives with high-level analysis</li></ul><p><strong>🚫 Skip it if:</strong><br>Your project is small or still in early prototyping.</p><h3>🤔 Working with Visuals? Meet Gemini 2.0 Flash</h3><p><strong>Nickname:</strong> <em>The Visual Thinker</em> 🎨</p><p>This model sees images and understands layouts — yes, really.</p><p><strong>✅ Use it for:</strong></p><ul><li>Analyzing mockups</li><li>Debugging UI layout issues</li><li>Generating frontend code from images</li><li>Quick visual feedback</li></ul><p><strong>🚫 Skip it if:</strong><br>You’re doing algorithm-heavy tasks or multi-layer reasoning.</p><h3>🎯 The Golden Rule: Match the Model to the Task</h3><p>You don’t need to memorize specs. Just think:</p><ul><li>💨 Need speed? Go lightweight.</li><li>🧠 Need reasoning? Go deeper.</li><li>🧮 Watching costs? Use Sonnet.</li><li>👁️ Working with images? Think multimodal.</li></ul><p>The more you use different models, the better you’ll get at picking the perfect one for the job.</p><h3>💬 Over to You</h3><p>What’s your go-to model for daily dev work?<br>Have you tried switching between them mid-project?<br>Let me know in the responses — I’d love to see what works for others in the trenches.</p><p>🧠 <strong>Like posts that make AI more human-friendly?</strong><br>Give this one a 👏, share it with your coding buddy, or follow for more practical dev &amp; AI content.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4d231b7cb410" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Be a Senior PHP Developer (Without Faking It)]]></title>
            <link>https://medium.com/@itxshakil/how-to-be-a-senior-php-developer-without-faking-it-75ed9c6154f3?source=rss-78f39e6d28af------2</link>
            <guid isPermaLink="false">https://medium.com/p/75ed9c6154f3</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[laravel]]></category>
            <dc:creator><![CDATA[Shakil Alam]]></dc:creator>
            <pubDate>Tue, 01 Apr 2025 18:16:33 GMT</pubDate>
            <atom:updated>2025-04-01T18:16:33.931Z</atom:updated>
            <content:encoded><![CDATA[<p>So, you’ve been coding in PHP for a while, and you’re wondering, “What does it take to be a senior PHP developer?”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pKm4zOE0ojnEdJkmfCcueA.png" /></figure><p>Sure, you could just wait until you have enough years under your belt, but let’s be real — years don’t equal skill. A senior developer isn’t just someone with experience; it’s someone who knows how to use that experience wisely.</p><p>Here’s what makes the difference.</p><h4>1. Master the Art of Debugging (Not Just Googling Errors)</h4><p>A junior dev sees an error and pastes it into Google. A senior dev knows how to truly debug. This means:</p><ul><li>Understanding stack traces and following them logically.</li><li>Using breakpoints instead of dumping variables everywhere.</li><li>Knowing how to isolate the issue rather than randomly tweaking code.</li></ul><p>Want to level up? Try solving issues <strong>without</strong> searching for them immediately. Dig into the PHP documentation. Challenge yourself.</p><h4>2. Think Architecture, Not Just Code</h4><p>Anyone can write working PHP code. But does your code <strong>scale</strong>? Does it follow SOLID principles? Are you thinking about maintainability?</p><p>Senior devs don’t just focus on “does it work?” but also “will it still work a year from now?” Learn:</p><ul><li>Design patterns (Factory, Singleton, Repository, etc.)</li><li>How to write modular code.</li><li>Why bad architecture leads to technical debt and how to avoid it.</li></ul><h4>3. Become a Database Whisperer</h4><p>A senior PHP dev doesn’t just write queries; they <strong>optimize</strong> them.</p><ul><li>Know when to use indexes and why they matter.</li><li>Avoid N+1 query problems like the plague.</li><li>Learn about database caching, transactions, and normalization.</li><li>Get comfortable with both SQL and NoSQL (sometimes MongoDB is a better fit!).</li></ul><p>Bonus tip: Run `<em>EXPLAIN</em>` on your queries and see how MySQL processes them. You’ll learn a lot!</p><h4>4. Automate Yourself Out of Repetitive Work</h4><p>A good developer writes code. A great developer writes code that writes code.</p><ul><li>Use Composer for dependency management.</li><li>Automate testing (unit, integration, functional) with PHPUnit and Pest.</li><li>Learn bash scripts or tools like Ansible to automate deployments.</li></ul><p>When you find yourself repeating the same task over and over, stop and think: <strong>“Can I automate this?”</strong></p><h4>5. Understand Security Beyond the Basics</h4><p>“Don’t trust user input” isn’t enough. A senior dev knows security vulnerabilities inside and out:</p><ul><li>SQL Injection (and why prepared statements are a must!)</li><li>XSS (Cross-Site Scripting) and how to sanitize inputs correctly.</li><li>CSRF (Cross-Site Request Forgery) and how to prevent it.</li><li>Why exposing stack traces in production is a bad idea.</li></ul><p>Get into the mindset of a hacker — because if you don’t, someone else will.</p><h4>6. Code Reviews: Give and Take Like a Pro</h4><p>A senior dev isn’t just good at writing code, but also at <strong>reviewing</strong> it.</p><ul><li>Instead of just pointing out what’s wrong, explain why.</li><li>Learn to spot performance issues before they happen.</li><li>Encourage best practices without being a code dictator.</li><li>Accept feedback with an open mind (you’re not perfect, and that’s okay!).</li></ul><h4>7. Know When *Not* to Use PHP</h4><p>It sounds weird, but hear me out: just because you <strong>can</strong> do something in PHP doesn’t mean you <strong>should</strong>.</p><ul><li>If you need real-time processing, maybe WebSockets with Node.js is better.</li><li>If you’re building a static site, why not use a headless CMS or a Jamstack approach?</li><li>Sometimes microservices with Go, Rust, or Python make more sense.</li></ul><p>A senior developer knows when PHP is the right tool — and when it’s not.</p><h4>8. Be the Dev Others Want to Work With</h4><p>Technical skills will get you the title, but soft skills will make you a real <strong>senior</strong> dev.</p><ul><li>Mentor juniors instead of gatekeeping knowledge.</li><li>Communicate clearly — whether in meetings, comments, or documentation.</li><li>Take ownership of problems instead of blaming others.</li></ul><h3>Wrapping Up</h3><p>Being a senior PHP developer isn’t just about experience or writing complex code. It’s about solving problems <strong>effectively</strong>, thinking long-term, and making life easier for your future self and your team.</p><p>So, don’t just count your years of experience — <strong>make them count.</strong></p><p>What’s one skill that helped YOU grow as a developer? Drop it in the comments! 👇</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=75ed9c6154f3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Git Checkout vs Git Switch: Why You Need to Break Up With Your Old Habits]]></title>
            <link>https://medium.com/@itxshakil/git-checkout-vs-git-switch-why-you-need-to-break-up-with-your-old-habits-b1a92e818197?source=rss-78f39e6d28af------2</link>
            <guid isPermaLink="false">https://medium.com/p/b1a92e818197</guid>
            <category><![CDATA[git-switch]]></category>
            <category><![CDATA[git-checkout]]></category>
            <category><![CDATA[git-tips]]></category>
            <dc:creator><![CDATA[Shakil Alam]]></dc:creator>
            <pubDate>Sun, 05 Jan 2025 07:44:32 GMT</pubDate>
            <atom:updated>2025-01-05T07:58:13.977Z</atom:updated>
            <content:encoded><![CDATA[<p>Ah, git checkout. The jack-of-all-trades, the Swiss Army knife, the “one command to rule them all” in the Git world. For years, it’s been our go-to for switching branches, restoring files, creating branches, and even detaching HEADs (not exactly). But let’s face it: multitasking isn’t always a good thing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AGoBlLdD3mPlAMswWtvaGg.png" /></figure><p>If you’ve ever found yourself wondering, <em>“Did I just restore a file or switch branches?”</em>, then congratulations — you’ve been a victim of git checkout’s overly ambitious nature.</p><p>That’s why, starting with Git 2.23, the Git wizards blessed us with two new commands: <strong>git switch</strong> and <strong>git restore</strong>. They’re clearer, safer, and designed to simplify your life. In this post, I’ll show you why these commands are the heroes your Git workflow needs—and how to start using them today.</p><h3>Why Did Git Break Up With git checkout?</h3><p>Let’s be honest: git checkout was doing too much. Think of it like a coworker who insists on doing <em>all</em> the tasks but ends up making a mess because they’re stretched too thin.</p><p>Here’s what git checkout used to juggle:</p><ol><li>Switching branches.</li><li>Creating new branches.</li><li>Restoring files.</li><li>Checking out specific commits.</li></ol><p>The result? Ambiguity and a whole lot of human error. Have you ever tried to restore a file but accidentally switched branches? Or detached your HEAD without knowing what just happened? Yep, we’ve all been there.</p><p>Git’s solution was simple: split git checkout into <strong>two specialized commands</strong>:</p><ul><li><strong>git switch</strong> for switching and creating branches.</li><li><strong>git restore</strong> for restoring files or working tree states.</li></ul><p>Now, instead of one multitasking command, we have tools that are purpose-built — and far less likely to make you pull your hair out.</p><h3>git checkout vs git switch: Let’s Settle This</h3><p>Here’s where the magic happens. Let’s compare how these commands stack up in common scenarios.</p><h3>1. Switching Branches</h3><p><strong>The Old Way (</strong><strong>git checkout):</strong></p><pre>git checkout feature-branch</pre><p>Does the job, but it’s easy to accidentally restore files if you’re not careful.</p><p><strong>The New Way (</strong><strong>git switch):</strong></p><pre>git switch feature-branch</pre><p>Straightforward and unambiguous. You’re <em>just</em> switching branches — nothing more, nothing less.</p><h3>2. Creating a New Branch</h3><p><strong>The Old Way (</strong><strong>git checkout):</strong></p><pre>git checkout -b new-branch</pre><p><strong>The New Way (</strong><strong>git switch):</strong></p><pre>git switch -c new-branch</pre><p><strong>Why </strong><strong>git switch is Better:</strong> The -c flag explicitly says, “Hey, I’m creating a branch here!” It’s clear, concise, and reduces confusion.</p><h3>3. Restoring Files</h3><p>This is where git restore truly shines.</p><p><strong>The Old Way (</strong><strong>git checkout):</strong></p><pre>git checkout HEAD~1 myfile.txt</pre><p>Did you just restore a file? Switch branches? Detach your HEAD? Who knows?</p><p><strong>The New Way (</strong><strong>git restore):</strong></p><pre>git restore myfile.txt</pre><p><strong>Why </strong><strong>git restore Wins:</strong> It’s laser-focused. If you’re restoring a file, use git restore. No side effects, no guessing games.</p><h3>Why This Matters for Your Workflow</h3><p>Still not convinced? Let’s talk about why switching to git switch and git restore is more than just a nice-to-have.</p><h3>1. Fewer Mistakes</h3><p>With git switch and git restore, you can’t accidentally restore files when you meant to switch branches—or vice versa. Each command has one job, so there’s less room for error.</p><h3>2. Cleaner Commands</h3><p>Your intent is crystal clear. When you type git switch, everyone knows you’re switching branches. When you type git restore, it’s obvious you’re fixing files. It makes your workflow more readable for both you and your team.</p><h3>3. Future-Proofing</h3><p>Git’s not-so-subtle nudges (“Did you mean git switch?”) are a sign of things to come. git switch and git restore are the future, so it’s better to get comfortable with them now.</p><h3>How to Start Using git switch and git restore</h3><p>Ready to level up your Git game? Here’s how to make the switch (pun intended):</p><h3>1. Update Your Git Version</h3><p>If you’re not seeing these commands, you’re probably running an ancient version of Git. Time to update:</p><pre>sudo apt update &amp;&amp; sudo apt install git  # Linux  <br>brew install git                        # macOS</pre><h3>2. Replace git checkout in Your Workflow</h3><p>Next time you need to switch branches or restore files, use the new commands:</p><pre>git switch main  <br>git switch -c new-branch  <br>git restore myfile.txt</pre><h3>3. Create Aliases for Convenience</h3><p>Save some keystrokes by setting up aliases:</p><pre>git config --global alias.sw switch  <br>git config --global alias.re restore</pre><p>Now you can type git sw and git re like a true Git ninja.</p><h3>Final Thoughts</h3><p>Look, I get it. Change is hard. But using <a href="https://git-scm.com/docs/git-switch">git switch</a> and <a href="https://git-scm.com/docs/git-restore">git restore</a> isn’t just about jumping on a trend—it’s about clarity, efficiency, and future-proofing your workflow.</p><p>So the next time you catch yourself typing <a href="https://git-scm.com/docs/git-checkout">git checkout</a>, stop. Ask yourself: <em>Am I switching branches or restoring files?</em> Then use the right tool for the job.</p><p>Your future self — and your team — will thank you. Now go ahead and give these commands a shot. Who knows, you might even start enjoying Git (well, almost).</p><p>You may also like <a href="https://blog.shakiltech.com/best-practices-for-using-github-issues/">7 Secrets to Writing Perfect GitHub Issues Developers Love</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b1a92e818197" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Form Design & Validation 101: The Ultimate Guide to User-Friendly, Accessible, and Secure Forms]]></title>
            <link>https://medium.com/@itxshakil/form-design-validation-101-the-ultimate-guide-to-user-friendly-accessible-and-secure-forms-218648680229?source=rss-78f39e6d28af------2</link>
            <guid isPermaLink="false">https://medium.com/p/218648680229</guid>
            <category><![CDATA[form-design]]></category>
            <category><![CDATA[form-validation]]></category>
            <dc:creator><![CDATA[Shakil Alam]]></dc:creator>
            <pubDate>Tue, 19 Nov 2024 15:46:42 GMT</pubDate>
            <atom:updated>2024-11-19T15:46:42.512Z</atom:updated>
            <content:encoded><![CDATA[<p>Forms are the unsung heroes of the web. Whether you’re filling out a sign-up sheet for your favorite social platform, making a purchase, or submitting a contact form, forms are the gateway through which we interact with most websites and applications. But here’s the thing — great forms can make or break the user experience.</p><p>We’ve all experienced the frustration of filling out a lengthy form, only to be hit with error messages that make no sense, or worse, a form that’s impossible to complete because it isn’t optimized for mobile devices. It’s frustrating. It’s time-consuming. And in many cases, it leads to form abandonment. According to statistics, <strong>about 70% of people abandon forms halfway through</strong> if they’re too difficult to fill out or unclear. So, creating user-friendly, secure, and accessible forms isn’t just important; it’s critical.</p><p>In this guide, we’re going to dive deep into every aspect of form design — from crafting intuitive user interfaces to implementing top-notch security and validation. Whether you’re designing forms for a personal project, a business website, or a full-fledged web application, this guide will give you the tools to create forms that are not only functional but a breeze for users to navigate. Think of it as your ultimate handbook for building better, more secure, and user-friendly forms.</p><h3>What Makes a Great Form?</h3><p>When we talk about great forms, we’re not just talking about collecting data. We’re talking about an experience. An experience that makes users feel like they are in control, that their data is secure, and that they’re not wasting time.</p><p>A great form achieves four key goals:</p><ul><li><strong>Usability</strong>: It’s easy to use. It doesn’t overwhelm the user with unnecessary information.</li><li><strong>Accessibility</strong>: It’s usable by everyone, including people with disabilities.</li><li><strong>Security</strong>: It keeps user data safe and prevents malicious attacks.</li><li><strong>Validation</strong>: It ensures the data collected is correct, complete, and properly formatted.</li></ul><p>These four pillars work together to make forms that serve their purpose without frustrating the user. Let’s break down how these pillars contribute to building better forms.</p><h3>Part 1: Designing User-Friendly and Accessible Forms</h3><p>Forms should be designed with the user in mind. The goal is to make the process as smooth as possible, guiding users through the form without frustration. That means thinking about <strong>user-centered design principles</strong>, <strong>accessibility</strong>, and ensuring your forms are <strong>mobile-friendly</strong>.</p><h3>User-Centered Design Principles</h3><p>When designing forms, it’s essential to put yourself in the user’s shoes. How would you want to fill out the form if you were them? Here are some principles that can make a world of difference:</p><h4>Simplify Form Fields</h4><p>Less is more. Only ask for essential information. A form should be as short as possible. Lengthy forms scare people away, and that’s why you’ll often see forms with just a name, email, and a submit button on landing pages. But how do you know what’s essential?</p><ul><li><strong>Ask only for what’s necessary</strong>: If your goal is to create a user account, don’t ask for the user’s phone number, address, or social media handles unless it’s truly necessary. Keep it simple!</li><li><strong>Use Progressive Disclosure</strong>: If you need additional information, ask for it later in the process or after a user completes a basic form.</li></ul><h4>Group Related Fields Together Logically</h4><p>A great form follows a logical progression. Grouping related fields together helps users quickly understand what’s being asked and why. For example, if you’re collecting a home address, group fields for street address, city, state, and ZIP code together.</p><ul><li><strong>For example</strong>: If you’re creating a billing form, place all payment information — credit card number, expiry date, and CVV — together.</li><li><strong>Use visual groupings</strong>: Use visual elements like boxes, separators, and headings to group similar fields.</li></ul><h4>Use Clear Labels, Instructions, and Tooltips</h4><p>Every input field should have a label that clearly explains what the user is expected to input. It’s essential to use <strong>clear and descriptive language</strong>.</p><ul><li><strong>Example</strong>: For a date field, instead of a vague label like “Date,” use something like “Enter your birth date (MM/DD/YYYY).” This way, users will instantly know what format is required.</li><li><strong>Tooltips</strong>: Small informational icons next to fields can offer additional help without cluttering the interface.</li></ul><h4>Apply Consistent Visual Styles</h4><p>Users appreciate consistency. The more consistent your design is across your forms, the easier it will be for users to follow. This includes colors, fonts, input sizes, and button styles.</p><p><strong>Consistency tips</strong>:</p><ul><li><strong>Use the same font sizes and colors</strong> throughout your form fields and labels.</li><li><strong>Design buttons that are easy to tap on</strong> mobile devices by using larger sizes and clear text.</li></ul><p>By applying these principles, your form becomes easier and more intuitive for users to navigate, reducing the chances of abandonment.</p><h3>Accessibility Best Practices</h3><p>If we truly want to build forms for everyone, <strong>accessibility</strong> is a must. Forms should work for all users, including those with disabilities. Let’s dive into some essential accessibility practices.</p><h4>Ensure Proper Contrast Between Text and Background</h4><p>A common mistake is using light text on a light background or dark text on a dark background. This makes the text hard to read for people with visual impairments.</p><ul><li><strong>Best practice</strong>: Use high-contrast color schemes like dark text on light backgrounds or vice versa. This will improve readability for everyone.</li></ul><p>Required standards for WCAG levels:</p><ul><li><strong>AA:</strong> Minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text (18pt or 14pt bold).</li><li><strong>AAA:</strong> Higher standard, requiring a ratio of 7:1 for normal text and 4.5:1 for large text.</li></ul><p>Modern browser dev tools help you view and achieve these standard levels.</p><h4>Use Labels, ARIA Attributes, and Appropriate HTML Tags</h4><p>Semantic HTML is crucial for accessibility. Using proper tags like &lt;label&gt;, &lt;fieldset&gt;, and &lt;legend&gt; makes it easier for screen readers to interpret and communicate the form’s structure to users with disabilities.</p><ul><li><strong>Example</strong>: A form field for entering a phone number should be wrapped in a &lt;label&gt; tag:</li></ul><pre>&lt;label for=&quot;phone&quot;&gt;Phone Number:&lt;/label&gt;<br>&lt;input type=&quot;tel&quot; id=&quot;phone&quot; name=&quot;phone&quot;&gt;</pre><ul><li><strong>ARIA Attributes</strong>: ARIA (Accessible Rich Internet Applications) roles and attributes provide additional context to screen readers, helping people with disabilities navigate more easily. For example, use aria-required=&quot;true&quot; fields that are mandatory to fill out.</li></ul><h4>Provide Keyboard Navigation and Focus Management</h4><p>Forms must be navigable using only the keyboard. Users with motor disabilities may rely on keyboard navigation rather than a mouse.</p><ul><li><strong>Focus management</strong>: When a user moves between fields, ensure that the focus is clearly indicated with a visible border or highlight. Focus should also move in a logical order (i.e., from top to bottom).</li><li><strong>Tab order</strong>: Ensure that pressing the <strong>Tab</strong> key allows users to jump to the next field logically.</li></ul><h4>Use aria-live Regions for Dynamic Feedback</h4><p>Sometimes, forms show error messages or success messages dynamically. It’s important that screen readers can detect these changes, so users know when something goes wrong or right.</p><ul><li><strong>Example</strong>: If a form validation message appears after submission, you can use the aria-live attribute to notify screen readers of the new message:</li></ul><pre>&lt;div aria-live=&quot;assertive&quot; id=&quot;error-message&quot;&gt;&lt;/div&gt;</pre><p>By following these best practices, your forms will be usable by a broader audience, including those with visual, auditory, or motor impairments.</p><h3>Mobile-Friendly Form Design</h3><p>With more than <strong>half of all web traffic coming from mobile devices</strong>, optimizing forms for mobile is no longer optional. Let’s break down some mobile-friendly design strategies that ensure your forms work seamlessly on all devices.</p><h4>Design for Smaller Screens</h4><p>Mobile forms should have <strong>larger input fields</strong> and <strong>touch-friendly buttons</strong> to make it easy for users to tap and type on smaller screens.</p><ul><li><strong>Input fields</strong>: Use larger text boxes and increase the height of fields. This helps users focus on what they are entering.</li><li><strong>Buttons</strong>: Make buttons larger (at least 48x48 pixels) to ensure they are tappable. Include plenty of space between buttons to avoid accidental clicks.</li></ul><h4>Optimize Input Types</h4><p>Mobile browsers offer special input types like email, tel, number, and date, which activates the correct keyboard for easier input.</p><ul><li><strong>For example</strong>: Use &lt;input type=&quot;email&quot;&gt; instead of &lt;input type=&quot;text&quot;&gt; for email fields. This brings up the email keyboard, making it easier for users to enter their email addresses.</li></ul><h4>Ensure Responsiveness</h4><p>A responsive design ensures that your form adapts to different screen sizes, from desktops to smartphones and everything in between.</p><ul><li><strong>Media Queries</strong>: Use CSS media queries to adjust the layout depending on the screen width. This can involve changing the layout from a two-column form on larger screens to a single column on mobile devices.</li></ul><pre>@media screen and (max-width: 600px) {<br>  .form-container {<br>    flex-direction: column;<br>  }<br>}</pre><p>By following these strategies, your form will be <strong>optimized for all devices</strong>, providing a smooth experience for mobile users.</p><h3>Visual Feedback and States</h3><p>One of the best ways to guide users through a form is through clear, <strong>real-time feedback</strong>. When a user makes a mistake or completes a field, feedback should be immediate and easy to understand.</p><h4>Highlight Fields with Errors</h4><p>It’s important to visually highlight fields that contain errors in a noticeable yet non-intrusive way. This helps users quickly locate the problem.</p><ul><li><strong>Best practice</strong>: When a user makes a mistake in a field, use <strong>red borders</strong> or <strong>icons</strong> to indicate an error. Example:</li></ul><pre>.input-error {<br>  border-color: red;<br>  background-color: #f8d7da;<br>}</pre><p>Add an error icon (e.g., a red exclamation mark) next to the field for an extra layer of visibility as a color-blind person can misunderstand color.</p><h4>Show Success Messages</h4><p>When a user fills out a form field correctly, a small success message or <strong>checkmark icon</strong> can reinforce their progress.</p><ul><li><strong>Example</strong>: A green checkmark next to an email field to confirm that the email address entered is valid:</li></ul><pre>&lt;span class=&quot;checkmark&quot;&gt;&amp;#10003;&lt;/span&gt;</pre><h4>Real-Time Validation</h4><p>Rather than waiting until the user submits the form, consider providing feedback as they type. For instance, you can let users know if their password is strong enough or if their email is valid.</p><h3>Part 2: Security Measures in Form Handling</h3><p>When it comes to handling user input, <strong>security</strong> should be a top priority. Forms are prime targets for attackers who want to inject malicious code, steal sensitive information, or exploit weaknesses in your system. In this section, we’ll go over how to safeguard your forms through proper validation, sanitization, and secure submission methods.</p><h3>Sanitizing and Validating User Input</h3><p>One of the first lines of defense against malicious input is proper validation and <strong>sanitization</strong>. Validation ensures the data is in the correct format, while sanitization removes any potentially harmful content. Together, they protect both the user and the server from unwanted actions.</p><h4>Client-Side Validation: A First Step</h4><p>Client-side validation (typically done with JavaScript) checks user input before it’s sent to the server. While client-side validation can provide a better user experience by giving instant feedback, <strong>it should never be relied upon exclusively</strong>, as it can be bypassed by attackers.</p><p>For example, if a user types an invalid email address, client-side validation can notify them immediately without the need to submit the form.</p><p><strong>Example of Client-Side Validation with JavaScript</strong>:</p><pre>document.querySelector(&#39;form&#39;).addEventListener(&#39;submit&#39;, function(event) {<br>    const email = document.querySelector(&#39;#email&#39;).value;<br>    const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;<br><br>    if (!email.match(emailPattern)) {<br>        alert(&quot;Please enter a valid email address.&quot;);<br>        event.preventDefault();<br>    }<br>});</pre><h4>Server-Side Validation: Always Essential</h4><p>While client-side validation improves user experience, <strong>server-side validation is critical</strong> because it ensures the integrity and safety of the data before it is processed or stored. Server-side validation should mirror client-side checks, but with stricter rules to prevent malicious inputs from sneaking through.</p><ul><li><strong>Example</strong>: If a form collects an email, the server must validate the format before saving it to a database:</li></ul><pre>function validate_email($email) {<br>    $email_pattern = &#39;/^[^\s@]+@[^\s@]+\.[^\s@]+$/&#39;;<br>    if (preg_match($email_pattern, $email)) {<br>        return true;<br>    }<br>    return false;<br>}<br><br>?&gt;</pre><p>By validating on both the client and server sides, you create a strong layer of protection that ensures the data entered is valid before it’s used.</p><h3>Preventing Cross-Site Scripting (XSS) and SQL Injection</h3><p><strong>Cross-Site Scripting (XSS)</strong> and <strong>SQL Injection</strong> are two of the most common and dangerous security vulnerabilities. Let’s explore how to prevent them in your forms.</p><h4>Preventing XSS (Cross-Site Scripting)</h4><p>XSS occurs when an attacker injects malicious scripts into your website through a form. To prevent this, never trust user input and always sanitize it before displaying it on your pages.</p><ul><li><strong>Sanitize Input</strong>: Use libraries like <strong>DOMPurify</strong> to remove malicious scripts from user inputs before displaying them on your page.</li><li><strong>Escaping Output</strong>: Always escape special characters in user input before displaying it to prevent it from being executed as code. For instance, when displaying a user comment, ensure any HTML characters are escaped so they don’t render as HTML or JavaScript.</li></ul><p><strong>Example of XSS Prevention with DOMPurify</strong>:</p><pre>let sanitizedInput = DOMPurify.sanitize(userInput);<br>document.querySelector(&#39;#output&#39;).innerHTML = sanitizedInput;</pre><h4>Preventing SQL Injection</h4><p>SQL injection is a method where attackers insert or manipulate SQL queries to execute malicious commands. To prevent this, <strong>always use parameterized queries</strong> instead of directly embedding user inputs into SQL queries.</p><ul><li><strong>Parameterized Queries</strong>: These queries treat user input as values rather than executable code. This prevents SQL injection by ensuring user input isn’t executed as part of the query.</li></ul><p><strong>Example of Parameterized Query in PHP</strong>:</p><pre>$stmt = $pdo-&gt;prepare(&#39;SELECT * FROM users WHERE email = :email&#39;);<br>$stmt-&gt;execute([&#39;email&#39; =&gt; $userEmail]);</pre><h3>Securing Form Submissions</h3><p>Securing form submissions is crucial in ensuring that data sent by users is protected. Without proper encryption, form data could be intercepted during transmission, leading to potential data theft or tampering.</p><h4>Use HTTPS for Encryption</h4><p>Always use <strong>HTTPS (Hypertext Transfer Protocol Secure)</strong> for transmitting form data. HTTPS encrypts the data between the client (user) and the server, ensuring that any sensitive information, such as passwords or credit card numbers, is not exposed.</p><ul><li><strong>How to implement</strong>: Ensure that your website has a valid SSL certificate, and configure your server to use HTTPS for all form submissions.</li></ul><h4>Cross-Site Request Forgery (CSRF) Protection</h4><p>Cross-Site Request Forgery (CSRF) is an attack that tricks users into submitting forms without their knowledge. To prevent this, you should include a unique <strong>CSRF token</strong> in each form that gets verified with the server.</p><ul><li><strong>Example of CSRF Token Implementation</strong>:</li></ul><pre>&lt;input type=&quot;hidden&quot; name=&quot;csrf_token&quot; value=&quot;&lt;?php echo generateCSRFToken(); ?&gt;&quot;&gt;</pre><ul><li>On the server, check the CSRF token for validity before processing the form.</li></ul><h4>Implement CAPTCHA/ reCAPTCHA</h4><p>CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) is used to ensure that a form is being submitted by a human and not an automated bot. Google’s <strong>reCAPTCHA</strong> is one of the most popular services used to prevent spam and abuse.</p><ul><li><strong>Example of Google reCAPTCHA Integration</strong>:</li></ul><pre>&lt;div class=&quot;g-recaptcha&quot; data-sitekey=&quot;your-site-key&quot;&gt;&lt;/div&gt;</pre><p>Once the form is submitted, the server verifies the reCAPTCHA response before processing the data.</p><h3>Storing Sensitive Data Securely</h3><p>Storing sensitive data requires extra attention to ensure that it’s stored securely and can’t be accessed by unauthorized parties.</p><h4>Avoid Storing Plain Text Passwords</h4><p>Never store passwords in plain text. Use strong hashing algorithms like <strong>bcrypt</strong> to hash passwords before storing them in the database. This ensures that even if your database is compromised, attackers cannot easily access user passwords.</p><ul><li><strong>Example of Password Hashing with bcrypt</strong>:</li></ul><pre>import bcrypt<br><br>password = &quot;user_password&quot;<br>hashed_password = bcrypt.hashpw(password.encode(&#39;utf-8&#39;), bcrypt.gensalt())</pre><h4>Encryption for Sensitive Data</h4><p>For other sensitive information, such as credit card numbers, use <strong>encryption</strong> before storing it. Ensure that your encryption keys are stored securely, and never hardcode them in your source code.</p><ul><li><strong>Example of Encrypting Data with AES</strong>:</li></ul><pre>from Crypto.Cipher import AES<br>from Crypto.Util.Padding import pad, unpad<br>from Crypto.Random import get_random_bytes<br><br>data = b&quot;Sensitive Data&quot;<br>key = get_random_bytes(16)<br>cipher = AES.new(key, AES.MODE_CBC)<br>ciphertext = cipher.encrypt(pad(data, AES.block_size))</pre><h4>Follow PCI DSS Standards for Payment Data</h4><p>If your form collects payment details, you must follow the <strong>PCI DSS (Payment Card Industry Data Security Standard)</strong> guidelines to ensure that cardholder data is handled securely. This includes encrypting payment information both in transit and at rest, as well as regularly testing your system for vulnerabilities.</p><h3>Part 3: Validation: A Detailed Guide</h3><p>Now, let’s dig deeper into validation — arguably one of the most important aspects of form design. Proper validation ensures that users submit correct, well-formatted data while preventing malicious inputs.</p><h3>HTML5 Built-In Validation</h3><p>HTML5 introduced built-in validation attributes that can handle many common validation cases without requiring JavaScript. These attributes allow you to enforce rules for fields like required fields, email addresses, passwords, and more.</p><h4>HTML5 Validation Attributes</h4><p>Here are some of the most useful HTML5 attributes you can use to validate form fields:</p><ul><li>required: Ensures that the field must be filled out before submission.</li><li>minlength and maxlength: Specifies the minimum and maximum length of the input.</li><li>pattern: Defines a regular expression that the input must match (e.g., for phone numbers or custom formats).</li><li>type: Ensures that the input is in the correct format (e.g., email, url, tel).</li></ul><p><strong>Example</strong>:</p><pre>&lt;form&gt;<br>    &lt;label for=&quot;email&quot;&gt;Email:&lt;/label&gt;<br>    &lt;input type=&quot;email&quot; id=&quot;email&quot; name=&quot;email&quot; required&gt;<br>    &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;<br>&lt;/form&gt;</pre><p>In this case, the browser will automatically validate the email address format when the user tries to submit the form.</p><h3>When to Validate: Timing and Control</h3><p>One key consideration is <strong>when</strong> to trigger validation. Do you want to validate after each input field, as the user types, or only when the user submits the form? Timing and control can significantly impact the user experience.</p><h4>Validation Timing</h4><ul><li><strong>On Blur</strong>: When a user leaves a field, you can trigger validation to immediately notify them of any errors.</li><li><strong>On Input</strong>: Validation triggered while the user is typing can provide <strong>real-time feedback</strong>, which can be useful for things like password strength or confirming email formats.</li><li><strong>On Submit</strong>: The most common method is to validate all fields when the user submits the form. This is typically the last check before the data is sent to the server.</li></ul><h4>Controlling Validation</h4><p>JavaScript gives you full control over when validation happens. You can use checkValidity() or reportValidity() to manually trigger validation on specific fields or the entire form.</p><p><strong>Example of Manual Validation Trigger</strong>:</p><pre>const form = document.querySelector(&#39;form&#39;);<br>form.addEventListener(&#39;submit&#39;, function(event) {<br>    if (!form.checkValidity()) {<br>        event.preventDefault(); // Prevent submission if invalid<br>    }<br>});</pre><h3>Custom Validation with the Constraint Validation API</h3><p>If built-in HTML5 validation isn’t enough, you can implement custom validation using JavaScript’s <a href="https://medium.com/@itxshakil/the-definitive-guide-to-the-constraint-validation-api-04f5c57f42dd"><strong>Constraint Validation API</strong></a>. This API provides methods like setCustomValidity() to create custom error messages and logic.</p><h4>Using setCustomValidity()</h4><p>For more advanced validation logic, you can use setCustomValidity() to define a custom error message that will appear if the input doesn’t meet your criteria.</p><p><strong>Example of Custom Validation</strong>:</p><pre>const emailInput = document.querySelector(&#39;#email&#39;);<br>emailInput.addEventListener(&#39;input&#39;, function() {<br>    if (emailInput.value.length &lt; 5) {<br>        emailInput.setCustomValidity(&quot;Email must be at least 5 characters long.&quot;);<br>    } else {<br>        emailInput.setCustomValidity(&#39;&#39;);<br>    }<br>});</pre><p>This ensures that the email field will not be submitted unless it meets your custom length condition.</p><h3>Crafting Meaningful Error Messages</h3><p>One of the most overlooked aspects of form design is crafting helpful, clear, and meaningful <strong>error messages</strong>. When users encounter an error, the message should tell them exactly what went wrong and how to fix it.</p><h4>Error Message Best Practices</h4><ul><li><strong>Be Specific</strong>: Instead of just saying “Invalid input,” be specific. For example, “Please enter a valid email address” or “Password must be at least 8 characters long.”</li><li><strong>Avoid Blame</strong>: Never use messages like “You’ve made a mistake” or “Oops! Something went wrong.” Focus on the field that needs attention and guide them on how the user can correct it.</li></ul><h3>Part 4: Backend Handling</h3><p>While front-end validation and security are crucial, <strong>back-end handling</strong> ensures that data is processed correctly and securely once it reaches the server.</p><h3>Server-Side Validation: Always Essential</h3><p>Server-side validation checks user data after it’s submitted before it’s processed or stored. This is <strong>non-negotiable</strong> — you can’t trust client-side validation alone.</p><h4>Example of Server-Side Validation:</h4><pre>if ($_SERVER[&#39;REQUEST_METHOD&#39;] === &#39;POST&#39;) {<br>    $email = $_POST[&#39;email&#39;];<br>    <br>    if (!validate_email($email)) {<br>        echo &quot;Invalid email format.&quot;;<br>        http_response_code(400);<br>        exit;<br>    }<br><br>    echo &quot;Form submitted successfully!&quot;;<br>}<br><br>function validate_email($email) {<br>    $email_pattern = &#39;/^[^\s@]+@[^\s@]+\.[^\s@]+$/&#39;;<br>    return preg_match($email_pattern, $email);<br>}</pre><h3>Handling Form Data in the Backend</h3><p>When a user submits a form, the server needs to <strong>sanitize</strong> and <strong>validate</strong> the data before processing it. This ensures that harmful data doesn’t reach your system and that it’s stored properly.</p><h4>Example of Form Data Processing:</h4><pre>function process_form_data($form_data) {<br>    // Sanitize inputs<br>    $sanitized_email = sanitize_email($form_data[&#39;email&#39;]);<br>    <br>    // Insert into database<br>    $db = new PDO(&#39;mysql:host=localhost;dbname=your_database&#39;, &#39;username&#39;, &#39;password&#39;);<br>    $stmt = $db-&gt;prepare(&quot;INSERT INTO users (email) VALUES (:email)&quot;);<br>    $stmt-&gt;bindParam(&#39;:email&#39;, $sanitized_email, PDO::PARAM_STR);<br>    $stmt-&gt;execute();<br>}<br><br>function sanitize_email($email) {<br>    // Sanitize email (simple example, consider using a more robust method for real applications)<br>    return filter_var($email, FILTER_SANITIZE_EMAIL);<br>}</pre><h3>Storing Form Data Securely</h3><p>Sensitive form data should always be <strong>encrypted</strong> and stored securely. This applies to passwords, credit card information, and other private data.</p><h4>Encryption Example:</h4><pre>// Generate a random key<br>$key = base64_encode(random_bytes(32));<br><br>// Encrypt the data<br>$cipher_text = encrypt_data(&quot;My sensitive data&quot;, $key);<br><br>echo &quot;Encrypted Text: &quot; . $cipher_text . &quot;\n&quot;;<br><br>function encrypt_data($data, $key) {<br>    // Create a cipher using the key<br>    $cipher = &quot;aes-256-cbc&quot;;<br>    <br>    // Generate an initialization vector (IV)<br>    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));<br><br>    // Encrypt the data<br>    $encrypted = openssl_encrypt($data, $cipher, base64_decode($key), 0, $iv);<br><br>    // Encode the IV and encrypted data together for storage or transmission<br>    return base64_encode($iv . $encrypted);<br>}</pre><h3>Part 5: Advanced Form Handling Features</h3><p>As we move beyond the basics of form validation and security, let’s explore more advanced and intricate features that can elevate your form-handling game. These include handling files, preventing double submissions, improving user interactions, and ensuring forms are resilient against common issues like spam or unexpected behavior.</p><h3>Handling File Uploads</h3><p>Forms are often used to collect not just text data but also files like images, documents, and other media. When accepting file uploads, it’s crucial to enforce <strong>validation rules</strong> regarding the <strong>file type</strong>, <strong>size</strong>, and other attributes to ensure they meet the expectations and security standards.</p><h4>File Type Checking</h4><p>Allowing users to upload files comes with its risks. You must ensure that the file type is appropriate and safe. For example, only allow image files if your form is for profile pictures.</p><ul><li><strong>MIME Type Verification</strong>: Ensure the file’s MIME type matches the expected type. For instance, if you’re accepting images, ensure the MIME type is something like image/jpeg, image/png, etc.</li></ul><p><strong>Example: File Type Check-in JavaScript</strong>:</p><pre>document.querySelector(&#39;form&#39;).addEventListener(&#39;submit&#39;, function(event) {<br>    const file = document.querySelector(&#39;#fileInput&#39;).files[0];<br>    const allowedTypes = [&#39;image/jpeg&#39;, &#39;image/png&#39;, &#39;image/gif&#39;];<br>    if (!allowedTypes.includes(file.type)) {<br>        alert(&quot;Please upload a valid image (JPEG, PNG, GIF).&quot;);<br>        event.preventDefault();<br>    }<br>});</pre><h4>File Size Validation</h4><p>Another key concern is <strong>file size</strong>. A user might try to upload a file that is too large, which can burden your server and lead to performance issues.</p><ul><li><strong>Maximum Size Check</strong>: Limit the size of uploaded files to avoid overwhelming your server. For instance, you might restrict image uploads to 5MB.</li></ul><p><strong>Example: File Size Check in JavaScript</strong>:</p><pre>document.querySelector(&#39;form&#39;).addEventListener(&#39;submit&#39;, function(event) {<br>    const file = document.querySelector(&#39;#fileInput&#39;).files[0];<br>    const maxSize = 5 * 1024 * 1024; // 5MB<br>    if (file.size &gt; maxSize) {<br>        alert(&quot;File is too large. Please upload a file under 5MB.&quot;);<br>        event.preventDefault();<br>    }<br>});</pre><h4>Renaming Files to Avoid Conflicts</h4><p>To ensure that file names don’t collide on the server, it’s a good practice to <strong>rename</strong> uploaded files before saving them.</p><ul><li><strong>Rename Uploaded Files</strong>: You can rename the files using a timestamp or a unique identifier (e.g., user ID).</li></ul><p><strong>Example: Renaming Files on Server-Side (PHP)</strong>:</p><pre>$targetDir = &quot;uploads/&quot;;<br>$uniqueName = uniqid(&#39;upload_&#39;, true) . &quot;.&quot; . pathinfo($_FILES[&#39;file&#39;][&#39;name&#39;], PATHINFO_EXTENSION);<br>$targetFile = $targetDir . $uniqueName;<br>move_uploaded_file($_FILES[&#39;file&#39;][&#39;tmp_name&#39;], $targetFile);</pre><p>This ensures that even if two users upload files with the same name, they won’t overwrite each other on the server.</p><ul><li><strong>Remove Special Characters: </strong>Remove or escape any potentially harmful characters to prevent security vulnerabilities like path traversal attacks.</li><li><strong>Organizing Files in Meaningful Folders:</strong> Organizing uploaded files into a structured and meaningful folder hierarchy can improve file management, enhance scalability, and make it easier to retrieve specific files.</li><li><strong>Store Files Outside the Web Root: </strong>Place uploaded files in directories that are not directly accessible via the web. This prevents users from executing uploaded scripts by navigating directly to the file URL.</li><li><strong>Implement Content Security Policies (CSP): </strong>Define CSP headers to restrict how content is loaded and executed on your website, preventing malicious scripts from running even if a malicious file is somehow uploaded.</li><li><strong>Implement Rate Limiting:</strong> Prevent users from uploading an excessive number of files in a short period, which can be a sign of automated attacks.</li></ul><h3>Preventing Double Submissions</h3><p>A common issue with forms, especially ones that involve longer processes (like payment or account creation), is <strong>double submissions</strong>. Double submissions occur when a user accidentally submits the form more than once, which can lead to duplicate data, transaction failures, or user confusion.</p><h4>Disabling the Submit Button After Clicking</h4><p>One of the most effective ways to prevent double submissions is by disabling the submit button once it’s clicked. This prevents users from resubmitting the form while it’s still processing.</p><ul><li><strong>JavaScript Example</strong>: Disable the submit button after the first click:</li></ul><pre>document.querySelector(&#39;form&#39;).addEventListener(&#39;submit&#39;, function(event) {<br>    const submitButton = document.querySelector(&#39;#submitButton&#39;);<br>    submitButton.disabled = true;<br>});</pre><h4>Server-Side Prevention with Tokens</h4><p>Another way to prevent double submissions is to generate a <strong>unique submission token</strong> that’s validated on the server side. This ensures that the form cannot be resubmitted with the same token.</p><ul><li><strong>Generate and Verify Token (PHP Example)</strong>:</li></ul><pre>session_start();<br>if ($_SERVER[&#39;REQUEST_METHOD&#39;] == &#39;POST&#39;) {<br>    if ($_SESSION[&#39;token&#39;] !== $_POST[&#39;token&#39;]) {<br>        die(&quot;Invalid form submission.&quot;);<br>    }<br>    // Process the form data<br>}<br>$_SESSION[&#39;token&#39;] = bin2hex(random_bytes(32));</pre><p>By using a <strong>CSRF token</strong>, you ensure that a form submission is unique and not duplicated.</p><h3>Spam Protection: Using Temporary Email and CAPTCHA</h3><p>Forms are often targeted by bots for <strong>spam</strong> submissions. This is particularly common in comment sections or user registration forms. You can combat this by introducing <strong>temporary email protection</strong> and integrating <strong>CAPTCHA</strong> mechanisms.</p><h4>Temporary Email Protection</h4><p>To prevent spam, you can block or filter out <strong>temporary email addresses</strong> from services like <strong>10 Minute Mail</strong> or <strong>Guerrilla Mail</strong>. These services provide disposable email addresses for users who want to stay anonymous or are just trying to bypass registration systems.</p><ul><li><strong>Check for Disposable Emails</strong>: You can use APIs or databases to validate email addresses and reject disposable ones.</li></ul><p><strong>Example</strong>: A third-party service or database to check for temporary emails:</p><pre>const tempEmailDomains = [&quot;tempmail.com&quot;, &quot;guerrillamail.com&quot;];<br>function validateEmail(email) {<br>    const domain = email.split(&#39;@&#39;)[1];<br>    if (tempEmailDomains.includes(domain)) {<br>        alert(&quot;Temporary email addresses are not allowed.&quot;);<br>        return false;<br>    }<br>    return true;<br>}</pre><h4>Integrating CAPTCHA/ reCAPTCHA</h4><p><strong>CAPTCHA</strong> and <strong>Google’s reCAPTCHA</strong> help prevent bots from submitting forms. Implementing reCAPTCHA can significantly reduce spam submissions and ensure that your form is only filled out by real users.</p><ul><li><strong>reCAPTCHA v2 Integration Example</strong>:</li></ul><pre>&lt;div class=&quot;g-recaptcha&quot; data-sitekey=&quot;your-site-key&quot;&gt;&lt;/div&gt;</pre><p>This will add a challenge where users need to prove they’re human by solving a puzzle or clicking a checkbox. Once the form is submitted, you can validate the reCAPTCHA response on the server.</p><h3>Improving User Experience with Meaningful CTA and Progress Indicators</h3><p>To make sure users know what’s happening after they submit a form, it’s important to provide clear calls to action (CTAs) and informative <strong>progress indicators</strong>.</p><h4>Clear CTAs and Confirmation</h4><p>A <strong>clear CTA</strong> tells users exactly what will happen when they click the button. For example, instead of a generic “Submit,” try using “Create My Account” or “Place Order.”</p><p><strong>Example CTA Design</strong>:</p><pre>&lt;button type=&quot;submit&quot; class=&quot;cta-button&quot;&gt;Place Order&lt;/button&gt;</pre><p>Once the form is submitted, a confirmation message or page should provide reassurance that the submission was successful. Consider showing a <strong>thank you message</strong>, or, if it’s an account creation form, a link to <strong>check their email</strong> for activation.</p><h3>Storing Sensitive Information Properly</h3><p>For forms that handle sensitive data — like <strong>credit card numbers</strong> or <strong>personal information</strong> — you need to adhere to strict <strong>security standards</strong> for data storage.</p><h4>PCI DSS Compliance for Payment Details</h4><p>For forms that involve <strong>payment processing</strong>, you must follow the <strong>Payment Card Industry Data Security Standard (PCI DSS)</strong>. PCI DSS mandates how payment data should be encrypted, stored, and transmitted to avoid credit card fraud.</p><ul><li><strong>PCI DSS Encryption Standards</strong>: Use industry-standard encryption protocols like <strong>TLS</strong> for transmitting payment data and <strong>AES-256</strong> for storing it.</li><li><strong>Tokenization</strong>: Rather than storing card data directly, use <strong>tokenization</strong>, which replaces sensitive data with a non-sensitive equivalent (a token) that can’t be used without authorization.</li></ul><h3>Common Issues with Forms and Solutions</h3><p>Even with all the best practices in place, there are still common issues users face when interacting with forms. Let’s look at a few frequent problems and how to fix them.</p><h4>Issue 1: Forms Not Submitting Due to Validation Errors</h4><p>This is one of the most common issues users face. It typically happens because either the <strong>client-side or server-side validation</strong> is too restrictive or not working properly.</p><ul><li><strong>Solution</strong>: Double-check validation rules and ensure that error messages are clear. Implement both client-side and server-side validation and test for edge cases.</li></ul><h4>Issue 2: Inconsistent or Missing Error Messages</h4><p>Users might submit a form, but if there’s no clear feedback, they might not know what went wrong. Missing or unclear error messages can confuse users and lead to frustration.</p><ul><li><strong>Solution</strong>: Use <strong>consistent and meaningful error messages</strong> that clearly describe what the user needs to correct. For example, instead of saying <em>“Invalid username,”</em> say <em>“Your username must be at least 5 characters long.”</em></li><li>Additionally, visually highlight the problematic field and bring focus to it. This becomes especially critical in multi-step forms, where users might not even see errors that occurred in previous steps.</li></ul><p>A combination of error messages and field-specific visual cues ensures users can quickly identify and resolve issues, improving their overall experience.</p><h4>Issue 3: Forms Timing Out</h4><p>If your forms are taking too long to submit (especially with file uploads), it could lead to timeouts or failure to submit.</p><ul><li><strong>Solution</strong>: Optimize your server’s processing time by compressing images, reducing the size of uploaded files, and improving database queries.</li><li><strong>Handle Time-Consuming Tasks in the Background:</strong> Offload lengthy operations like sending emails to run after responding to the user.</li><li><strong>Provide Feedback with Loading Indicators: </strong>Users should never be left wondering if their submission is being processed. Use progress bars or loading spinners for large uploads.</li></ul><h3>Conclusion</h3><p>Forms are an integral part of web development, whether they are used for contact, registration, feedback, or payment. Designing, validating, securing, and managing forms efficiently ensures a positive user experience and protects against malicious attacks.</p><h4>Recap of Key Takeaways:</h4><ol><li><strong>Designing Forms</strong>: Keep it simple and user-friendly, and ensure the layout is clean and the field names are intuitive.</li><li><strong>Validating Forms</strong>: Always validate data on both the client and server sides to prevent erroneous or malicious data.</li><li><strong>Securing Forms</strong>: Use encryption, and tokens, and avoid storing sensitive data unless necessary.</li><li><strong>Improving User Experience</strong>: Provide meaningful error messages, and clear CTAs, and allow for smooth submission processes.</li></ol><h4>Encouraging Continuous Improvement</h4><p>Form development is an ongoing process. Always test your forms, gather user feedback, and make adjustments to improve functionality, security, and usability.</p><p>By continuously refining and enhancing your forms, you can provide a seamless and secure experience for your users, keeping them engaged and satisfied.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=218648680229" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Supercharge Laravel Workflow with Git Hooks: Automate Migrations and Caching — Only When You Need…]]></title>
            <link>https://medium.com/@itxshakil/supercharge-laravel-workflow-with-git-hooks-automate-migrations-and-caching-only-when-you-need-9cf0261e70ed?source=rss-78f39e6d28af------2</link>
            <guid isPermaLink="false">https://medium.com/p/9cf0261e70ed</guid>
            <category><![CDATA[laravel-automation]]></category>
            <category><![CDATA[laravel-git-hook]]></category>
            <category><![CDATA[git-hooks]]></category>
            <category><![CDATA[github-actions]]></category>
            <dc:creator><![CDATA[Shakil Alam]]></dc:creator>
            <pubDate>Sun, 10 Nov 2024 06:37:26 GMT</pubDate>
            <atom:updated>2024-11-10T06:37:26.575Z</atom:updated>
            <content:encoded><![CDATA[<h3>Supercharge Laravel Workflow with Git Hooks: Automate Migrations and Caching — Only When You Need It</h3><p>If you’re tired of manually running migrations, clearing caches, and recaching configurations every time you git pull or git merge, Git hooks are here to make your life easier. But rather than running every command every time, let’s make this hook smarter, so it only performs these actions when necessary.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Gf8BlMCUKLsQoeUD2-zk0g.jpeg" /><figcaption>Supercharge Laravel Workflow with Git Hooks: Automate Migrations and Caching Only When You Need It</figcaption></figure><p>This post will walk you through setting up a post-merge hook that checks for changes in specific files or folders and only runs migrations, recaches configuration, views, and routes if something’s been updated. Let’s dive in!</p><h3>What Are Git Hooks?</h3><p>Git hooks are scripts that Git runs before or after specific events, like committing or merging code. For our setup, we’ll use the post-merge hook, which runs every time you pull or merge new changes into your local repository. This way, your development environment will always be ready to go without you lifting a finger.</p><h3>Setting Up a Smart Post-Merge Hook</h3><ol><li><strong>Locate or Create the </strong><strong>post-merge File.</strong> Navigate to the .git/hooks directory in your project. If you don’t already see a post-merge file, create one there.</li><li><strong>Write the Script with Conditions for Each Action.</strong> Here’s a smart, optimized script that checks for changes before running commands:</li></ol><pre>#!/bin/bash<br><br>   echo &quot;Running optimized post-merge hook...&quot;<br><br>   # Check for new migration files<br>   if git diff --name-only HEAD@{1} HEAD | grep -qE &#39;^database/migrations/.*\.php&#39;; then<br>       echo &quot;New migrations found. Running database migrations...&quot;<br>       php artisan migrate --force<br>   else<br>       echo &quot;No new migrations found. Skipping migration step.&quot;<br>   fi<br><br>   # Check for changes in config files<br>   if git diff --name-only HEAD@{1} HEAD | grep -qE &#39;^config/.*\.php&#39;; then<br>       echo &quot;Config changes detected. Recaching config...&quot;<br>       php artisan config:cache<br>   else<br>       echo &quot;No config changes detected. Skipping config cache.&quot;<br>   fi<br><br>   # Check for changes in view files<br>   if git diff --name-only HEAD@{1} HEAD | grep -qE &#39;^resources/views/.*\.(php|blade\.php)&#39;; then<br>       echo &quot;View changes detected. Recaching views...&quot;<br>       php artisan view:cache<br>   else<br>       echo &quot;No view changes detected. Skipping view cache.&quot;<br>   fi<br><br>   # Check for changes in route files<br>   if git diff --name-only HEAD@{1} HEAD | grep -qE &#39;^routes/.*\.php&#39;; then<br>       echo &quot;Route changes detected. Recaching routes...&quot;<br>       php artisan route:cache<br>   else<br>       echo &quot;No route changes detected. Skipping route cache.&quot;<br>   fi<br><br>   echo &quot;Optimized post-merge tasks completed!&quot;</pre><h4>Explanation:</h4><ul><li><strong>Database Migrations:</strong> Checks if there are new migration files in database/migrations. If found, it runs php artisan migrate --force to apply changes automatically.</li><li><strong>Config Caching:</strong> Checks for changes in config files and recaches if needed.</li><li><strong>View Caching:</strong> Looks for updates in resources/views to decide whether view caching is necessary.</li><li><strong>Route Caching:</strong> Checks if any files in the routes directory have been modified, and recaches if they have.</li></ul><p>3. <strong>Make Your Script Executable.</strong> Run the following command to allow Git to execute the hook:</p><pre>chmod +x .git/hooks/post-merge</pre><p><strong>4. Test It Out!</strong> Now, whenever you pull or merge changes with updated migration, config, views, or route files, only the necessary tasks will run. This keeps your development environment synced and responsive, without unnecessary processing.</p><p>You can even find the code at <a href="https://gist.github.com/itxshakil/54845e124959bfb70d040964ba0a0eef">https://gist.github.com/itxshakil/54845e124959bfb70d040964ba0a0eef</a></p><h3>Key Takeaways</h3><ul><li><strong>Automate Smartly:</strong> By adding conditions, this Git hook only runs migrations, config caching, view caching, or route caching when needed, saving time and resources.</li><li><strong>Easy to Implement:</strong> Just a few lines of Bash scripting streamline your setup tasks automatically.</li><li><strong>Keep Your Environment in Sync with Minimal Effort:</strong> This hook ensures that your local environment reflects the latest changes without manual intervention.</li></ul><h3>Conclusion</h3><p>Setting up this optimized Git hook is a game-changer, especially for projects with frequent updates. With just a few lines of code, you get a workflow that’s fast, automated, and always up-to-date — no more missed migrations or forgotten caches. Try it out, and let Git take care of the busywork for you!</p><p>Happy coding, and here’s to a smoother, smarter development experience!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9cf0261e70ed" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Definitive Guide to the Constraint Validation API]]></title>
            <link>https://medium.com/@itxshakil/the-definitive-guide-to-the-constraint-validation-api-04f5c57f42dd?source=rss-78f39e6d28af------2</link>
            <guid isPermaLink="false">https://medium.com/p/04f5c57f42dd</guid>
            <category><![CDATA[validation]]></category>
            <category><![CDATA[constraint-validation-api]]></category>
            <category><![CDATA[api-validations]]></category>
            <category><![CDATA[client-side-validation]]></category>
            <dc:creator><![CDATA[Shakil Alam]]></dc:creator>
            <pubDate>Sat, 02 Nov 2024 10:45:44 GMT</pubDate>
            <atom:updated>2024-11-02T10:45:44.648Z</atom:updated>
            <content:encoded><![CDATA[<p>The Definitive Guide to the Constraint Validation API</p><p>Take full control of your form validation with the Constraint Validation API! Say goodbye to generic browser messages and hello to custom, user-friendly error handling. Learn how to create smarter forms with personalized feedback, precise timing, and advanced validation techniques. Discover the power of seamless JavaScript integration and make your forms more intuitive and accessible than ever!</p><p>Form Validation: Enhancing User Experience with the Constraint Validation API</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ghgv2Ca2Q7qeAXn5LHncKw.jpeg" /></figure><p>Form validation is a cornerstone of user-friendly web development. While HTML5 provides basic form validation, the Constraint Validation API offers advanced tools to go beyond the limitations of standard browser validation. With this API, developers gain control over custom messages, timing, error handling, and more — all without relying on extra libraries or backend checks.</p><p>This guide covers everything you need to know to make the most of the Constraint Validation API and build forms that are robust, flexible, and user-friendly.</p><h3>Why Use the Constraint Validation API?</h3><p>HTML5 introduced several form attributes for validation (e.g., <em>required</em>, <em>pattern</em>, <em>type</em>), but browser validation alone can be restrictive. The messages are often generic, and the flow is automatic, making it hard to customize feedback for users.</p><p>The Constraint Validation API addresses these issues by giving you:</p><p>- <strong>Customizable Validation Feedback</strong>: Replace default error messages with specific, friendly prompts.<br>- <strong>Controlled Validation Timing</strong>: Validate inputs only when you want, like on blur or before submission.<br>- <strong>Detailed Error Identification</strong>: Pinpoint specific validation errors for a more intuitive user experience.</p><p>With the Constraint Validation API, you can enforce custom validation rules and improve the overall usability of your forms.</p><p>— -</p><h3><strong>Key Features of the Constraint Validation API</strong></h3><p>The Constraint Validation API is packed with powerful features to control and enhance form validation. Here’s a breakdown of the core components.</p><h4>1. HTML5 Validation Attributes</h4><p>Even before using the API, HTML5 attributes provide a solid foundation for form validation:</p><p>- <em>required</em>: Ensures the field isn’t empty.<br>- <em>type</em>: Controls the input format (e.g., email, URL).<br>- <em>min</em>, <em>max</em>, <em>step</em>: For numeric inputs, these specify acceptable ranges and intervals.<br>- <em>pattern</em>: Defines a regex pattern that the input must match.<br>- <em>maxlength </em>and <em>minlength</em>: Limit the length of the input.</p><p>These attributes offer a quick way to apply essential validation rules that integrate seamlessly with the Constraint Validation API.</p><h4>2. Methods: checkValidity() and reportValidity()</h4><p>The API provides two main methods that are central to validation control:</p><p>- <strong><em>checkValidity()</em></strong>: Checks if all constraints are met, returning <em>true</em> or <em>false</em>.<br>- <strong><em>reportValidity()</em></strong>: Not only checks validity but also displays error messages for any invalid fields.</p><p><strong>Example:</strong></p><pre>const form = document.querySelector(&quot;form&quot;);<br>form.addEventListener(&quot;submit&quot;, (event) =&gt; {<br> if (!form.checkValidity()) {<br> form.reportValidity();<br> event.preventDefault(); // Prevent submission if form is invalid<br> }<br>});</pre><h4>3. Custom Error Messages with setCustomValidity()</h4><p>The <em>setCustomValidity()</em> method allows you to set custom error messages for form fields. This can replace default messages with specific instructions, making it easier for users to understand what’s needed.</p><p><strong>Example:</strong></p><pre>const emailInput = document.querySelector(&quot;input[type=&#39;email&#39;]&quot;);<br>emailInput.addEventListener(&quot;input&quot;, () =&gt; {<br> if (emailInput.validity.typeMismatch) {<br> emailInput.setCustomValidity(&quot;Please enter a valid email address (e.g., name@example.com).&quot;);<br> } else {<br> emailInput.setCustomValidity(&quot;&quot;); // Clear message once valid<br> }<br>});</pre><p>This method is ideal for adding personalized messages that guide users, especially if they’re entering complex data formats.</p><h4>4. The validity Property: Detailed Error Information</h4><p>Each input element in a form has a `validity` property, which is an object that contains specific Boolean values representing different validation states. This property allows you to determine exactly why an input is invalid.</p><p><strong>Key validity properties include:</strong></p><p>- <strong><em>valueMissing</em></strong>: <em>true</em> if the field is required but empty.<br>- <strong><em>typeMismatch</em></strong>: <em>true</em> if the input does not match the specified type (e.g., email format).<br>- <strong><em>patternMismatch</em></strong>: <em>true</em> if the input fails to match the defined regex pattern.<br>- <strong><em>tooShort</em></strong> and <strong><em>tooLong</em></strong>: <em>true</em> if the input length is shorter or longer than the specified limits.</p><p>By utilizing the <em>validity</em> property, you can provide users with specific feedback about their input. For instance:</p><p><strong>Example</strong>:</p><pre>const usernameInput = document.querySelector(&quot;#username&quot;);<br>if (usernameInput.validity.tooShort) {<br> usernameInput.setCustomValidity(&quot;Username must be at least 5 characters long.&quot;);<br>} else {<br> usernameInput.setCustomValidity(&quot;&quot;); // Clear message once valid<br>}j</pre><p>This level of detail enhances user experience by allowing for precise error messages that guide users in correcting their input.</p><h4>5. The willValidate Property</h4><p>The <em>willValidate</em> property is a Boolean that indicates whether the input element is subject to validation when methods like <em>checkValidity()</em> or <em>reportValidity()</em> are invoked. This property is particularly useful in dynamic forms, where you might want to exclude certain fields from validation based on specific conditions.</p><p><strong>Example:</strong></p><pre>const inputField = document.querySelector(&quot;input&quot;);<br>if (inputField.willValidate) {<br> console.log(&quot;This field is set to be validated.&quot;);<br>} else {<br> console.log(&quot;This field will not be validated.&quot;);<br>}</pre><p>By checking the <em>willValidate</em> property, developers can implement custom validation logic more effectively, ensuring that only relevant fields are validated according to the form’s context.</p><p>— -</p><h3>Best Practices for Using the Constraint Validation API</h3><p>- <strong>Start with HTML5 Attributes</strong>: Establish a foundation for validation by using built-in attributes like <em>required</em> and <em>type</em>. Afterward, enhance your forms by incorporating features from the Constraint Validation API as needed.</p><p>- <strong>Add <em>novalidate</em> for Custom Validations</strong>: If you plan to manage validation entirely with JavaScript, add the <em>novalidate </em>attribute to the <em>&lt;form&gt;</em> tag to prevent the browser from displaying default validation messages.</p><p>- <strong>Ensure Accessible Validation</strong>: Utilize <em>aria-live</em> regions to announce validation errors for screen readers, ensuring an inclusive experience for all users.</p><p>- <strong>Craft Meaningful Error Messages</strong>: Avoid generic messages. Use <em>setCustomValidity()</em> to provide clear, actionable, and friendly feedback that helps users understand what is needed.</p><p>- <strong>Use Validation Timing Wisely</strong>: Be mindful of how often you validate inputs to avoid overwhelming users. Utilize <em>checkValidity()</em> at strategic moments, like on blur events, to provide helpful guidance without causing frustration.</p><p>- <strong>Employ Friendly Error Messaging</strong>: Replace technical jargon with simple, friendly language. For example, instead of saying “Invalid email,” consider a message like “Your email is missing ‘@’ — could you add it?”</p><p>— -</p><h3>Additional Reading</h3><p>- Constraint Validation MDN — <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation">https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation</a><br>- Complex Constraint Example: Constraint validation — HTML: HyperText Markup Language | MDN — <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation">https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation</a><br>- Client-Side Form Validation: Learn Web Development | MDN — <a href="https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation">https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation</a><br>- Constraint Validation API Browser Support (97% currently): Can I use… Support tables for HTML5, CSS3, etc. — <a href="https://caniuse.com/">https://caniuse.com</a></p><p>— -</p><h3>Conclusion</h3><p>The Constraint Validation API provides a robust and flexible approach to form validation that surpasses the limitations of default browser validation. By enabling custom error messages, precise error detection, and control over validation timing, this API allows you to create forms that are not only user-friendly but also accessible.</p><p>When your validation requirements extend beyond basic HTML attributes to accommodate complex data needs, the Constraint Validation API is your ideal solution. Leverage its features, and you’ll discover that validating user input can be both efficient and, dare we say, enjoyable!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=04f5c57f42dd" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>