<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aqib Javaid</title>
    <description>The latest articles on DEV Community by Aqib Javaid (@aqib_javaid).</description>
    <link>https://dev.to/aqib_javaid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4145615%2Fbcfee141-e994-4c88-9b3b-0cde889296b2.jpg</url>
      <title>DEV Community: Aqib Javaid</title>
      <link>https://dev.to/aqib_javaid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aqib_javaid"/>
    <language>en</language>
    <item>
      <title>Marketplace Website Development Cost: What to Budget</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Wed, 04 Nov 2026 10:40:25 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/marketplace-website-development-cost-what-to-budget-3me5</link>
      <guid>https://dev.to/aqib_javaid/marketplace-website-development-cost-what-to-budget-3me5</guid>
      <description>&lt;p&gt;If you are planning a marketplace, the first question is almost always the same: what does &lt;strong&gt;marketplace website development cost&lt;/strong&gt;? The honest answer is that the price is driven by a handful of decisions that have nothing to do with how the site looks. Payments between three parties, trust and safety, search, messaging and admin tooling are where the budget goes.&lt;/p&gt;

&lt;p&gt;I'm Aqib Javaid, a senior full-stack engineer, and I've built marketplace-style platforms where money, content and multiple user roles all have to work together. This guide breaks down what actually moves the cost, gives rough planning ranges by scope, and shows where founders overspend. The ranges are planning estimates, not quotes. A real number needs a scoped spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes a marketplace more expensive than a normal website?
&lt;/h2&gt;

&lt;p&gt;A brochure site has one audience. A marketplace has at least three: buyers, sellers (or vendors, or service providers) and you, the operator. Every feature has to be built three times over, once for each side's view and permissions.&lt;/p&gt;

&lt;p&gt;Four things separate a marketplace from a typical web app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Split payments.&lt;/strong&gt; Money goes from buyer to seller, minus your commission, and sometimes with refunds, disputes and payouts on a schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-sided accounts.&lt;/strong&gt; Sellers need onboarding, profiles, listings and dashboards. Buyers need search, favourites, orders and messaging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust and moderation.&lt;/strong&gt; Reviews, reporting, listing approval and admin controls are not optional once real strangers transact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liquidity tooling.&lt;/strong&gt; Search, filters, notifications and email flows that bring both sides back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are exotic, but each one adds weeks, and they interact. That is why a "simple Airbnb for X" is rarely simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much does a marketplace cost at different scopes?
&lt;/h2&gt;

&lt;p&gt;Treat these as rough planning ranges for a senior engineer or small team building a custom product. Agency rates, location and team size will move them in either direction.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;What is included&lt;/th&gt;
&lt;th&gt;Typical planning range&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Validation MVP&lt;/td&gt;
&lt;td&gt;One listing type, simple seller profiles, basic search, one payment flow, admin panel&lt;/td&gt;
&lt;td&gt;$15,000 to $35,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch-ready marketplace&lt;/td&gt;
&lt;td&gt;Seller onboarding, split payments with payouts, messaging, reviews, email notifications, admin moderation&lt;/td&gt;
&lt;td&gt;$35,000 to $80,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaled platform&lt;/td&gt;
&lt;td&gt;Multi-language, real-time messaging, advanced search, multiple payment methods, analytics, mobile apps&lt;/td&gt;
&lt;td&gt;$80,000 and up&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The wide spread exists because "marketplace" covers a services board and a full multi-vendor commerce platform equally. If you are still deciding what to include in version one, the same logic I use in the &lt;a href="https://aqibjavaid.site/blog/saas-mvp-development-cost" rel="noopener noreferrer"&gt;SaaS MVP development cost&lt;/a&gt; breakdown applies here: build the smallest thing that proves buyers and sellers will actually transact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does the money actually go?
&lt;/h2&gt;

&lt;p&gt;On a typical build, I see the budget split roughly like this: payments and payouts, seller onboarding and dashboards, search and listings, messaging and notifications, admin and moderation, then design and QA. Payments are consistently the line founders underestimate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payments and payouts
&lt;/h3&gt;

&lt;p&gt;Taking one payment is a day of work. Taking a payment, holding the platform fee, paying the seller later, handling refunds and reconciling it all is a project. Stripe's own &lt;a href="https://docs.stripe.com/connect/marketplace" rel="noopener noreferrer"&gt;Connect marketplace documentation&lt;/a&gt; is the right place to see how fund flows work, and the choice of charge type changes who is liable for disputes and refunds. Decide this before you design the seller dashboard, because it shapes the whole data model.&lt;/p&gt;

&lt;p&gt;On the &lt;a href="https://aqibjavaid.site/work/imperial-votes-nextjs-saas-voting-platform-case-study" rel="noopener noreferrer"&gt;Imperial Votes platform&lt;/a&gt;, the payment logic had to support different countries, currencies and payment gateways, alongside organizer dashboards and paid voting. Multi-gateway support was a major part of the work, not an afterthought.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-language and localisation
&lt;/h3&gt;

&lt;p&gt;If your marketplace serves more than one country, translation becomes a content system, not a plugin. On &lt;a href="https://aqibjavaid.site/work/planuojam-marketplace-case-study" rel="noopener noreferrer"&gt;Planuojam, a multilingual event planning marketplace&lt;/a&gt;, we rescued a failing platform by building a headless marketplace in Next.js and Strapi 5 that supports five languages, real-time messaging and Stripe payments. We added an AWS Translate-powered engine in Strapi to automate nested content localisation while preserving SEO integrity. Strapi's &lt;a href="https://docs.strapi.io/cms/features/internationalization" rel="noopener noreferrer"&gt;internationalization docs&lt;/a&gt; cover the baseline, but anything beyond the baseline takes custom work, and it should be priced in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Messaging and notifications
&lt;/h3&gt;

&lt;p&gt;Buyer-to-seller messaging looks small on a feature list. Real-time delivery, unread counts, email fallbacks and abuse controls make it a mid-sized feature. If you can launch with a simple contact form and add live chat once you have traffic, you save real money.&lt;/p&gt;

&lt;h3&gt;
  
  
  Admin and moderation
&lt;/h3&gt;

&lt;p&gt;Founders forget that they are the third user. You need to approve sellers, remove listings, handle refunds, see payouts and export reports. A weak admin panel turns every support request into a developer ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build custom, use a SaaS platform, or start with a plugin?
&lt;/h2&gt;

&lt;p&gt;Not every marketplace needs custom code on day one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Trade-off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Marketplace SaaS or plugin&lt;/td&gt;
&lt;td&gt;Testing demand quickly with standard flows&lt;/td&gt;
&lt;td&gt;Limited customisation, fees, and you may outgrow it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom build on a proven stack&lt;/td&gt;
&lt;td&gt;Unique workflows, multiple payment rules, SEO-led growth&lt;/td&gt;
&lt;td&gt;Higher upfront cost, full control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid: CMS plus custom app&lt;/td&gt;
&lt;td&gt;Content-heavy marketplaces needing editorial control&lt;/td&gt;
&lt;td&gt;Needs good architecture so the pieces stay in sync&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My rule of thumb: if your differentiator is the workflow or the pricing model, build custom. If your differentiator is supply or community, test with an off-the-shelf tool first. For the stack itself, I usually reach for &lt;a href="https://aqibjavaid.site/services/react-nextjs-development" rel="noopener noreferrer"&gt;Next.js and React&lt;/a&gt; on the front end with a Laravel or Strapi backend, depending on whether the product is application-heavy or content-heavy. The &lt;a href="https://nextjs.org/docs" rel="noopener noreferrer"&gt;Next.js documentation&lt;/a&gt; is a good starting reference for what the framework handles out of the box.&lt;/p&gt;

&lt;p&gt;If the platform will have subscriptions, tiers or multiple organisations on it, read the &lt;a href="https://aqibjavaid.site/blog/multi-tenant-saas-shared-vs-separate-database" rel="noopener noreferrer"&gt;multi-tenant database trade-offs&lt;/a&gt; before you commit to a data model. Changing it later is expensive. Our &lt;a href="https://aqibjavaid.site/services/saas-development" rel="noopener noreferrer"&gt;SaaS development service&lt;/a&gt; covers exactly this kind of architecture work.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can you reduce marketplace development cost without cutting corners?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick one side to serve first.&lt;/strong&gt; Launch with a supply you personally curate, so you don't need full seller self-service on day one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep one payment method and one currency&lt;/strong&gt; until revenue proves you need more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use managed services&lt;/strong&gt; for email, search and file storage instead of building them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delay the mobile apps.&lt;/strong&gt; A responsive web app validates demand for a fraction of the price. The &lt;a href="https://aqibjavaid.site/blog/mobile-app-development-cost" rel="noopener noreferrer"&gt;mobile app development cost guide&lt;/a&gt; shows when native is worth it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the spec before you hire.&lt;/strong&gt; Most budget overruns come from features added mid-build, not from the original scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget for after launch.&lt;/strong&gt; Plan for hosting, fixes and iteration from the start.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip&lt;/p&gt;
&lt;p&gt;
Before you ask for quotes, write one page describing who pays whom, when, and who handles a refund. That one page removes more estimate variance than any feature list.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When should you not build a custom marketplace?
&lt;/h2&gt;

&lt;p&gt;Skip the custom build if you have no proof that sellers will list and buyers will pay. A spreadsheet, a form and manual payouts can validate demand for almost nothing. I would rather you spend $2,000 learning that the idea doesn't work than $50,000 finding out after launch.&lt;/p&gt;

&lt;p&gt;Also avoid building if a niche Shopify store or WooCommerce setup already covers your model. Our &lt;a href="https://aqibjavaid.site/blog/shopify-vs-custom-ecommerce" rel="noopener noreferrer"&gt;custom versus Shopify comparison&lt;/a&gt; explains where that line sits for commerce-style products.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How long does it take to build a marketplace?
&lt;/h3&gt;

&lt;p&gt;A focused validation MVP usually takes two to three months with a small team. A launch-ready platform with split payments, messaging and moderation more often takes four to six months. Multi-language or mobile app scope extends that.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the ongoing costs after launch?
&lt;/h3&gt;

&lt;p&gt;Expect hosting, third-party fees (payment processing, email, maps or search), security updates and a steady stream of improvements. Plan a monthly maintenance budget rather than treating launch as the finish line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I build the marketplace in phases?
&lt;/h3&gt;

&lt;p&gt;Yes, and I recommend it. Phase one proves transactions work. Phase two adds the features that raise retention, such as reviews and messaging. Phase three scales with localisation or mobile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a developer or an agency?
&lt;/h3&gt;

&lt;p&gt;For a focused MVP, a senior full-stack engineer is often faster and cheaper than an agency because there are fewer handoffs. Larger platforms with design, QA and project management needs may justify a team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Marketplace cost is driven by payments, two-sided accounts, moderation and admin tooling, not by page design.&lt;/li&gt;
&lt;li&gt;Planning ranges run from roughly $15,000 for a validation MVP to $80,000 and up for a scaled, multi-language platform.&lt;/li&gt;
&lt;li&gt;Decide your payment model (who holds funds, who handles refunds) before you design anything else.&lt;/li&gt;
&lt;li&gt;Validate demand manually first, then build only the side of the marketplace you need to start transacting.&lt;/li&gt;
&lt;li&gt;Write a one-page spec of the money flow before requesting quotes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are scoping a marketplace and want a realistic number, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; and I'll walk through your requirements with you.&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/marketplace-website-development-cost" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>startup</category>
      <category>stripe</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>SaaS MVP Development Cost: What to Budget Before You Build</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:14:16 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/saas-mvp-development-cost-what-to-budget-before-you-build-2124</link>
      <guid>https://dev.to/aqib_javaid/saas-mvp-development-cost-what-to-budget-before-you-build-2124</guid>
      <description>&lt;p&gt;Founders ask me the same question early in almost every SaaS conversation: "What will the MVP cost?" The honest answer is that &lt;strong&gt;SaaS MVP development cost&lt;/strong&gt; is mostly decided by a handful of scope choices you make before a developer writes a line of code. The tech stack matters far less than people expect.&lt;/p&gt;

&lt;p&gt;This guide walks through the pieces that make up the budget, the features that quietly inflate it, and how I help founders cut a first version down to something that proves demand. It complements my general guide to &lt;a href="https://aqibjavaid.site/blog/custom-web-app-mvp-cost-2026" rel="noopener noreferrer"&gt;custom web app MVP cost&lt;/a&gt;, with a focus on subscription software. If you are planning a build, my &lt;a href="https://aqibjavaid.site/services/saas-development" rel="noopener noreferrer"&gt;SaaS development service&lt;/a&gt; is the place to start.&lt;/p&gt;

&lt;p&gt;I am not giving fixed price figures here, because a number without a scope is meaningless. What I can give you is the structure of the estimate, so you can judge any quote you receive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an MVP is (and is not)#
&lt;/h2&gt;

&lt;p&gt;An MVP is the &lt;strong&gt;smallest product that lets real users complete the one job you are selling, so you can learn whether they will pay for it.&lt;/strong&gt; It is not a cheap version of the final product. It is a deliberately narrow product.&lt;/p&gt;

&lt;p&gt;Most overspending comes from treating the MVP as "version one of everything." Each extra feature adds build time, testing, support and a longer wait before you learn anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The building blocks of a SaaS budget#
&lt;/h2&gt;

&lt;p&gt;Every SaaS product shares a core of plumbing before your unique value appears. Know what is in it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Block&lt;/th&gt;
&lt;th&gt;What it includes&lt;/th&gt;
&lt;th&gt;Why it costs more than it looks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Accounts and auth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sign-up, login, password reset, email verification, optional social login&lt;/td&gt;
&lt;td&gt;Security details and edge cases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Roles and permissions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Admin, member, owner; team invites&lt;/td&gt;
&lt;td&gt;Grows with every new role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Billing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plans, trials, upgrades, failed payments, invoices, taxes&lt;/td&gt;
&lt;td&gt;Billing is a state machine, not a form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Core feature&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The one thing users came for&lt;/td&gt;
&lt;td&gt;Your actual product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Admin tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manage users, support, content&lt;/td&gt;
&lt;td&gt;Needed from day one for support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Notifications&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transactional email, in-app alerts&lt;/td&gt;
&lt;td&gt;Deliverability and templates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hosting, environments, backups, monitoring&lt;/td&gt;
&lt;td&gt;Often forgotten in estimates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Design and UX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Flows, UI, responsive layouts&lt;/td&gt;
&lt;td&gt;Quality drives conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing and QA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automated tests, manual passes&lt;/td&gt;
&lt;td&gt;Prevents costly regressions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you receive a quote, check that each of these is accounted for. A suspiciously low number usually leaves out billing, admin tools or testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features that quietly inflate the cost#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Multi-tenancy#
&lt;/h3&gt;

&lt;p&gt;If each customer is a separate organisation with isolated data, you need a tenancy model from the start. It touches every query and permission check. I explain the options in &lt;a href="https://aqibjavaid.site/blog/multi-tenant-saas-shared-vs-separate-database" rel="noopener noreferrer"&gt;shared vs separate database for multi-tenant SaaS&lt;/a&gt;. Decide this early, because retrofitting it is expensive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Billing complexity#
&lt;/h3&gt;

&lt;p&gt;A single flat monthly plan is straightforward. Add usage-based pricing, multiple currencies, seat-based plans, coupons, proration and annual contracts, and the effort multiplies. Start with the simplest model that lets you charge, and let customer feedback justify more. My notes on &lt;a href="https://aqibjavaid.site/blog/subscription-billing-architecture-laravel-saas" rel="noopener noreferrer"&gt;subscription billing architecture in Laravel&lt;/a&gt; show why this area deserves care.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with SaaS Development?
&lt;/h3&gt;

&lt;p&gt;Build scalable SaaS products from MVP to production, including subscriptions, multi-tenant architecture, dashboards, integrations, automation, and complex business workflows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/saas-development" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrations#
&lt;/h3&gt;

&lt;p&gt;Each third-party integration (CRM, accounting, calendar, data providers) adds authentication, error handling and maintenance. Pick the one or two that are essential to the first customers and defer the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-time features#
&lt;/h3&gt;

&lt;p&gt;Live updates, chat and collaborative editing are powerful and costly. Use them only where the product is meaningless without them. On the &lt;a href="https://aqibjavaid.site/work/imperial-votes-nextjs-saas-voting-platform-case-study" rel="noopener noreferrer"&gt;Imperial Votes platform&lt;/a&gt;, a custom online voting product for contests and pageants, I built it from scratch using Next.js, Prisma and PostgreSQL with Stripe and Paystack for payments and Socket.IO for live behaviour. Features like payments and real-time updates were core to that product, so they belonged in the first version. For many other products they would not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom admin and analytics#
&lt;/h3&gt;

&lt;p&gt;Dashboards, reports and exports look small on a wireframe and are large in code. Use off-the-shelf analytics early, and build custom reporting after you know what users ask for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mobile apps#
&lt;/h3&gt;

&lt;p&gt;A responsive web app often serves an MVP. Native apps double the surface area. Add them when you have evidence that mobile is where users are.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to scope the first version#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Write the one-sentence promise.&lt;/strong&gt; "Help X do Y without Z." Every feature must serve it.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;List user journeys, not features.&lt;/strong&gt; Sign up, do the core job, pay. If a journey is not needed to learn something, defer it.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Sort into must, should and later.&lt;/strong&gt; Be strict about "must": would the first ten customers refuse to use it without this?&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Replace features with manual processes.&lt;/strong&gt; Onboard customers by hand, invoice the first few manually, review data in a spreadsheet. These are valid MVP techniques.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Buy before you build.&lt;/strong&gt; Use proven services for auth, email, payments and error tracking where it fits.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a deeper look at decisions to make before development starts, read &lt;a href="https://aqibjavaid.site/blog/technical-decisions-before-building-saas-product" rel="noopener noreferrer"&gt;technical decisions before building a SaaS product&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost drivers outside the code#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unclear requirements.&lt;/strong&gt; Changes during development cost more than changes in planning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow feedback.&lt;/strong&gt; Delays in decisions and approvals stretch a project and increase cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team structure.&lt;/strong&gt; A senior engineer working directly with a founder usually moves faster than a layered team, but suits some projects better than others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-launch work.&lt;/strong&gt; Budget for fixes, small improvements and hosting. A common rule of thumb is to keep a reserve for the months after launch rather than spending everything on version one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to compare quotes#
&lt;/h2&gt;

&lt;p&gt;When two quotes differ widely, compare these before you compare price:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Is the scope written down in terms of user journeys?&lt;/li&gt;
&lt;li&gt; Does it include billing, admin, notifications and testing?&lt;/li&gt;
&lt;li&gt; Who owns the code and where is it hosted?&lt;/li&gt;
&lt;li&gt; What is the plan for the first three months after launch?&lt;/li&gt;
&lt;li&gt; Can the work be delivered in phases with a review after each?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A phased approach, with a working slice every couple of weeks, is the best protection against surprises. You see progress, you can change direction and you can stop with something usable.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to build custom software yet#
&lt;/h2&gt;

&lt;p&gt;If you can test the idea with a no-code tool, a spreadsheet and a payment link, do that first. Custom software is justified when the workflow cannot be faked, or when no-code tools cannot deliver the experience or control you need. I have helped teams move off those tools once they hit limits, as in &lt;a href="https://aqibjavaid.site/blog/migrate-no-code-app-to-nextjs-postgres" rel="noopener noreferrer"&gt;migrating a no-code app to Next.js and Postgres&lt;/a&gt;, but a first prototype in a simpler tool can save months.&lt;/p&gt;

&lt;p&gt;The principle behind all of this is to launch with a small feature set and learn from real users before you invest further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How long does it take to build a SaaS MVP?#
&lt;/h3&gt;

&lt;p&gt;It depends on scope, but a focused first version is typically measured in weeks to a few months, not a year. If the plan says a year, the scope is probably too large.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use Laravel or Next.js for a SaaS MVP?#
&lt;/h3&gt;

&lt;p&gt;Either can work. Laravel gives you authentication, queues, billing integrations and an admin-friendly structure quickly. Next.js suits products with rich front ends. The choice should follow your product and team, and I compare the options in my &lt;a href="https://aqibjavaid.site/blog/laravel-vs-nodejs-for-saas" rel="noopener noreferrer"&gt;Laravel vs Node.js for SaaS&lt;/a&gt; article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need multi-tenancy from day one?#
&lt;/h3&gt;

&lt;p&gt;If customers are organisations with their own users and data, yes. If your product is for individuals, probably not.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should I keep out of the MVP?#
&lt;/h3&gt;

&lt;p&gt;Anything you cannot connect to a measurable learning goal: advanced reporting, multiple integrations, custom roles, native apps and elaborate settings screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MVP cost is driven by scope choices: tenancy, billing, integrations, real-time features and admin tools.&lt;/li&gt;
&lt;li&gt;Check every quote for auth, billing, admin, notifications, infrastructure and testing.&lt;/li&gt;
&lt;li&gt;Define the first version by user journeys and cut anything that does not help you learn.&lt;/li&gt;
&lt;li&gt;Phase the work, review after each phase and keep a reserve for post-launch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are planning a SaaS product and want help turning an idea into a scoped first version, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/saas-mvp-development-cost" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>saas</category>
      <category>mvpdevelopment</category>
      <category>softwareestimation</category>
      <category>startupengineering</category>
    </item>
    <item>
      <title>Laravel N+1 Query Problem: How to Find and Fix It</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:14:03 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/laravel-n1-query-problem-how-to-find-and-fix-it-f83</link>
      <guid>https://dev.to/aqib_javaid/laravel-n1-query-problem-how-to-find-and-fix-it-f83</guid>
      <description>&lt;p&gt;When a Laravel page that used to load in half a second suddenly takes four, the cause is very often the same: the &lt;strong&gt;N+1 query problem&lt;/strong&gt;. One query fetches a list, and then your code quietly runs one more query for every row. With 10 rows nobody notices. With 500 rows the database is doing 501 round trips for a single page.&lt;/p&gt;

&lt;p&gt;This article shows how to find N+1 queries, fix them properly and keep them from returning. It builds on my guide to &lt;a href="https://aqibjavaid.site/blog/laravel-slow-queries-fix" rel="noopener noreferrer"&gt;finding slow queries in Laravel&lt;/a&gt;, and if your whole application feels slow, my &lt;a href="https://aqibjavaid.site/services/database-optimization-backend-performance" rel="noopener noreferrer"&gt;database optimization and backend performance service&lt;/a&gt; is where I start.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the problem looks like#
&lt;/h2&gt;

&lt;p&gt;Imagine a list of orders that shows each customer's name:&lt;/p&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$orders = Order::latest()-&amp;gt;take(50)-&amp;gt;get();

foreach ($orders as $order) {
    echo $order-&amp;gt;customer-&amp;gt;name; // runs a query per order
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That is 1 query for orders plus 50 for customers: &lt;strong&gt;51 queries&lt;/strong&gt;. The code looks innocent because relationship access reads like a property. Eloquent loads relations lazily by default, so each first access hits the database.&lt;/p&gt;

&lt;p&gt;It gets worse when it hides in places you do not expect: a Blade loop, an API resource, an accessor that touches a relation, or a policy check inside a loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to find N+1 queries#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Laravel's built-in guard#
&lt;/h3&gt;

&lt;p&gt;The simplest tool is to make lazy loading an error in non-production environments. In a service provider:&lt;/p&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Database\Eloquent\Model;

public function boot(): void
{
    Model::preventLazyLoading(! app()-&amp;gt;isProduction());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now any lazy load throws an exception during development and tests, with the model and relation named. This turns a silent performance bug into a visible failure. The behaviour is documented in the &lt;a href="https://laravel.com/docs/eloquent-relationships#preventing-lazy-loading" rel="noopener noreferrer"&gt;Eloquent relationships docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In production, you can log violations instead of throwing, using &lt;code&gt;Model::handleLazyLoadingViolationUsing()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a debugging tool#
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Laravel Debugbar&lt;/strong&gt; or &lt;strong&gt;Telescope&lt;/strong&gt; shows the number of queries per request and highlights duplicates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clockwork&lt;/strong&gt; gives a timeline in the browser.&lt;/li&gt;
&lt;li&gt;For production, an APM or logging setup that records query counts per request lets you find the worst endpoints. I cover this in &lt;a href="https://aqibjavaid.site/blog/laravel-observability-architecture" rel="noopener noreferrer"&gt;observability for Laravel&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Count queries in a test#
&lt;/h3&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB::enableQueryLog();
$this-&amp;gt;getJson('/api/orders')-&amp;gt;assertOk();
$this-&amp;gt;assertLessThan(10, count(DB::getQueryLog()));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Tests that assert a query budget on your busiest endpoints catch regressions before release.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with Database Optimization &amp;amp; Backend Performance?
&lt;/h3&gt;

&lt;p&gt;Find and fix database and backend bottlenecks in data-heavy applications, with a focus on query performance, indexing, architecture, and scalability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/database-optimization-backend-performance" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fixes#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Eager loading with &lt;code&gt;with()&lt;/code&gt;#
&lt;/h3&gt;

&lt;p&gt;Load the relation up front. Laravel runs one extra query for all related records instead of one per row:&lt;/p&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$orders = Order::with('customer')-&amp;gt;latest()-&amp;gt;take(50)-&amp;gt;get();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now it is 2 queries, regardless of how many orders there are.&lt;/p&gt;

&lt;p&gt;Eager load nested relations with dot notation, and select only the columns you need:&lt;/p&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order::with(['customer:id,name', 'items.product:id,name,price'])-&amp;gt;get();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Remember to include the key columns Eloquent needs to match records, such as &lt;code&gt;id&lt;/code&gt; and foreign keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Counts and aggregates with &lt;code&gt;withCount()&lt;/code&gt;#
&lt;/h3&gt;

&lt;p&gt;Showing "12 comments" per post by calling &lt;code&gt;$post-&amp;gt;comments-&amp;gt;count()&lt;/code&gt; loads every comment. Use:&lt;/p&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Post::withCount('comments')-&amp;gt;get(); // adds comments_count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There are also &lt;code&gt;withSum&lt;/code&gt;, &lt;code&gt;withAvg&lt;/code&gt;, &lt;code&gt;withMin&lt;/code&gt; and &lt;code&gt;withMax&lt;/code&gt; for aggregates computed in SQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional eager loading#
&lt;/h3&gt;

&lt;p&gt;If a relation is only needed sometimes, use &lt;code&gt;load()&lt;/code&gt; or &lt;code&gt;loadMissing()&lt;/code&gt; after the fact:&lt;/p&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$orders-&amp;gt;loadMissing('customer');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In API resources, use &lt;code&gt;whenLoaded('customer')&lt;/code&gt; so the resource does not trigger a lazy load by accident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Default eager loads: use sparingly#
&lt;/h3&gt;

&lt;p&gt;You can set &lt;code&gt;$with&lt;/code&gt; on a model to always load certain relations. It is convenient and dangerous, because every query for that model now loads those relations whether they are needed or not. Prefer explicit &lt;code&gt;with()&lt;/code&gt; in the places that need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where N+1 hides#
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hiding place&lt;/th&gt;
&lt;th&gt;Why it happens&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Blade loops&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@foreach&lt;/code&gt; accesses &lt;code&gt;$item-&amp;gt;relation&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Eager load in the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API resources&lt;/td&gt;
&lt;td&gt;Resource reads a relation per item&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;whenLoaded()&lt;/code&gt; plus eager load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessors&lt;/td&gt;
&lt;td&gt;An attribute method touches a relation&lt;/td&gt;
&lt;td&gt;Eager load or compute in SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policies and gates&lt;/td&gt;
&lt;td&gt;Authorisation queries per row&lt;/td&gt;
&lt;td&gt;Preload permissions once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Counts in views&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-&amp;gt;count()&lt;/code&gt; on a loaded relation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;withCount()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nested relations&lt;/td&gt;
&lt;td&gt;Parent loaded, children lazily loaded&lt;/td&gt;
&lt;td&gt;Dot notation in &lt;code&gt;with()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Eager loading is not always the answer#
&lt;/h2&gt;

&lt;p&gt;Sometimes the better fix is different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pagination:&lt;/strong&gt; do not load thousands of records at all. Paginate or use cursors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Joins or subqueries:&lt;/strong&gt; for sorting or filtering by a related value, a join can be better than loading models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking:&lt;/strong&gt; for big exports and jobs, use &lt;code&gt;chunkById()&lt;/code&gt; or lazy collections to limit memory, then eager load per chunk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching:&lt;/strong&gt; if the result is expensive and changes rarely, cache it. See &lt;a href="https://aqibjavaid.site/blog/laravel-redis-caching-architecture" rel="noopener noreferrer"&gt;Redis caching in Laravel&lt;/a&gt;, and remember that cache hides a slow query, it does not fix it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indexes:&lt;/strong&gt; a query that runs once but scans a whole table is a different problem. Check &lt;code&gt;EXPLAIN&lt;/code&gt; and add the right index.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eager loading can also overshoot: loading large relations you do not display wastes memory. Select only what you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real-world pattern#
&lt;/h2&gt;

&lt;p&gt;Data-heavy dashboards are where N+1 bites hardest. On the &lt;a href="https://aqibjavaid.site/work/violerts-nyc-proptech-compliance-saas" rel="noopener noreferrer"&gt;Violerts NYC compliance platform&lt;/a&gt;, the backend consolidates property data from several city sources and serves it to a React frontend through REST APIs. In any system with related records shown in lists and maps, the habits are the same: eager load what a view needs, aggregate in SQL and keep query counts predictable per endpoint. I treat query count as a metric to watch, in the same way as response time.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple routine for existing apps#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Turn on &lt;code&gt;preventLazyLoading&lt;/code&gt; in local and CI environments.&lt;/li&gt;
&lt;li&gt; Run your main pages and API endpoints and note any exceptions.&lt;/li&gt;
&lt;li&gt; Fix each with &lt;code&gt;with()&lt;/code&gt;, &lt;code&gt;withCount()&lt;/code&gt; or a query change.&lt;/li&gt;
&lt;li&gt; Add query-count assertions to tests for your busiest endpoints.&lt;/li&gt;
&lt;li&gt; In production, log slow requests and high query counts, and review them regularly.&lt;/li&gt;
&lt;li&gt; Re-check after major features. N+1 problems come back as code changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For broader MySQL tuning (slow log, &lt;code&gt;EXPLAIN&lt;/code&gt;, indexes), the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/using-explain.html" rel="noopener noreferrer"&gt;MySQL documentation on EXPLAIN&lt;/a&gt; is the reference to keep open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Laravel fix N+1 automatically?#
&lt;/h3&gt;

&lt;p&gt;No. By default, Eloquent lazy loads. You need to eager load or enable &lt;code&gt;preventLazyLoading&lt;/code&gt; to catch it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will eager loading always be faster?#
&lt;/h3&gt;

&lt;p&gt;Usually, for lists. For a single record where you rarely need the relation, lazy loading is fine. Measure rather than assume.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is N+1 still a problem with a fast database?#
&lt;/h3&gt;

&lt;p&gt;Yes. Each query has network and parsing overhead, so hundreds of small queries add up even when each one is fast, and they load the database under traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many queries per page is too many?#
&lt;/h3&gt;

&lt;p&gt;There is no fixed number, but a list page that needs more than a dozen or so queries deserves a look, and counts that grow with the number of rows are a red flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;N+1 happens when code lazy loads a relation inside a loop; the query count grows with the row count.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Model::preventLazyLoading()&lt;/code&gt; in development and tests to make it visible.&lt;/li&gt;
&lt;li&gt;Fix with &lt;code&gt;with()&lt;/code&gt;, &lt;code&gt;withCount()&lt;/code&gt; and &lt;code&gt;loadMissing()&lt;/code&gt;, selecting only needed columns.&lt;/li&gt;
&lt;li&gt;Guard against regressions with query-count tests and production monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your Laravel application is slow and you want an expert to find out why, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;contact me&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/laravel-n-plus-one-query-fix" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>databaseoptimization</category>
      <category>mysql</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to Migrate WordPress to Next.js Without Losing SEO</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:13:33 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/how-to-migrate-wordpress-to-nextjs-without-losing-seo-n1h</link>
      <guid>https://dev.to/aqib_javaid/how-to-migrate-wordpress-to-nextjs-without-losing-seo-n1h</guid>
      <description>&lt;p&gt;Moving a WordPress site to Next.js can make it faster and easier to evolve. Done badly, it can also wipe out years of search rankings in a week. The technology rarely causes that. &lt;strong&gt;Missing redirects, changed URLs, lost metadata and a forgotten sitemap&lt;/strong&gt; do. This guide is the plan I follow to &lt;strong&gt;migrate WordPress to Next.js without losing SEO&lt;/strong&gt;, written for business owners and the developers they hire.&lt;/p&gt;

&lt;p&gt;If you are still deciding whether to move at all, read &lt;a href="https://aqibjavaid.site/blog/nextjs-vs-wordpress-for-business" rel="noopener noreferrer"&gt;Next.js vs WordPress for a business website&lt;/a&gt; first. If you have decided, my &lt;a href="https://aqibjavaid.site/services/react-nextjs-development" rel="noopener noreferrer"&gt;React and Next.js development service&lt;/a&gt; covers builds like this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle: keep everything Google already knows#
&lt;/h2&gt;

&lt;p&gt;Search engines have learned your URLs, titles, headings, links and content. A migration is safe when, after launch, the same URLs return the same content (or a clean redirect to its new home), with the same metadata and a fast, crawlable page. Everything below serves that principle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Take a full inventory before touching code#
&lt;/h2&gt;

&lt;p&gt;Build a spreadsheet of what exists now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every indexed URL: crawl the site and export URLs from your sitemap, analytics and Google Search Console.&lt;/li&gt;
&lt;li&gt;For each URL: title, meta description, H1, canonical, status code, internal links in, organic traffic and impressions.&lt;/li&gt;
&lt;li&gt;Rankings for your key pages and queries.&lt;/li&gt;
&lt;li&gt;Structured data, redirects already in place, and any special files like &lt;code&gt;robots.txt&lt;/code&gt;, &lt;code&gt;ads.txt&lt;/code&gt; or verification files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save a copy of this baseline. It is how you will prove, a month later, whether the migration worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Decide the URL strategy#
&lt;/h2&gt;

&lt;p&gt;The safest approach is to &lt;strong&gt;keep URLs identical&lt;/strong&gt;. WordPress commonly uses paths like &lt;code&gt;/blog/post-name/&lt;/code&gt; with a trailing slash, category archives and pagination. Next.js can reproduce these through its routing and configuration (for example, the &lt;code&gt;trailingSlash&lt;/code&gt; option and dynamic routes).&lt;/p&gt;

&lt;p&gt;If a URL must change, give every old URL a &lt;strong&gt;one-to-one 301 redirect&lt;/strong&gt; to its new equivalent. Avoid redirecting everything to the home page, which Google treats as a soft 404. Avoid chains: old to new in a single hop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Choose how content is managed#
&lt;/h2&gt;

&lt;p&gt;You rarely want to hand-code content into React. Options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Headless WordPress:&lt;/strong&gt; keep WordPress as the editor and fetch content through its API into Next.js. The editing experience stays familiar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A headless CMS&lt;/strong&gt; such as Strapi or Contentful, with content migrated across.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Markdown or MDX files&lt;/strong&gt; in the repository, which suit developer-run blogs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the &lt;a href="https://aqibjavaid.site/work/nooora-beauty-concierge-nextjs-strapi-seo-case-study" rel="noopener noreferrer"&gt;Nooora Beauty Concierge&lt;/a&gt; site, I joined a project where some sections came from Strapi and others were hard-coded, and client content changes required a developer. The work was moving major content areas into Strapi, adding dynamic service pages and building SEO and sitemap support, so the client could update content themselves on a platform that depends on search visibility. The lesson applies to any migration: &lt;strong&gt;decide who edits content and make the CMS support that person.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Recreate on-page SEO exactly#
&lt;/h2&gt;

&lt;p&gt;For every page, carry over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Title tags and meta descriptions.&lt;/strong&gt; In the Next.js App Router, use the Metadata API (&lt;code&gt;generateMetadata&lt;/code&gt;) to render them server-side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canonical URLs&lt;/strong&gt; that point to the preferred version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heading structure,&lt;/strong&gt; especially H1.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open Graph and social tags.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image alt text&lt;/strong&gt; and sensible dimensions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured data&lt;/strong&gt; (JSON-LD) for articles, products, organisation and breadcrumbs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal links,&lt;/strong&gt; with the same anchors where possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Next.js docs on &lt;a href="https://nextjs.org/docs/app/building-your-application/optimizing/metadata" rel="noopener noreferrer"&gt;metadata&lt;/a&gt; explain the API. Plugins like Yoast or Rank Math store this data in WordPress, so export it rather than rewriting it by hand.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with React &amp;amp; Next.js Development?
&lt;/h3&gt;

&lt;p&gt;Build fast, responsive React and Next.js applications with modern TypeScript architecture, API integrations, reusable components, and production-ready frontend experiences.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/react-nextjs-development" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Render pages so crawlers can read them#
&lt;/h2&gt;

&lt;p&gt;Use server rendering or static generation for public pages, not client-only rendering. Google can process JavaScript, but delivering complete HTML in the first response is faster and more reliable. Next.js does this by default for Server Components. Check the page source after building, not just what you see in the browser.&lt;/p&gt;

&lt;p&gt;Use incremental static regeneration or on-demand revalidation so new posts appear without a full rebuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Build redirects, sitemap and robots#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redirects:&lt;/strong&gt; define them in &lt;code&gt;next.config.js&lt;/code&gt; or middleware. For large lists, load them from a file or a database table, and test every one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitemap:&lt;/strong&gt; generate &lt;code&gt;sitemap.xml&lt;/code&gt; from your content source and include only canonical, indexable URLs. Submit it in Search Console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;robots.txt:&lt;/strong&gt; make sure you do not accidentally carry over a staging "disallow all" rule. This is one of the most common launch mistakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trailing slash and case rules:&lt;/strong&gt; pick one version and redirect the other.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Check performance and Core Web Vitals#
&lt;/h2&gt;

&lt;p&gt;Speed is one of the reasons to migrate, so confirm it. Use &lt;code&gt;next/image&lt;/code&gt; for responsive images, load fonts efficiently and avoid shipping heavy client-side JavaScript on content pages. Measure with &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt; before and after. A migration that is slower than the original is a failed migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Test on staging, thoroughly#
&lt;/h2&gt;

&lt;p&gt;Block staging from search engines with authentication, not just a &lt;code&gt;noindex&lt;/code&gt; tag that might ship to production. Then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Crawl staging and compare URLs, titles, canonicals and status codes to your baseline.&lt;/li&gt;
&lt;li&gt; Test every redirect from the old URL list.&lt;/li&gt;
&lt;li&gt; Validate structured data.&lt;/li&gt;
&lt;li&gt; Check forms, search, comments and any plugin-powered features you must replace.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I took a similar approach in rebuilding &lt;a href="https://aqibjavaid.site/work/tattoo-panda-wordpress-to-plain-php-contentful" rel="noopener noreferrer"&gt;Tattoo Panda from WordPress to plain PHP with Contentful&lt;/a&gt;, where every page was checked against the original. The method is the same whichever stack you move to: compare the new site to the old one page by page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: Launch with care#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Choose a &lt;strong&gt;low-traffic window&lt;/strong&gt; and avoid launching alongside a redesign of the copy, which makes cause and effect impossible to separate.&lt;/li&gt;
&lt;li&gt;Keep the old server available briefly, in case you need to compare or roll back.&lt;/li&gt;
&lt;li&gt;Update DNS, then immediately re-test redirects, robots.txt and the sitemap on the live domain.&lt;/li&gt;
&lt;li&gt;Submit the sitemap and use Search Console's URL Inspection on key pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 10: Monitor for 8 to 12 weeks#
&lt;/h2&gt;

&lt;p&gt;Watch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search Console:&lt;/strong&gt; Pages report for indexing errors, Performance report for impressions and clicks versus your baseline, and crawl stats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;404s&lt;/strong&gt; from logs and Search Console, adding redirects for any you missed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rankings&lt;/strong&gt; for priority pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics:&lt;/strong&gt; traffic, conversions and form submissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A small dip in the first weeks is common as Google re-crawls. A sustained drop after a clean migration usually points to a specific problem, such as missing redirects or changed content, and the baseline helps you find it.&lt;/p&gt;

&lt;p&gt;⚠️Warning&lt;/p&gt;

&lt;p&gt;Do not change domain, URLs, design and content in the same release. Each is a separate risk. Migrate the platform first, change other things afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to migrate#
&lt;/h2&gt;

&lt;p&gt;If your WordPress site is fast, easy to edit and ranking well, a migration adds risk with limited benefit. Consider it when you need custom application features, outgrow what plugins can safely do or have performance problems that cannot be fixed within WordPress. For a broader look at the decision to change a system versus improve it, see &lt;a href="https://aqibjavaid.site/blog/rewrite-vs-refactor-legacy-application" rel="noopener noreferrer"&gt;rewrite vs refactor&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Will I lose rankings when I migrate?#
&lt;/h3&gt;

&lt;p&gt;Not if URLs, content and metadata are preserved and redirects are correct. Temporary fluctuation is normal, but a planned migration should return to baseline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I keep using the WordPress editor?#
&lt;/h3&gt;

&lt;p&gt;Yes, with headless WordPress. You trade some plugin features for a faster front end.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does a migration take?#
&lt;/h3&gt;

&lt;p&gt;It depends on the number of templates and features, not just the number of pages. A small brochure site can take weeks; a large site with custom features takes longer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to keep my old URLs?#
&lt;/h3&gt;

&lt;p&gt;It is the safest approach. If you change them, use one-to-one 301 redirects and monitor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Inventory every URL, title and ranking before you start.&lt;/li&gt;
&lt;li&gt;Keep URLs the same, or map each old URL to its new one with a single 301.&lt;/li&gt;
&lt;li&gt;Reproduce metadata, canonicals, structured data and sitemaps, and render HTML on the server.&lt;/li&gt;
&lt;li&gt;Test on a protected staging site, launch carefully and monitor for weeks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are planning a WordPress to Next.js move and want it done without a ranking drop, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;contact me&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/migrate-wordpress-to-nextjs-seo" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>wordpress</category>
      <category>seo</category>
      <category>applicationmodernization</category>
    </item>
    <item>
      <title>UPS and FedEx API Integration in Laravel: Real-Time Shipping Rates</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:13:24 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/ups-and-fedex-api-integration-in-laravel-real-time-shipping-rates-5g4p</link>
      <guid>https://dev.to/aqib_javaid/ups-and-fedex-api-integration-in-laravel-real-time-shipping-rates-5g4p</guid>
      <description>&lt;p&gt;If your business ships parcels, showing accurate shipping costs in real time is not optional. Customers abandon carts when shipping is a surprise, and staff waste hours quoting by hand. Both UPS and FedEx publish APIs that return rates for a given package and destination, and wiring them into a Laravel application is very doable. The difficulty is not the first successful request. It is everything that happens when the carrier is slow, the address is wrong or the response format changes.&lt;/p&gt;

&lt;p&gt;This guide covers how I approach &lt;strong&gt;UPS and FedEx API integration in Laravel&lt;/strong&gt;, based on building the rate calculation for a logistics quote system. It is part of the broader work I do in &lt;a href="https://aqibjavaid.site/services/api-development-integrations" rel="noopener noreferrer"&gt;API development and integrations&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the integration needs to do#
&lt;/h2&gt;

&lt;p&gt;Before writing code, be precise about the job. A quote engine has to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Accept package details (weight, dimensions, quantity) and origin and destination addresses.&lt;/li&gt;
&lt;li&gt; Ask each carrier for its available services and prices.&lt;/li&gt;
&lt;li&gt; Convert the different responses into &lt;strong&gt;one common format&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Show the user the options, and store the quote so it can be sent, accepted or audited later.&lt;/li&gt;
&lt;li&gt; Keep working, with a clear message, when a carrier is unavailable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the &lt;a href="https://aqibjavaid.site/work/expreco-logistics-quote-system" rel="noopener noreferrer"&gt;EXPRECO logistics quote system&lt;/a&gt;, I built this in Laravel and Livewire, integrating UPS and FedEx APIs to calculate real-time shipping rates from package details and destination, plus a ticketing workflow so businesses could respond to customers and share quotes from the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Get credentials and read the docs#
&lt;/h2&gt;

&lt;p&gt;Both carriers require a developer account and application credentials. Their authentication has moved toward OAuth 2.0 client credentials, so check the current documentation rather than relying on older tutorials that describe retired access-key methods. The official portals are the &lt;a href="https://developer.ups.com/" rel="noopener noreferrer"&gt;UPS Developer Portal&lt;/a&gt; and the &lt;a href="https://developer.fedex.com/" rel="noopener noreferrer"&gt;FedEx Developer Portal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Practical notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start in the &lt;strong&gt;sandbox&lt;/strong&gt; environment. Rates there may not match production, so validate with real accounts before launch.&lt;/li&gt;
&lt;li&gt;Decide whether you need &lt;strong&gt;published rates&lt;/strong&gt; or your &lt;strong&gt;negotiated account rates&lt;/strong&gt;. They are different requests with different credentials.&lt;/li&gt;
&lt;li&gt;Read the terms about caching and displaying rates. Some programmes have rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Build a carrier abstraction#
&lt;/h2&gt;

&lt;p&gt;Do not scatter HTTP calls through controllers. Create a small interface that every carrier implements:&lt;/p&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface RateProvider
{
    /** @return Collection&amp;lt;int, RateQuote&amp;gt; */
    public function quote(ShipmentRequest $request): Collection;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then write a &lt;code&gt;UpsRateProvider&lt;/code&gt; and a &lt;code&gt;FedExRateProvider&lt;/code&gt; that translate your &lt;code&gt;ShipmentRequest&lt;/code&gt; into each carrier's payload and map the response into a common &lt;code&gt;RateQuote&lt;/code&gt; object (carrier, service name, price, currency, estimated delivery, raw reference ID). A third carrier later becomes one new class instead of a rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Handle authentication tokens properly#
&lt;/h2&gt;

&lt;p&gt;OAuth access tokens are short-lived. Request one, cache it in Laravel's cache until shortly before expiry, and reuse it for every call. Requesting a new token on each rate request wastes time and can trigger rate limits.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with API Development &amp;amp; Integrations?
&lt;/h3&gt;

&lt;p&gt;Design and integrate reliable APIs connecting your application with third-party platforms, business systems, payment providers, and external data sources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/api-development-integrations" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$token = Cache::remember('fedex.token', $ttlSeconds - 60, function () {
    return $this-&amp;gt;requestNewToken();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Wrap the call so that a &lt;code&gt;401&lt;/code&gt; response clears the cached token and retries once. Keep client IDs and secrets in environment variables, never in the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Use Laravel's HTTP client with timeouts and retries#
&lt;/h2&gt;

&lt;p&gt;The HTTP client makes timeouts and retries a one-liner, and it is where reliability starts:&lt;/p&gt;

&lt;p&gt;php&lt;/p&gt;

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$response = Http::withToken($token)
    -&amp;gt;timeout(8)
    -&amp;gt;retry(2, 200, throw: false)
    -&amp;gt;post($url, $payload);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Choose timeouts that suit the user experience. A checkout page cannot wait 30 seconds. If the carrier does not answer in time, show the other carrier's rates or a fallback, rather than failing the whole page.&lt;/p&gt;

&lt;p&gt;Call both carriers &lt;strong&gt;in parallel&lt;/strong&gt; using &lt;code&gt;Http::pool()&lt;/code&gt; so the user waits for the slowest one, not the sum of both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Normalise and validate the response#
&lt;/h2&gt;

&lt;p&gt;Carriers return different structures, units and service codes. Map them carefully:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convert currencies and rounding in one place.&lt;/li&gt;
&lt;li&gt;Translate service codes into names your users understand.&lt;/li&gt;
&lt;li&gt;Reject responses with missing prices instead of showing zero.&lt;/li&gt;
&lt;li&gt;Log the raw request and response identifiers so you can debug disputes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validate input before you call anyone. Missing postcodes, impossible weights and bad dimensions are the most common causes of carrier errors. Show the user a plain message and let them correct it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Cache sensibly#
&lt;/h2&gt;

&lt;p&gt;Identical requests (same package, same route) within a short window can reuse results. A cache key built from the normalised request and a short TTL cuts calls and speeds up the UI. Keep the TTL short, since rates can change with fuel surcharges and service levels, and check each carrier's terms. My notes on &lt;a href="https://aqibjavaid.site/blog/laravel-redis-caching-architecture" rel="noopener noreferrer"&gt;Redis caching architecture in Laravel&lt;/a&gt; cover key design and invalidation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Plan for failure#
&lt;/h2&gt;

&lt;p&gt;Carrier APIs go down and return odd errors. Decide up front what the user sees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One carrier fails:&lt;/strong&gt; show the other's rates, with a note if relevant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both fail:&lt;/strong&gt; allow the quote to be saved as "pending" and processed by a queued job, or let staff quote manually.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Address problems:&lt;/strong&gt; surface the carrier's message in plain language.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also respect carrier rate limits. If many users hit your quote form at once, you may need to queue requests or throttle them. My article on &lt;a href="https://aqibjavaid.site/blog/laravel-api-rate-limiting-architecture" rel="noopener noreferrer"&gt;API rate limiting architecture&lt;/a&gt; covers the incoming side of the problem, and the same ideas of back-off and limits apply when you are the client.&lt;/p&gt;

&lt;p&gt;ℹ️Note&lt;/p&gt;

&lt;p&gt;Always store the quote you showed the customer. If the price changes by the time they accept it, you need the original to resolve the dispute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Test without hitting the carrier#
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;Http::fake()&lt;/code&gt; to return recorded sample responses in your test suite so tests are fast and deterministic. Keep a handful of real recorded payloads for edge cases: oversized packages, remote areas, multi-package shipments. Add a few sandbox tests you run manually before each release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping rates vs full shipping#
&lt;/h2&gt;

&lt;p&gt;Rates are the easiest part. Creating labels, tracking shipments, handling customs documents for international orders and managing returns all add separate integrations. Decide on scope early so a "show me rates" project does not quietly become a logistics platform. For an overview of how integration projects are scoped and what influences their cost, read &lt;a href="https://aqibjavaid.site/blog/api-integration-cost-timeline" rel="noopener noreferrer"&gt;API integration cost and timeline&lt;/a&gt;, and for general design rules see &lt;a href="https://aqibjavaid.site/blog/third-party-api-integration-best-practices" rel="noopener noreferrer"&gt;third-party API integration best practices&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I use a shipping aggregator instead of direct carrier APIs?#
&lt;/h3&gt;

&lt;p&gt;An aggregator gives you many carriers through one integration and may include labels and tracking. The trade-off is fees and less control. Direct APIs make sense when you use one or two carriers and want negotiated rates and full control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need Livewire or a frontend framework?#
&lt;/h3&gt;

&lt;p&gt;No. The integration is backend work. Livewire is convenient for live quote forms, but a standard form or a JavaScript client works equally well.&lt;/p&gt;

&lt;h3&gt;
  
  
  How accurate are the rates?#
&lt;/h3&gt;

&lt;p&gt;Accuracy depends on the data you send and the account credentials used. Wrong dimensions or addresses produce wrong prices, and surcharges may appear later. Validate input and store the original quote.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does an integration like this take?#
&lt;/h3&gt;

&lt;p&gt;A single carrier's rate lookup is usually days of work. Two carriers with a clean abstraction, error handling and tests is more. Labels and tracking add more again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Wrap each carrier behind a common interface and return one normalised quote format.&lt;/li&gt;
&lt;li&gt;Cache OAuth tokens, set timeouts, retry sensibly and call carriers in parallel.&lt;/li&gt;
&lt;li&gt;Decide what happens when a carrier fails before you launch.&lt;/li&gt;
&lt;li&gt;Store every quote shown to a customer, and test with faked responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need rates or full shipping integrated into your application, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/ups-fedex-api-integration-laravel" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>apiintegration</category>
      <category>restapis</category>
      <category>ecommercedevelopment</category>
    </item>
    <item>
      <title>RAG vs Fine-Tuning for a Business Chatbot: Which to Choose</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:12:55 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/rag-vs-fine-tuning-for-a-business-chatbot-which-to-choose-fd8</link>
      <guid>https://dev.to/aqib_javaid/rag-vs-fine-tuning-for-a-business-chatbot-which-to-choose-fd8</guid>
      <description>&lt;p&gt;When a business decides it wants an AI chatbot that "knows our company," the next question is usually technical: should we &lt;strong&gt;fine-tune a model on our data, or use retrieval-augmented generation (RAG)?&lt;/strong&gt; The two are often described as competitors. They solve different problems, and for most business chatbots the answer is clear once you ask the right questions. This is the framework I use when advising on &lt;a href="https://aqibjavaid.site/services/ai-integration-development" rel="noopener noreferrer"&gt;AI integration projects&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The short answer: &lt;strong&gt;use RAG for knowledge, use fine-tuning for behaviour.&lt;/strong&gt; If your goal is for the bot to answer questions about your products, policies or documents correctly, start with RAG.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each approach actually does#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  RAG: look it up, then answer#
&lt;/h3&gt;

&lt;p&gt;In retrieval-augmented generation, your documents are split into chunks and turned into embeddings that are stored in a vector index. When a user asks a question, the system finds the most relevant chunks, adds them to the prompt and asks the model to answer using only that context. The model's weights are not changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fine-tuning: change the model#
&lt;/h3&gt;

&lt;p&gt;Fine-tuning continues training a model on examples you provide. It changes how the model writes or behaves: tone, format, handling of a specific task. It is not a reliable way to teach it a large body of changing facts, and a fine-tuned model can still produce confident wrong answers about those facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions that decide it#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How often does your knowledge change?#
&lt;/h3&gt;

&lt;p&gt;Prices, policies, documentation and product details change. With RAG, you update a document and the bot knows within minutes of re-indexing. With fine-tuning, you must prepare new training data and run training again. If your information changes monthly or faster, RAG is the practical choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do answers need to be traceable?#
&lt;/h3&gt;

&lt;p&gt;For support, compliance or anything where "where did that come from?" matters, RAG lets you show the source passages and link to them. A fine-tuned model cannot cite what it learned, because that knowledge is spread across its weights.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who needs to control what the bot says?#
&lt;/h3&gt;

&lt;p&gt;With RAG, you can restrict the bot to a defined set of documents, add topic rules and handle "I don't know" cleanly. On the &lt;a href="https://aqibjavaid.site/work/henceforward-ai-rag-chatbot-knowledge-base" rel="noopener noreferrer"&gt;Henceforward AI chatbot and knowledge base&lt;/a&gt;, I built an admin suite for brand persona, topic restrictions and AI guardrails around a RAG pipeline that ingests web pages, documents and visual assets. Business owners needed control without involving a developer each time the content changed, which is a RAG strength.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are you trying to change: facts or style?#
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Better fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Answer questions from company documents&lt;/td&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keep answers current as content changes&lt;/td&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cite sources to the user&lt;/td&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Match a specific writing tone or output format&lt;/td&gt;
&lt;td&gt;Fine-tuning (or careful prompting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perform a narrow classification or extraction task at scale&lt;/td&gt;
&lt;td&gt;Fine-tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reduce prompt size for a repeated task&lt;/td&gt;
&lt;td&gt;Fine-tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What can you afford to maintain?#
&lt;/h3&gt;

&lt;p&gt;RAG has ongoing work: ingestion pipelines, chunking, an index, evaluation. Fine-tuning has its own: dataset curation, training runs, evaluation and re-training when the base model changes. Neither is free, but RAG's costs are usually easier to predict for knowledge-heavy bots. For budgeting guidance, see &lt;a href="https://aqibjavaid.site/blog/ai-chatbot-development-cost" rel="noopener noreferrer"&gt;AI chatbot development cost&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with AI Integration &amp;amp; AI-Powered Applications?
&lt;/h3&gt;

&lt;p&gt;Integrate AI into existing products or build AI-powered applications that automate workflows, analyze data, generate content, and improve business operations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/ai-integration-development" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why RAG is the default for business knowledge#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Freshness.&lt;/strong&gt; Update the source, not the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control.&lt;/strong&gt; You decide what the model can see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditability.&lt;/strong&gt; You can log which passages produced each answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower risk of hallucination&lt;/strong&gt; when the prompt requires answers to come from the supplied context. It reduces, but does not eliminate, wrong answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model portability.&lt;/strong&gt; Your knowledge base is yours. You can swap the language model later without retraining.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where RAG goes wrong#
&lt;/h2&gt;

&lt;p&gt;RAG is not magic. Most "the bot gave a wrong answer" reports I debug trace back to retrieval, not the model: poor chunking, missing documents, ambiguous queries, or no instruction on what to do when nothing relevant is found. I cover the diagnosis steps in &lt;a href="https://aqibjavaid.site/blog/rag-chatbot-wrong-answers-fix" rel="noopener noreferrer"&gt;fixing RAG chatbot wrong answers&lt;/a&gt;. The index choice also matters at larger scale, which I compare in &lt;a href="https://aqibjavaid.site/blog/pgvector-hnsw-vs-ivfflat-index" rel="noopener noreferrer"&gt;pgvector HNSW vs IVFFlat&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;💡Tip&lt;/p&gt;

&lt;p&gt;Before you tune anything, build a small set of real questions with known good answers. It is the cheapest way to tell whether a change helped or hurt.&lt;/p&gt;

&lt;h2&gt;
  
  
  When fine-tuning makes sense#
&lt;/h2&gt;

&lt;p&gt;Fine-tuning earns its place when you have a &lt;strong&gt;narrow, repeatable task&lt;/strong&gt; and a clean set of examples, such as extracting fields in a fixed format, classifying tickets into your categories, or writing in a very specific voice at volume. Even then, try careful prompting and a few examples in the prompt first. Fine-tuning adds a training pipeline you have to own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The combined approach#
&lt;/h2&gt;

&lt;p&gt;Many mature systems use both: a fine-tuned or well-prompted model for tone and structure, with RAG supplying the facts. If you are starting out, build RAG first. It gets you to useful faster and tells you where fine-tuning might add value.&lt;/p&gt;

&lt;p&gt;For general background on the technique, the original paper is &lt;a href="https://arxiv.org/abs/2005.11401" rel="noopener noreferrer"&gt;Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks&lt;/a&gt;, and Anthropic's &lt;a href="https://docs.anthropic.com/en/docs/build-with-claude/embeddings" rel="noopener noreferrer"&gt;documentation on embeddings&lt;/a&gt; explains how retrieval fits into an application.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical way to start#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Collect the 20 to 50 questions your users actually ask.&lt;/li&gt;
&lt;li&gt; Gather the documents that contain the answers, and note gaps.&lt;/li&gt;
&lt;li&gt; Build a basic RAG pipeline with source citations.&lt;/li&gt;
&lt;li&gt; Test it against your question list and record failures.&lt;/li&gt;
&lt;li&gt; Add guardrails: scope limits, refusal behaviour and handoff to a human.&lt;/li&gt;
&lt;li&gt; Only consider fine-tuning if the remaining failures are about behaviour, not knowledge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is how production features are built. I talk about the habits that separate demos from reliable systems in &lt;a href="https://aqibjavaid.site/blog/production-grade-ai-features" rel="noopener noreferrer"&gt;production-grade AI features&lt;/a&gt;. On &lt;a href="https://aqibjavaid.site/work/safetyspace" rel="noopener noreferrer"&gt;SafetySpace&lt;/a&gt;, the AI sits inside configurable safety workflows where reliable access to information matters, which is a similar kind of problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can a fine-tuned model replace a knowledge base?#
&lt;/h3&gt;

&lt;p&gt;Not reliably. It may reproduce some facts but cannot guarantee accuracy or point to a source, and it goes stale as your information changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is RAG more expensive than fine-tuning?#
&lt;/h3&gt;

&lt;p&gt;It depends on volume. RAG adds retrieval and larger prompts per request. Fine-tuning adds training and evaluation costs up front. For most business chatbots, RAG is cheaper to launch and easier to keep current.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does RAG stop hallucinations?#
&lt;/h3&gt;

&lt;p&gt;It reduces them by grounding answers in your documents, but it does not remove them. You still need clear "I don't know" behaviour and testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a vector database?#
&lt;/h3&gt;

&lt;p&gt;Not necessarily. For many projects, Postgres with the pgvector extension is enough, and it keeps your stack simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;RAG is for knowledge; fine-tuning is for behaviour.&lt;/li&gt;
&lt;li&gt;Choose RAG when information changes, answers must be sourced or content owners need control.&lt;/li&gt;
&lt;li&gt;Most wrong answers come from retrieval, so test against real questions.&lt;/li&gt;
&lt;li&gt;Start with RAG, and add fine-tuning only for a clearly defined behavioural gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are planning a chatbot and want to choose the right architecture, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;contact me&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/rag-vs-fine-tuning-business-chatbot" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>llm</category>
      <category>aichatbot</category>
      <category>vectorsearch</category>
    </item>
    <item>
      <title>GoHighLevel Stripe Integration: Charge Leads Automatically</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:12:46 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/gohighlevel-stripe-integration-charge-leads-automatically-ddh</link>
      <guid>https://dev.to/aqib_javaid/gohighlevel-stripe-integration-charge-leads-automatically-ddh</guid>
      <description>&lt;p&gt;GoHighLevel is excellent for managing leads, pipelines and follow-up. Billing is where agencies hit its limits. If you sell leads and want to charge each client when a qualified lead is delivered, a flat subscription does not fit, and charging by hand does not scale. This guide explains how a &lt;strong&gt;GoHighLevel Stripe integration&lt;/strong&gt; for pay-per-lead billing works, based on a workflow I built for a lead-generation business. Custom work like this sits within my &lt;a href="https://aqibjavaid.site/services/api-development-integrations" rel="noopener noreferrer"&gt;API development and integrations service&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The core idea: let the client &lt;strong&gt;save a payment method once without paying anything&lt;/strong&gt;, then &lt;strong&gt;charge it automatically&lt;/strong&gt; whenever an event in GoHighLevel says a lead has been delivered. Everything between those two moments needs to be safe, idempotent and visible to both systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with the built-in options#
&lt;/h2&gt;

&lt;p&gt;Native payment links and invoices assume the amount is known up front. Pay-per-lead billing breaks that assumption in three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The amount depends on &lt;strong&gt;how many qualified leads&lt;/strong&gt; are delivered.&lt;/li&gt;
&lt;li&gt;Charges happen &lt;strong&gt;later&lt;/strong&gt;, when the client is not on the page.&lt;/li&gt;
&lt;li&gt;Failures matter: a declined card means a lead was delivered and not paid for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you need a flow that separates "authorise the card" from "take the money", and a link between payment results and the CRM.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture in one picture#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; The client receives a secure link and enters card details on a page that uses Stripe.js, so card numbers never touch your server.&lt;/li&gt;
&lt;li&gt; A &lt;strong&gt;SetupIntent&lt;/strong&gt; saves the card against a Stripe customer, with no charge.&lt;/li&gt;
&lt;li&gt; When a lead is marked as delivered in GoHighLevel, a trigger calls your integration.&lt;/li&gt;
&lt;li&gt; Your service creates an &lt;strong&gt;off-session PaymentIntent&lt;/strong&gt; for the saved card.&lt;/li&gt;
&lt;li&gt; Stripe sends &lt;strong&gt;webhooks&lt;/strong&gt; for success or failure, and your service updates GoHighLevel so the pipeline reflects reality.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the pattern behind the &lt;a href="https://aqibjavaid.site/work/stripe-lead-billing-gohighlevel" rel="noopener noreferrer"&gt;Stripe lead billing workflow I built for a GoHighLevel client&lt;/a&gt;: a secure flow that saves payment methods without an immediate charge, charges them automatically when qualified leads are delivered, and uses webhook-driven status synchronisation for successful and failed payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Save the card with a SetupIntent#
&lt;/h2&gt;

&lt;p&gt;A SetupIntent is Stripe's way to collect a payment method for future use. You create a Stripe customer, create a SetupIntent for that customer, and confirm it on the client side with Stripe.js. Once confirmed, the payment method is attached to the customer.&lt;/p&gt;

&lt;p&gt;Things that matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask for the right &lt;code&gt;usage&lt;/code&gt; (&lt;code&gt;off_session&lt;/code&gt;) so the card can be charged when the client is absent.&lt;/li&gt;
&lt;li&gt;Handle &lt;strong&gt;authentication&lt;/strong&gt; (3D Secure) at setup time, so later charges are less likely to be interrupted.&lt;/li&gt;
&lt;li&gt;Store the Stripe customer ID and payment method ID against the CRM contact, as custom fields or in your own database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stripe documents this flow in its &lt;a href="https://docs.stripe.com/payments/save-and-reuse" rel="noopener noreferrer"&gt;guide to saving payment details&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Trigger charges from GoHighLevel#
&lt;/h2&gt;

&lt;p&gt;GoHighLevel can call an external URL when something happens, for example when an opportunity moves to a "Lead delivered" stage. Use that trigger to send a request to your own endpoint with the contact ID and the amount or lead count. Options include a webhook action in a workflow or an automation tool such as Zapier if you prefer to keep logic outside custom code.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with API Development &amp;amp; Integrations?
&lt;/h3&gt;

&lt;p&gt;Design and integrate reliable APIs connecting your application with third-party platforms, business systems, payment providers, and external data sources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/api-development-integrations" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do not let the CRM decide the amount blindly. Your endpoint should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Verify the request really came from your workflow (a shared secret or signature).&lt;/li&gt;
&lt;li&gt; Look up the contact's saved Stripe customer and payment method.&lt;/li&gt;
&lt;li&gt; Calculate the amount on the server from trusted data.&lt;/li&gt;
&lt;li&gt; Create the PaymentIntent with &lt;code&gt;off_session: true&lt;/code&gt; and &lt;code&gt;confirm: true&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 3: Make charges idempotent#
&lt;/h2&gt;

&lt;p&gt;Automations retry. Workflows fire twice. Networks time out. If your endpoint can be called twice for the same lead, you must never charge twice.&lt;/p&gt;

&lt;p&gt;Use an &lt;strong&gt;idempotency key&lt;/strong&gt; derived from the lead or opportunity ID when creating the PaymentIntent. Stripe will return the original result for repeated requests with the same key. I wrote about the same idea for APIs in general in &lt;a href="https://aqibjavaid.site/blog/laravel-api-idempotency-keys" rel="noopener noreferrer"&gt;idempotency keys in Laravel APIs&lt;/a&gt;, and the principle applies to any stack.&lt;/p&gt;

&lt;p&gt;📌Important&lt;/p&gt;

&lt;p&gt;Record each charge attempt in your own database before calling Stripe. If the call or response is lost, you still have a record to reconcile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Use webhooks as the source of truth#
&lt;/h2&gt;

&lt;p&gt;A synchronous response from Stripe tells you what happened right now. Webhooks tell you what is true. Some payments finish later, some need further action and some fail after initially looking fine.&lt;/p&gt;

&lt;p&gt;Subscribe to events such as &lt;code&gt;payment_intent.succeeded&lt;/code&gt; and &lt;code&gt;payment_intent.payment_failed&lt;/code&gt;, then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verify the signature&lt;/strong&gt; on every webhook using Stripe's signing secret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;De-duplicate events&lt;/strong&gt; by event ID, because Stripe may deliver the same one more than once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update GoHighLevel&lt;/strong&gt; through its API: move the opportunity, set a "Paid" or "Payment failed" tag, or write a custom field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have written a full walkthrough of safe webhook handling in &lt;a href="https://aqibjavaid.site/blog/stripe-webhooks-laravel-verify-dedupe-retry" rel="noopener noreferrer"&gt;Stripe webhooks: verify, dedupe, retry&lt;/a&gt;. The principles are not specific to Laravel, and Stripe's own &lt;a href="https://docs.stripe.com/webhooks" rel="noopener noreferrer"&gt;webhook documentation&lt;/a&gt; covers signature verification in several languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Plan for failed payments#
&lt;/h2&gt;

&lt;p&gt;Failures are not an edge case in pay-per-lead billing. Decide up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What happens when a card is declined?&lt;/strong&gt; Notify the client, pause new lead delivery or both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How is a retry handled?&lt;/strong&gt; Offer a link to update the card, then re-attempt the failed charge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who sees it?&lt;/strong&gt; Your team should get an alert, and the CRM record should show the state, so nobody has to open Stripe to find out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What about disputes and refunds?&lt;/strong&gt; Make sure refunds update the CRM status too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make the payment state visible inside GoHighLevel. A pipeline that shows "Delivered, paid" or "Delivered, payment failed" is what makes the integration useful for the people who work in it daily.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs to build#
&lt;/h2&gt;

&lt;p&gt;A basic version, with one Stripe account, one card-saving page, one trigger and two webhook events, is a small project. It grows with more pricing rules (tiers, discounts, credits), multi-account or multi-currency needs, invoicing and reporting. For general guidance on estimating this kind of work, see &lt;a href="https://aqibjavaid.site/blog/api-integration-cost-timeline" rel="noopener noreferrer"&gt;API integration cost and timeline&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to build this#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If you charge a fixed monthly fee, &lt;strong&gt;Stripe Billing subscriptions&lt;/strong&gt; are simpler and need no custom charge logic.&lt;/li&gt;
&lt;li&gt;If volume is a handful of clients, &lt;strong&gt;manual invoicing&lt;/strong&gt; may be fine until it hurts.&lt;/li&gt;
&lt;li&gt;If you cannot hold customer card data under your compliance comfort level, keep everything with Stripe-hosted components and never store card details yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does GoHighLevel connect to Stripe natively?#
&lt;/h3&gt;

&lt;p&gt;It has a native Stripe connection for its own payment features, such as invoices and products. Charging on a custom trigger like "lead delivered" usually needs extra logic through workflows or custom code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it safe to charge a saved card without the customer present?#
&lt;/h3&gt;

&lt;p&gt;Yes, if the customer authorised future charges when saving the card and you follow Stripe's off-session flow. Be clear in your terms about what will be charged and when.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if a charge needs authentication?#
&lt;/h3&gt;

&lt;p&gt;Off-session payments can fail if the bank demands authentication. Handle the resulting error by sending the customer a link to complete it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I do this with Zapier instead of code?#
&lt;/h3&gt;

&lt;p&gt;For simple flows, yes. For anything involving idempotency, signature checks and reconciliation, custom code is more reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Separate saving the card (SetupIntent) from charging it (off-session PaymentIntent).&lt;/li&gt;
&lt;li&gt;Make the charge endpoint idempotent and calculate amounts on the server.&lt;/li&gt;
&lt;li&gt;Treat Stripe webhooks as the source of truth and sync results back to GoHighLevel.&lt;/li&gt;
&lt;li&gt;Design the failed-payment path before launch, not after the first decline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need a billing workflow like this connected to your CRM, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;contact me&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/gohighlevel-stripe-integration" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>stripe</category>
      <category>webhooks</category>
      <category>apiintegration</category>
      <category>payments</category>
    </item>
    <item>
      <title>Laravel Production Deployment Checklist: 25 Things to Check</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:12:17 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/laravel-production-deployment-checklist-25-things-to-check-1hi6</link>
      <guid>https://dev.to/aqib_javaid/laravel-production-deployment-checklist-25-things-to-check-1hi6</guid>
      <description>&lt;p&gt;Most Laravel launch problems are not exotic. They are a missed environment variable, a queue worker that was never started, a debug page left switched on, or a migration that locked a table. This &lt;strong&gt;Laravel production deployment checklist&lt;/strong&gt; is the list I run through before and after a go-live. It works whether you deploy with a managed tool, a container platform or your own server. If you want help doing it, my &lt;a href="https://aqibjavaid.site/services/cloud-devops-deployment" rel="noopener noreferrer"&gt;cloud and DevOps deployment service&lt;/a&gt; covers exactly this kind of work.&lt;/p&gt;

&lt;p&gt;Use it as a working document: copy it into your repository and tick items off per release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you deploy: environment and configuration#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;APP_ENV=production&lt;/code&gt; and &lt;code&gt;APP_DEBUG=false&lt;/code&gt;.&lt;/strong&gt; Debug mode can leak stack traces, environment values and queries to anyone who triggers an error.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;A unique &lt;code&gt;APP_KEY&lt;/code&gt;&lt;/strong&gt; generated for production and stored outside the repository. Never reuse the development key.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Secrets live in the environment&lt;/strong&gt;, not in committed files. Rotate anything that was ever pushed to a repository.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;APP_URL&lt;/code&gt; is correct&lt;/strong&gt;, including the scheme, so generated links and signed URLs work.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Cache the configuration.&lt;/strong&gt; Run &lt;code&gt;php artisan config:cache&lt;/code&gt;, &lt;code&gt;route:cache&lt;/code&gt;, &lt;code&gt;view:cache&lt;/code&gt; and &lt;code&gt;event:cache&lt;/code&gt; as part of the deploy. After &lt;code&gt;config:cache&lt;/code&gt;, only call &lt;code&gt;env()&lt;/code&gt; inside config files, because it returns null elsewhere.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Optimise the autoloader:&lt;/strong&gt; &lt;code&gt;composer install --no-dev --optimize-autoloader&lt;/code&gt; on the server or in your build.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Laravel docs describe each of these in the &lt;a href="https://laravel.com/docs/deployment" rel="noopener noreferrer"&gt;official deployment guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security checks#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;HTTPS everywhere&lt;/strong&gt;, with HTTP redirected, and &lt;code&gt;Secure&lt;/code&gt; and &lt;code&gt;HttpOnly&lt;/code&gt; cookie flags set.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Only the &lt;code&gt;public&lt;/code&gt; directory is web-reachable.&lt;/strong&gt; Your &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;storage&lt;/code&gt; and &lt;code&gt;vendor&lt;/code&gt; folders must never be served.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Trusted proxies configured&lt;/strong&gt; if you sit behind a load balancer or CDN, otherwise client IPs and HTTPS detection break.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Rate limiting on login and API routes.&lt;/strong&gt; I cover design choices in &lt;a href="https://aqibjavaid.site/blog/laravel-api-rate-limiting-architecture" rel="noopener noreferrer"&gt;API rate limiting architecture&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dependencies audited:&lt;/strong&gt; run &lt;code&gt;composer audit&lt;/code&gt; and update anything with a known vulnerability.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Admin areas protected&lt;/strong&gt; with strong authentication, and ideally restricted by role, network or both.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;⚠️Warning&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;.env&lt;/code&gt; was ever reachable over HTTP, treat every secret in it as compromised and rotate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database and migrations#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Back up before you migrate&lt;/strong&gt;, and confirm you can restore. A backup you have never restored is a hope.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Run migrations with &lt;code&gt;--force&lt;/code&gt;&lt;/strong&gt; in production, as part of the deploy script, not by hand.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Check migrations for locking.&lt;/strong&gt; Adding an index or altering a large table can block writes. Use the safe patterns: add nullable columns first, backfill in batches, then add constraints.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Make migrations backward compatible&lt;/strong&gt; with the code that is currently running, so a rolling deploy does not break mid-flight. The expand-then-contract approach covers it.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Database credentials use a least-privilege user&lt;/strong&gt;, not root.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Queues, scheduler and background work#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Run a process supervisor&lt;/strong&gt; (Supervisor or systemd) for queue workers so they restart after a crash or deploy.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Restart workers on each deploy&lt;/strong&gt; with &lt;code&gt;php artisan queue:restart&lt;/code&gt;, otherwise they keep running old code.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Add the scheduler cron entry:&lt;/strong&gt; &lt;code&gt;* * * * * php /path/artisan schedule:run&lt;/code&gt;. Missing this is a classic launch bug.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Set job timeouts and retry limits&lt;/strong&gt;, and watch the failed jobs table. For heavier workloads I use Redis and Horizon, which give visibility into throughput and failures.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the &lt;a href="https://aqibjavaid.site/work/violerts-nyc-proptech-compliance-saas" rel="noopener noreferrer"&gt;Violerts NYC platform&lt;/a&gt;, the stack includes Redis, Laravel Horizon and asynchronous processing. Any system built that way depends on supervised workers that restart cleanly on deploy, which is why I treat this section of the checklist as non-negotiable.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with Cloud, DevOps &amp;amp; Deployment?
&lt;/h3&gt;

&lt;p&gt;Set up and improve production infrastructure for web applications, including VPS environments, Docker, CI/CD, web servers, migrations, and reliable deployments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/cloud-devops-deployment" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance and caching#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Use a real cache and session driver&lt;/strong&gt; such as Redis in production, not &lt;code&gt;file&lt;/code&gt; on a multi-server setup.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Run OPcache&lt;/strong&gt; with sensible settings, and clear or reset it on deploy.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Serve assets through a CDN&lt;/strong&gt; or at least with long-lived cache headers and hashed filenames.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deploy process, monitoring and rollback#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Make deploys repeatable.&lt;/strong&gt; A script or pipeline should do the same steps each time: pull, install, build, migrate, cache, restart. Doing it by hand invites mistakes. See &lt;a href="https://aqibjavaid.site/blog/laravel-zero-downtime-deployments" rel="noopener noreferrer"&gt;zero-downtime deployments in Laravel&lt;/a&gt; for the release-directory approach with atomic switching.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Beyond the numbered list, these matter just as much:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error tracking&lt;/strong&gt; such as Sentry, so exceptions reach you before customers do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uptime and health checks&lt;/strong&gt; on a route that also tests the database and cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured logs&lt;/strong&gt; sent somewhere you can search. See &lt;a href="https://aqibjavaid.site/blog/laravel-observability-architecture" rel="noopener noreferrer"&gt;observability for Laravel&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rollback plan.&lt;/strong&gt; Know which release to go back to and whether the database changes can be reversed. If a migration cannot be undone, plan a forward fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk and memory alerts.&lt;/strong&gt; Full disks and log files are a common silent failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple deploy sequence#
&lt;/h2&gt;

&lt;p&gt;Here is a minimal pattern that covers the essentials:&lt;/p&gt;

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

&lt;p&gt;Copy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git pull origin main
composer install --no-dev --optimize-autoloader --no-interaction
npm ci &amp;amp;&amp;amp; npm run build
php artisan down --retry=30   # skip if you do atomic releases
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan queue:restart
php artisan up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is a starting point. With zero-downtime tooling, you build into a new release directory and switch a symlink instead of using maintenance mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Post-deploy smoke test#
&lt;/h2&gt;

&lt;p&gt;Right after a release, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Home page, login and one key transaction work.&lt;/li&gt;
&lt;li&gt;A queued job runs to completion.&lt;/li&gt;
&lt;li&gt;A scheduled command appears in the logs.&lt;/li&gt;
&lt;li&gt;No new errors are showing in your tracker.&lt;/li&gt;
&lt;li&gt;Emails send, using a real test address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the &lt;a href="https://aqibjavaid.site/work/henceforward-wordpress-migration-security-optimization" rel="noopener noreferrer"&gt;Henceforward WordPress migration&lt;/a&gt;, moving to a new infrastructure on Scaleway with Laravel Forge meant that the checks around server configuration, security and production optimisation were as important as the application itself. The same discipline applies to a Laravel app: the environment is part of the product.&lt;/p&gt;

&lt;p&gt;If you are still choosing where to host, read my comparison of &lt;a href="https://aqibjavaid.site/blog/laravel-hosting-options-cost" rel="noopener noreferrer"&gt;Laravel hosting options and cost&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I use &lt;code&gt;php artisan down&lt;/code&gt; for every deploy?#
&lt;/h3&gt;

&lt;p&gt;Only if you cannot do atomic releases. Maintenance mode shows users an error page, so for busy sites a symlink-based zero-downtime release is better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does &lt;code&gt;config:cache&lt;/code&gt; break my app?#
&lt;/h3&gt;

&lt;p&gt;Because &lt;code&gt;env()&lt;/code&gt; returns null outside config files once the config is cached. Read environment values in config files and use &lt;code&gt;config()&lt;/code&gt; everywhere else.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should I back up?#
&lt;/h3&gt;

&lt;p&gt;At a frequency matching how much data you can afford to lose. Before every migration, and on a schedule for everything else, with periodic restore tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need Kubernetes?#
&lt;/h3&gt;

&lt;p&gt;Usually not. Many Laravel apps run well on a single well-configured server or a managed platform. Pick complexity when the load demands it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Debug off, secrets safe, config cached, HTTPS on: the basics prevent most launch incidents.&lt;/li&gt;
&lt;li&gt;Back up before migrating, and write migrations that are safe for running code.&lt;/li&gt;
&lt;li&gt;Supervise queue workers, restart them on deploy, and add the scheduler cron.&lt;/li&gt;
&lt;li&gt;Automate the deploy, monitor errors, and know your rollback before you ship.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you would like a second pair of eyes on a launch, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/laravel-production-deployment-checklist" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>deployment</category>
      <category>devops</category>
      <category>productionengineering</category>
    </item>
    <item>
      <title>Web Scraping vs Official API: Which Should You Use?</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:12:08 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/web-scraping-vs-official-api-which-should-you-use-52pc</link>
      <guid>https://dev.to/aqib_javaid/web-scraping-vs-official-api-which-should-you-use-52pc</guid>
      <description>&lt;p&gt;When a business needs data that lives on someone else's website, the first instinct is often "let's scrape it." Sometimes that is right. Often there is an API, export or data feed that gives you the same information with far less risk and far less upkeep. This article gives you a practical way to choose between &lt;strong&gt;web scraping vs an official API&lt;/strong&gt;, based on what I check before I recommend either one to a client. If scraping turns out to be the answer, my &lt;a href="https://aqibjavaid.site/services/web-scraping-data-extraction" rel="noopener noreferrer"&gt;web scraping and data extraction service&lt;/a&gt; covers how I build it.&lt;/p&gt;

&lt;p&gt;The short version: &lt;strong&gt;use the API when one exists and covers what you need; scrape when it does not, and build it as if it will break.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The six questions that decide it#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Does an API exist, and does it cover the fields you need?#
&lt;/h3&gt;

&lt;p&gt;Start here, because it takes ten minutes. Look for developer documentation, a public data portal, an export button or a partner programme. Check the specific fields you need, not just whether an API exists. Many APIs expose a subset of what the website shows. If the API covers your fields, the decision is usually already made.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. What does the provider allow?#
&lt;/h3&gt;

&lt;p&gt;Terms of service, licences and rate limits tell you what the owner permits. An API usually comes with explicit permission. Scraping may be restricted by the site's terms, and the data itself may carry licence or privacy obligations. I go through the legal side in &lt;a href="https://aqibjavaid.site/blog/is-web-scraping-legal-for-business" rel="noopener noreferrer"&gt;is web scraping legal for business&lt;/a&gt;. This is not legal advice, and you should get it for anything sensitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. How stable does it need to be?#
&lt;/h3&gt;

&lt;p&gt;APIs are contracts. They are versioned, documented and usually changed with notice. Websites are not contracts. A redesign can break a scraper overnight with no warning. If a process depends on the data arriving every day, stability points strongly toward the API.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. What will it cost to run, not just to build?#
&lt;/h3&gt;

&lt;p&gt;Compare the full cost over a year:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cost item&lt;/th&gt;
&lt;th&gt;Official API&lt;/th&gt;
&lt;th&gt;Scraping&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build effort&lt;/td&gt;
&lt;td&gt;Usually lower, structured responses&lt;/td&gt;
&lt;td&gt;Higher, parsing and edge cases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fees&lt;/td&gt;
&lt;td&gt;May charge per request or by plan&lt;/td&gt;
&lt;td&gt;Usually free to fetch, but infrastructure is yours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;Low, follow version notices&lt;/td&gt;
&lt;td&gt;Ongoing, fixes when pages change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Light&lt;/td&gt;
&lt;td&gt;Heavier if headless browsers or proxies are needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring&lt;/td&gt;
&lt;td&gt;Basic error checks&lt;/td&gt;
&lt;td&gt;Needs validation and alerts for silent breakage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An API with a monthly fee can still be cheaper than a free scraper once you count maintenance time.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. How fresh and how complete must the data be?#
&lt;/h3&gt;

&lt;p&gt;APIs often give real-time or near-real-time data and clean identifiers. Scraped pages may lag, paginate oddly or hide records behind filters. If you need complete history or reliable IDs to match records, an API is easier to trust.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with Web Scraping &amp;amp; Data Extraction?
&lt;/h3&gt;

&lt;p&gt;Architect resilient, high-volume automated scrapers and data pipelines that bypass anti-bot protections, extract structured public data, and deliver clean, validated feeds to your database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/web-scraping-data-extraction" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. What happens if access is cut?#
&lt;/h3&gt;

&lt;p&gt;APIs can be withdrawn or repriced. Scrapers can be blocked. Either way, have a fallback plan. For anything critical, I design the integration so that the data source can be swapped without rewriting the rest of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the API route looks like in practice#
&lt;/h2&gt;

&lt;p&gt;Integrating a real API is still real engineering. On the &lt;a href="https://aqibjavaid.site/work/expreco-logistics-quote-system" rel="noopener noreferrer"&gt;EXPRECO logistics quote system&lt;/a&gt;, I integrated UPS and FedEx APIs into a Laravel and Livewire application to calculate real-time shipping rates from package details and destination. That is exactly the kind of data you would never want to scrape from a carrier's website: it is dynamic, account-specific and dependent on rules you cannot see. The official APIs were the only sensible route.&lt;/p&gt;

&lt;p&gt;The work in an API integration is mostly in the edges: authentication, rate limits, error handling, retries and mapping the provider's model to yours. I list the practices I rely on in &lt;a href="https://aqibjavaid.site/blog/third-party-api-integration-best-practices" rel="noopener noreferrer"&gt;third-party API integration best practices&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When scraping is the right answer#
&lt;/h2&gt;

&lt;p&gt;Scraping earns its place when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No API exists&lt;/strong&gt; for data that is legitimately public.&lt;/li&gt;
&lt;li&gt;The API is &lt;strong&gt;incomplete&lt;/strong&gt;, missing fields that the public page shows.&lt;/li&gt;
&lt;li&gt;The API is &lt;strong&gt;priced out of reach&lt;/strong&gt; for your use, and the public data is allowed to be collected.&lt;/li&gt;
&lt;li&gt;You are collecting from &lt;strong&gt;many different sources&lt;/strong&gt; that each lack a consistent API, such as local government portals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last case is common. On the &lt;a href="https://aqibjavaid.site/work/violerts-nyc-proptech-compliance-saas" rel="noopener noreferrer"&gt;Violerts NYC compliance platform&lt;/a&gt;, data comes from multiple municipal sources with different formats, and the ingestion layer needed to handle that variety. I wrote about the engineering that makes this work in &lt;a href="https://aqibjavaid.site/blog/resilient-web-scraping-system-government-data-sources" rel="noopener noreferrer"&gt;a resilient scraping system for government data sources&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you do scrape, assume it will break and design for it: validation of every record, alerts when output shape changes, retries, and isolation between sources. &lt;a href="https://aqibjavaid.site/blog/custom-web-scraper-development-cost" rel="noopener noreferrer"&gt;Cost estimates for a custom scraper&lt;/a&gt; depend heavily on how much of that resilience you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  A hybrid approach often wins#
&lt;/h2&gt;

&lt;p&gt;You are not forced to pick one. Common patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API first, scraper as gap-filler&lt;/strong&gt; for fields the API omits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scraper as a prototype&lt;/strong&gt; while you apply for API access, then switch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Official bulk export plus a light scraper&lt;/strong&gt; for the freshest records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using several sources also gives you a way to cross-check data, which helps quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical steps before you commission anything#
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Search for developer docs, data portals and export options for the target.&lt;/li&gt;
&lt;li&gt; List the exact fields you need and check each against the API.&lt;/li&gt;
&lt;li&gt; Read the terms of service and any licence for the data.&lt;/li&gt;
&lt;li&gt; Estimate the 12-month cost for each route, including maintenance.&lt;/li&gt;
&lt;li&gt; Decide how you will detect and handle a source going away.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can check the target site's crawling rules via its robots.txt file. &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots/intro" rel="noopener noreferrer"&gt;Google explains how robots.txt works&lt;/a&gt;, and the format is a convention, not a legal permission, so treat it as one input and not the whole answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is scraping cheaper than using an API?#
&lt;/h3&gt;

&lt;p&gt;Not necessarily. Scraping is free to fetch but expensive to maintain. Once you add monitoring and fixes, a paid API is often cheaper over a year.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a website block my scraper?#
&lt;/h3&gt;

&lt;p&gt;Yes, and sites often do. That is one more reason to prefer an API when you depend on the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is using an undocumented internal endpoint the same as an API?#
&lt;/h3&gt;

&lt;p&gt;No. It may return clean JSON, which helps, but it is not a supported contract and can change without notice. Treat it as scraping for risk purposes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should I ask a developer before they start?#
&lt;/h3&gt;

&lt;p&gt;Ask which route they recommend and why, how failures will be detected, and who maintains it afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check for an official API or data feed first; it takes minutes and can save months.&lt;/li&gt;
&lt;li&gt;Compare 12-month cost, not just build cost.&lt;/li&gt;
&lt;li&gt;Scrape only legitimate public data, and design as if the source will change.&lt;/li&gt;
&lt;li&gt;A hybrid of API plus scraper is often the most robust option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are unsure which route fits your data need, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; and I will look at the sources with you, or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;send me the details&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/web-scraping-vs-api" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>apiintegration</category>
      <category>dataengineering</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How Much Does a Custom Web Scraper Cost to Build?</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:11:38 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/how-much-does-a-custom-web-scraper-cost-to-build-12m7</link>
      <guid>https://dev.to/aqib_javaid/how-much-does-a-custom-web-scraper-cost-to-build-12m7</guid>
      <description>&lt;p&gt;"How much does a custom web scraper cost?" is one of the most common questions I get, and the only honest first answer is: it depends on five things that you can mostly list before you talk to a developer. A scraper that pulls one public page into a spreadsheet is a few hours of work. A system that collects data from a dozen sources every day, fixes itself when a site changes and delivers clean records to your database is a small product.&lt;/p&gt;

&lt;p&gt;This guide explains what drives &lt;strong&gt;custom web scraper development cost&lt;/strong&gt;, so you can scope the job properly, compare quotes fairly and avoid paying for the wrong thing. I will not quote prices I cannot back up, and if you want a scoped estimate for your own sources, that is what my &lt;a href="https://aqibjavaid.site/services/web-scraping-data-extraction" rel="noopener noreferrer"&gt;web scraping and data extraction service&lt;/a&gt; starts with. I will show you where the effort goes, because the effort is what you are paying for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five cost drivers#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. How many sources and how different they are#
&lt;/h3&gt;

&lt;p&gt;Each website is its own small project. Ten pages on one site share most of the work. Ten sites mean ten sets of selectors, ten sets of edge cases and ten things that can break. A useful rule: &lt;strong&gt;cost scales with the number of distinct sources far more than with the number of records.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. How the data is delivered#
&lt;/h3&gt;

&lt;p&gt;A plain HTML page that returns the data in the response is the cheapest case. Sites that build everything in the browser with JavaScript need a headless browser such as Playwright, which is slower, heavier and more fragile. Sometimes the site calls a JSON endpoint behind the scenes, and using that endpoint is both cheaper and more reliable than parsing pages. Part of any good scoping exercise is simply looking at the network traffic to find the cheapest honest way to get the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Defences and access rules#
&lt;/h3&gt;

&lt;p&gt;Login walls, rate limits, CAPTCHAs and bot detection all add work, and some should add caution. Before building anything I check the site's terms and what the data actually is. I covered the legal side in &lt;a href="https://aqibjavaid.site/blog/is-web-scraping-legal-for-business" rel="noopener noreferrer"&gt;is web scraping legal for business&lt;/a&gt;. If an official API or data feed exists, it is often the better choice, because it is stable and explicitly allowed.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Volume and frequency#
&lt;/h3&gt;

&lt;p&gt;Fetching 500 pages once a week is trivial. Fetching 500,000 pages daily needs queues, concurrency control, retries, proxies where permitted, storage and monitoring. Frequency matters too: a daily job needs to finish reliably before the next one starts.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. What happens after the scrape#
&lt;/h3&gt;

&lt;p&gt;Raw HTML is not a deliverable. The real work is often cleaning, de-duplicating, matching records to your existing data, and detecting what changed. If you want to know when something changes rather than just receiving a dump, see my notes on &lt;a href="https://aqibjavaid.site/blog/scraped-data-change-detection" rel="noopener noreferrer"&gt;change detection for scraped data&lt;/a&gt;. A pipeline with validation and alerts costs more than a script, and it is the thing that keeps the data trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three typical scopes#
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;What it looks like&lt;/th&gt;
&lt;th&gt;Where the effort goes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;One-off extraction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A single site, run once or occasionally, output as CSV&lt;/td&gt;
&lt;td&gt;Finding the right data source, cleaning output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recurring feed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A few sources, scheduled runs, data loaded into your database&lt;/td&gt;
&lt;td&gt;Scheduling, retries, de-duplication, basic monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data product&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Many sources, change tracking, alerts, an admin view, an API&lt;/td&gt;
&lt;td&gt;Architecture, resilience, ongoing maintenance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are categories, not price points. A developer who gives you a number without asking which of the three you need has not understood the job.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with Web Scraping &amp;amp; Data Extraction?
&lt;/h3&gt;

&lt;p&gt;Architect resilient, high-volume automated scrapers and data pipelines that bypass anti-bot protections, extract structured public data, and deliver clean, validated feeds to your database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/web-scraping-data-extraction" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability is the expensive part#
&lt;/h2&gt;

&lt;p&gt;Anyone can get a scraper to work once. What you pay a senior engineer for is making it keep working. Sites change markup, move endpoints and throttle clients. A production scraper needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retries with back-off, so a temporary failure does not lose data;&lt;/li&gt;
&lt;li&gt;validation, so a changed page produces an alert instead of a column of empty values;&lt;/li&gt;
&lt;li&gt;logging that shows which source failed and why;&lt;/li&gt;
&lt;li&gt;isolation, so one broken source does not stop the others.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the &lt;a href="https://aqibjavaid.site/work/violerts-nyc-proptech-compliance-saas" rel="noopener noreferrer"&gt;Violerts NYC compliance platform&lt;/a&gt;, I worked on multi-agency data ingestion with asynchronous scraping and real-time alerts on a Laravel backend. In that kind of system the scraping itself is only one layer. Most of the design effort goes into what surrounds it: queues, error handling and making failures visible. I wrote about the same problem in &lt;a href="https://aqibjavaid.site/blog/resilient-web-scraping-system-government-data-sources" rel="noopener noreferrer"&gt;building a resilient scraping system for government data sources&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;ℹ️Note&lt;/p&gt;

&lt;p&gt;If a vendor promises a "set and forget" scraper, treat that as a warning. Websites change, so any scraper that matters needs someone responsible for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to budget after launch#
&lt;/h2&gt;

&lt;p&gt;Ongoing cost is the number people forget. Plan for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance time&lt;/strong&gt; when a source changes. The more sources, the more frequent this is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure:&lt;/strong&gt; servers or containers, storage, and proxy or browser services if they are needed and permitted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring:&lt;/strong&gt; someone, or something, has to look at the alerts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A reasonable approach is to treat the first build and the first few months of upkeep as one budget. For a general view of how to think about running costs, see &lt;a href="https://aqibjavaid.site/blog/software-maintenance-cost-what-to-budget" rel="noopener noreferrer"&gt;what to budget for software maintenance&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to get a fair quote#
&lt;/h2&gt;

&lt;p&gt;Send a prospective developer a short brief with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The list of source URLs, and which fields you need from each.&lt;/li&gt;
&lt;li&gt; How often you need fresh data, and how fast it must arrive.&lt;/li&gt;
&lt;li&gt; Where the data should end up (spreadsheet, database, API, dashboard).&lt;/li&gt;
&lt;li&gt; What you will do if a source disappears or blocks access.&lt;/li&gt;
&lt;li&gt; Whether you have checked that you are allowed to collect it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With that, you can ask for a fixed-scope first phase, usually one or two sources end to end, then expand. It shows you the real behaviour of the sources before you commit to the whole project.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to build a scraper#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An official &lt;strong&gt;API, export or data feed&lt;/strong&gt; exists. Use it.&lt;/li&gt;
&lt;li&gt;The data is &lt;strong&gt;licensed&lt;/strong&gt; and scraping would breach the licence. Buy access instead.&lt;/li&gt;
&lt;li&gt;You need it &lt;strong&gt;once&lt;/strong&gt;, from a small number of pages. Copying manually or using a no-code tool may be cheaper.&lt;/li&gt;
&lt;li&gt;The source is &lt;strong&gt;personal data&lt;/strong&gt; and you have no lawful basis to collect it. Do not scrape it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots/intro" rel="noopener noreferrer"&gt;Google's own guidance on robots.txt&lt;/a&gt; to understand how sites signal their crawling rules, and read the target site's terms before you plan anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is a cheap scraper from a freelance marketplace a good idea?#
&lt;/h3&gt;

&lt;p&gt;It can be for a one-off. For anything recurring, the cost is in reliability and maintenance, not the first working version. Ask how failures will be detected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use a scraping API service instead?#
&lt;/h3&gt;

&lt;p&gt;Sometimes. Managed services handle proxies and browsers for you, but you still pay per request and still need parsing, validation and storage. Whether it is cheaper depends on your volume.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does a recurring scraper take to build?#
&lt;/h3&gt;

&lt;p&gt;A single-source recurring feed can often be done in days. A multi-source data product takes weeks, mostly spent on resilience and data quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who owns the code?#
&lt;/h3&gt;

&lt;p&gt;You should. Ask for the source code, deployment notes and documentation as part of the engagement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cost follows the number of distinct sources, the delivery method, defences, volume and the post-scrape pipeline.&lt;/li&gt;
&lt;li&gt;Reliability and monitoring are what you are really paying for.&lt;/li&gt;
&lt;li&gt;Budget for maintenance as part of the project, not as an afterthought.&lt;/li&gt;
&lt;li&gt;Check legality and look for an official API before building anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a list of sources and want a scoped first phase, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;contact me&lt;/a&gt;. You can also read more about my &lt;a href="https://aqibjavaid.site/services/web-scraping-data-extraction" rel="noopener noreferrer"&gt;web scraping and data extraction service&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/custom-web-scraper-development-cost" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>dataengineering</category>
      <category>datapipelines</category>
      <category>softwareestimation</category>
    </item>
    <item>
      <title>Legacy PHP Application Modernization Cost: What to Expect</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:11:28 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/legacy-php-application-modernization-cost-what-to-expect-4bmn</link>
      <guid>https://dev.to/aqib_javaid/legacy-php-application-modernization-cost-what-to-expect-4bmn</guid>
      <description>&lt;p&gt;If you own a PHP application that has been running for years, you have probably been quoted a number for "modernizing" it and wondered what is inside that number. The honest answer is that &lt;strong&gt;legacy PHP application modernization cost&lt;/strong&gt; depends far more on how the system is built than on how big it is. Two applications with the same number of screens can differ by a factor of three or four in effort.&lt;/p&gt;

&lt;p&gt;This article explains what actually moves the price, how I size a modernization before anyone commits budget, and how to phase the work so you are never betting everything on a single estimate. I am not going to give you a single magic figure, because any number without an audit behind it is a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "modernization" actually means#
&lt;/h2&gt;

&lt;p&gt;People use the word for very different jobs, and the price follows the job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version upgrade:&lt;/strong&gt; moving to a supported PHP version and a current framework release, with the existing structure kept.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactor in place:&lt;/strong&gt; adding tests, extracting business logic from templates, and cleaning up the data layer without changing what users see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strangler-style migration:&lt;/strong&gt; replacing the application piece by piece behind the same URLs, usually onto Laravel, while the old code keeps running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full rewrite:&lt;/strong&gt; building a new system and cutting over.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most buyers want the outcome of a rewrite at the price of an upgrade. The useful question is which of these four your business problem actually needs. If the pain is that the server runs an unsupported PHP version, you do not need a rewrite. If the pain is that every change breaks something else, an upgrade alone will not fix it. This is the kind of question my &lt;a href="https://aqibjavaid.site/services/legacy-application-modernization" rel="noopener noreferrer"&gt;legacy application modernization service&lt;/a&gt; starts with. I cover that decision in more depth in &lt;a href="https://aqibjavaid.site/blog/rewrite-vs-refactor-legacy-application" rel="noopener noreferrer"&gt;rewrite vs refactor for a legacy application&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five factors that drive cost#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Test coverage (or the lack of it)#
&lt;/h3&gt;

&lt;p&gt;This is the biggest single driver. An application with a decent automated test suite can be upgraded and refactored safely, because the tests tell you what broke. An application with none means every change needs manual verification, and manual verification is slow and expensive. Often the first piece of paid work should be building characterization tests around the riskiest flows, such as checkout, billing or anything that moves money.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. How logic is organised#
&lt;/h3&gt;

&lt;p&gt;Business rules buried in templates, in SQL strings, or in a 4,000-line include file are expensive to move. Code that already has clear boundaries (even if the style is dated) costs much less. When I audit a system I look for where the rules live, not how pretty the code is.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The database#
&lt;/h3&gt;

&lt;p&gt;Schemas that have grown without constraints, with duplicated data and unclear ownership, create most of the nasty surprises. Migrating code is predictable. Migrating data you do not fully understand is not. Reviewing the schema early is the cheapest risk reduction available.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Dependencies and integrations#
&lt;/h3&gt;

&lt;p&gt;Old Composer packages that were abandoned years ago, payment gateways on retired API versions, and hand-rolled integrations all need attention. Each external system is a small project of its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Downtime tolerance#
&lt;/h3&gt;

&lt;p&gt;If the business can accept a maintenance window, cutover is simple. If it cannot, you need parallel running, feature flags or a strangler approach, and that adds engineering time.&lt;/p&gt;

&lt;p&gt;🛠 Engineering Service&lt;/p&gt;

&lt;h3&gt;
  
  
  Need specialized help with Legacy Application Modernization?
&lt;/h3&gt;

&lt;p&gt;Modernize aging PHP and Laravel applications without disrupting existing business operations, improving architecture, performance, security, and maintainability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;Discuss Architecture →&lt;/a&gt;&lt;a href="https://aqibjavaid.site/services/legacy-application-modernization" rel="noopener noreferrer"&gt;About this service&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I size a modernization before quoting it#
&lt;/h2&gt;

&lt;p&gt;A reliable estimate comes from an audit, not from a sales call. On the &lt;a href="https://aqibjavaid.site/work/jamaica-freight-forwarding-core-php-audit" rel="noopener noreferrer"&gt;freight-forwarding platform audit&lt;/a&gt;, I was brought in to review a nearly finished Core PHP and MySQL system before further development. The work was understanding first: auditing operational workflows, reviewing the database structure, assessing frontend maintainability, mapping dependencies and evaluating configuration and environment governance, then assessing the path towards a future Laravel migration. The point of that phase was to replace assumptions with a written map.&lt;/p&gt;

&lt;p&gt;A good audit typically produces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; An inventory of modules, routes and external integrations.&lt;/li&gt;
&lt;li&gt; A risk list ranked by business impact, not by code ugliness.&lt;/li&gt;
&lt;li&gt; A proposed phase plan with a rough effort band for each phase.&lt;/li&gt;
&lt;li&gt; A clear list of what is out of scope.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ask for this before you accept a fixed quote. If a vendor quotes a firm price without seeing the code, either the price includes a large safety margin or the scope will change later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase the work so the budget stays honest#
&lt;/h2&gt;

&lt;p&gt;Instead of one big project, split the engagement into stages with a decision point after each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Typical focus&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0. Audit&lt;/td&gt;
&lt;td&gt;Read-only review of code, database, hosting&lt;/td&gt;
&lt;td&gt;Written map, risks, phased estimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1. Stabilise&lt;/td&gt;
&lt;td&gt;PHP version, security patches, backups, monitoring&lt;/td&gt;
&lt;td&gt;A system that is safe to keep running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Safety net&lt;/td&gt;
&lt;td&gt;Tests around critical flows, CI pipeline&lt;/td&gt;
&lt;td&gt;Confidence to change things&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Modernise&lt;/td&gt;
&lt;td&gt;Framework upgrade or incremental migration&lt;/td&gt;
&lt;td&gt;Maintainable core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Improve&lt;/td&gt;
&lt;td&gt;New features on the new foundation&lt;/td&gt;
&lt;td&gt;Business value&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can stop after any phase and still have something useful. That is the main protection against a modernization that runs over budget and delivers nothing until the end.&lt;/p&gt;

&lt;p&gt;💡Tip&lt;/p&gt;

&lt;p&gt;If a quote jumps straight to phase 3 or 4 without phases 0 to 2, ask what the plan is for verifying that behaviour did not change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where budgets usually go wrong#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treating the audit as optional.&lt;/strong&gt; Skipping it moves the discovery cost into the build, where it is more expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changing scope mid-flight.&lt;/strong&gt; Adding features during a migration doubles the testing burden.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Underestimating data work.&lt;/strong&gt; Cleaning and migrating data is often a quarter or more of the effort and rarely appears in first estimates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No owner on the business side.&lt;/strong&gt; Decisions about which legacy behaviours to keep need someone who can answer quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting the run cost.&lt;/strong&gt; A modernized system still needs hosting, monitoring and maintenance. I wrote about that in &lt;a href="https://aqibjavaid.site/blog/software-maintenance-cost-what-to-budget" rel="noopener noreferrer"&gt;what to budget for software maintenance&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When modernization is the wrong answer#
&lt;/h2&gt;

&lt;p&gt;Sometimes the right call is to leave the system alone. If the application is stable, low-risk, rarely changed and on a supported PHP version, spending money to rewrite it creates risk without business benefit. Put the budget into backups, monitoring and security patching instead. Modernize when the code is blocking something you care about: shipping features, passing a security review, hiring developers, or integrating with newer systems.&lt;/p&gt;

&lt;p&gt;Also check the version situation first. The &lt;a href="https://www.php.net/supported-versions.php" rel="noopener noreferrer"&gt;PHP supported versions page&lt;/a&gt; shows which releases still get security fixes, and the &lt;a href="https://laravel.com/docs/releases" rel="noopener noreferrer"&gt;Laravel release notes&lt;/a&gt; show the support window for each framework version. If you are far behind, an upgrade path such as the one in my &lt;a href="https://aqibjavaid.site/blog/laravel-upgrade-checklist" rel="noopener noreferrer"&gt;Laravel upgrade checklist&lt;/a&gt; is often cheaper than people expect, because it is mostly mechanical. Tools like &lt;a href="https://getrector.com/" rel="noopener noreferrer"&gt;Rector&lt;/a&gt; automate a good share of the syntax changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions#
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How long does a legacy PHP modernization take?#
&lt;/h3&gt;

&lt;p&gt;It depends on the route. A version upgrade with good tests can take weeks. A staged migration of a business-critical system usually takes months, because the old and new code run side by side. An audit will give you a realistic band.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can the site stay online during modernization?#
&lt;/h3&gt;

&lt;p&gt;Yes, if the work is planned for it. The strangler pattern, described well on &lt;a href="https://martinfowler.com/bliki/StranglerFigApplication.html" rel="noopener noreferrer"&gt;Martin Fowler's site&lt;/a&gt;, replaces functionality gradually so users never see a cutover day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Laravel the right target?#
&lt;/h3&gt;

&lt;p&gt;Often, but not always. Laravel suits most business applications because of its conventions, queue system and ecosystem. The right target is whatever your team can maintain. I walk through one real move from Core PHP in &lt;a href="https://aqibjavaid.site/blog/migrating-legacy-core-php-platform-to-laravel" rel="noopener noreferrer"&gt;migrating a legacy Core PHP platform to Laravel&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I rewrite instead?#
&lt;/h3&gt;

&lt;p&gt;Only if the existing system cannot be understood or safely changed and the business logic is small enough to re-specify. Rewrites hide risk until late in the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cost is driven by test coverage, code organisation, data quality, integrations and downtime tolerance, not by page count.&lt;/li&gt;
&lt;li&gt;Pay for an audit first. It converts guesses into a phased estimate.&lt;/li&gt;
&lt;li&gt;Phase the work so you can stop at any stage with something usable.&lt;/li&gt;
&lt;li&gt;Do not modernize stable, low-risk systems for the sake of it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a sized, phased plan for your own application, you can &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; or &lt;a href="https://aqibjavaid.site/contact" rel="noopener noreferrer"&gt;send the details through the contact page&lt;/a&gt;. That is exactly how an engagement with me begins.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/legacy-php-modernization-cost" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>legacymigration</category>
      <category>php</category>
      <category>applicationmodernization</category>
      <category>technicaldebt</category>
    </item>
    <item>
      <title>Why Your RAG Chatbot Gives Wrong Answers (and How to Fix It)</title>
      <dc:creator>Aqib Javaid</dc:creator>
      <pubDate>Thu, 22 Oct 2026 08:46:52 +0000</pubDate>
      <link>https://dev.to/aqib_javaid/why-your-rag-chatbot-gives-wrong-answers-and-how-to-fix-it-1kgg</link>
      <guid>https://dev.to/aqib_javaid/why-your-rag-chatbot-gives-wrong-answers-and-how-to-fix-it-1kgg</guid>
      <description>&lt;p&gt;You built a chatbot on your own documents, and it still answers with confident nonsense. Customers get wrong prices, invented policies or a polite "I don't know" when the answer is on your website. This is the most common complaint I hear about &lt;strong&gt;RAG chatbot wrong answers&lt;/strong&gt;, and the good news is that most of the time the model is not the problem. The retrieval and the rules around it are.&lt;/p&gt;

&lt;p&gt;I build and tune these systems as part of &lt;a href="https://aqibjavaid.site/services/ai-integration-development" rel="noopener noreferrer"&gt;AI integration and AI-powered applications&lt;/a&gt; work. This guide walks through why a retrieval-augmented chatbot gives bad answers and the fixes I try, in the order I try them.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, understand how a RAG chatbot answers
&lt;/h2&gt;

&lt;p&gt;RAG stands for retrieval-augmented generation. When a user asks a question, the system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Searches your content for the passages most related to the question&lt;/li&gt;
&lt;li&gt;Passes those passages to the language model along with the question&lt;/li&gt;
&lt;li&gt;Asks the model to answer using that material&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That means a wrong answer can come from three places: &lt;strong&gt;the right passage was never found&lt;/strong&gt;, &lt;strong&gt;the passage was found but was poor or incomplete&lt;/strong&gt;, or &lt;strong&gt;the model ignored or went beyond the material it was given&lt;/strong&gt;. Fixing the wrong layer wastes weeks, so diagnose first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Find out which layer is failing
&lt;/h2&gt;

&lt;p&gt;Take 20 to 30 real questions that the bot got wrong. For each one, log what was retrieved and look at it yourself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Right answer not in the retrieved passages:&lt;/strong&gt; a retrieval problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right answer present, but the bot answered wrongly:&lt;/strong&gt; a prompt or generation problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right answer not in your content at all:&lt;/strong&gt; a content problem. No technique fixes missing information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This simple audit usually shows that one layer causes most of the failures. Everything below maps to one of these three layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing retrieval problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Chunk by meaning, not by character count
&lt;/h3&gt;

&lt;p&gt;Splitting documents into fixed-size pieces often cuts a policy in half or separates a heading from its answer. Split on headings, paragraphs and list boundaries, keep a little overlap, and store the page title and section name with each chunk so the passage carries its own context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean the source content
&lt;/h3&gt;

&lt;p&gt;Navigation menus, cookie banners, repeated footers and duplicated pages pollute search results. Strip boilerplate before indexing and remove outdated pages, so the bot doesn't find an old price list that outranks the new one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combine keyword and semantic search
&lt;/h3&gt;

&lt;p&gt;Semantic search finds related meaning, but it can miss exact terms such as product codes, names and error messages. Combining it with keyword search (hybrid search) and merging the results catches both kinds of question. If you store vectors in PostgreSQL, the &lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector extension&lt;/a&gt; works alongside normal full-text search, and my comparison of &lt;a href="https://aqibjavaid.site/blog/pgvector-hnsw-vs-ivfflat-index" rel="noopener noreferrer"&gt;HNSW and IVFFlat indexes&lt;/a&gt; covers the index choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieve more, then rerank
&lt;/h3&gt;

&lt;p&gt;Fetch a wider set of candidates, then use a reranking step to pick the few most relevant passages for the model. This often improves answer quality without changing anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rewrite follow-up questions
&lt;/h3&gt;

&lt;p&gt;Users ask things like "what about the cheaper one?" A search for that phrase finds nothing useful. Before searching, rewrite follow-ups into standalone questions using the conversation so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing prompt and generation problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tell the model to stay inside the material
&lt;/h3&gt;

&lt;p&gt;Instruct the model to answer only from the supplied passages and to say so when they don't contain the answer. Anthropic's guidance on &lt;a href="https://platform.claude.com/docs/claude/docs/minimizing-hallucinations" rel="noopener noreferrer"&gt;minimizing hallucinations&lt;/a&gt; describes techniques such as allowing the model to say it doesn't know and asking it to ground claims in quotes from the source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Require citations
&lt;/h3&gt;

&lt;p&gt;Make the bot show which page or document each answer came from. Citations let users verify, and they let you see at a glance when an answer is not supported by a source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a "no good match" threshold
&lt;/h3&gt;

&lt;p&gt;If the best retrieved passage is only weakly related, don't force an answer. Return a fallback such as offering to connect the user with a person. A bot that says "I'm not sure" at the right moment is more trusted than one that always answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set topic limits and a persona
&lt;/h3&gt;

&lt;p&gt;Define what the bot may and may not discuss, and how it should speak. On the Henceforward platform I built, the admin suite included controls for brand persona, topic restrictions and AI guardrails, so non-developers could adjust behaviour without a deploy. See the &lt;a href="https://aqibjavaid.site/work/henceforward-ai-rag-chatbot-knowledge-base" rel="noopener noreferrer"&gt;Henceforward RAG chatbot case study&lt;/a&gt; for how that was structured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing content problems
&lt;/h2&gt;

&lt;p&gt;If the answer isn't in your content, no setting will conjure it. Look at the questions the bot can't answer and treat them as a to-do list for your knowledge base:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add pages for questions customers ask repeatedly&lt;/li&gt;
&lt;li&gt;Make pricing, policies and hours explicit and dated&lt;/li&gt;
&lt;li&gt;Remove contradictory versions of the same information&lt;/li&gt;
&lt;li&gt;Put the answer near the question it belongs to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many "AI problems" turn out to be documentation problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a small evaluation set
&lt;/h2&gt;

&lt;p&gt;The single most useful habit is a fixed list of test questions with the answers you expect. Run it after every change to chunking, search or prompts, and compare results. Without it, you fix one question and silently break three others.&lt;/p&gt;

&lt;p&gt;A good starter set:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;30 to 50 real customer questions, with the expected answer or source page&lt;/li&gt;
&lt;li&gt;A handful of questions the bot should refuse or hand off&lt;/li&gt;
&lt;li&gt;A few tricky cases: ambiguous wording, outdated information, follow-up questions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Re-run it whenever you change anything, and add every new failure you find.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the bot in production
&lt;/h2&gt;

&lt;p&gt;Log each question, the passages retrieved, the answer and any user feedback. Review a sample every week. Thumbs-down ratings and "I don't know" responses point you straight to content gaps and retrieval failures. My article on &lt;a href="https://aqibjavaid.site/blog/production-grade-ai-features" rel="noopener noreferrer"&gt;making AI features production-ready&lt;/a&gt; covers the logging, cost controls and failure handling that keep a bot trustworthy after launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  A realistic order of work
&lt;/h2&gt;

&lt;p&gt;If you have limited time, this is the order I would follow, because each step is cheap and builds on the last:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the test question set and record the current score&lt;/li&gt;
&lt;li&gt;Clean and re-chunk the content, then re-run the test set&lt;/li&gt;
&lt;li&gt;Add hybrid search and reranking, then re-run&lt;/li&gt;
&lt;li&gt;Tighten the prompt, add citations and a no-match fallback, then re-run&lt;/li&gt;
&lt;li&gt;Fill content gaps found in the failed questions&lt;/li&gt;
&lt;li&gt;Start the weekly production review&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Changing one thing at a time, and measuring after each change, is what separates steady improvement from guesswork. It also gives you evidence to show stakeholders that answer quality is actually improving.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it's still wrong: check the basics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Is the content actually indexed, or did ingestion fail silently?&lt;/li&gt;
&lt;li&gt;Are you searching the right version or language of the content?&lt;/li&gt;
&lt;li&gt;Is the context window being filled with too many weak passages?&lt;/li&gt;
&lt;li&gt;Did a prompt change accidentally loosen the rules?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are planning a chatbot from scratch, my guide to &lt;a href="https://aqibjavaid.site/blog/ai-chatbot-development-cost" rel="noopener noreferrer"&gt;AI chatbot development cost&lt;/a&gt; explains what drives the budget, and &lt;a href="https://aqibjavaid.site/blog/building-ai-powered-chatbot-laravel-openai-database" rel="noopener noreferrer"&gt;building an AI chatbot with Laravel and OpenAI&lt;/a&gt; shows one implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Diagnose first: was the answer not retrieved, poorly retrieved or mishandled by the model?&lt;/li&gt;
&lt;li&gt;Better chunking, clean content, hybrid search and reranking fix most retrieval failures.&lt;/li&gt;
&lt;li&gt;Ground the model in the sources, require citations and allow "I don't know".&lt;/li&gt;
&lt;li&gt;Missing content is a content problem, not an AI problem.&lt;/li&gt;
&lt;li&gt;A small test set and weekly log reviews keep quality from slipping.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your chatbot is giving answers you can't trust, &lt;a href="https://aqibjavaid.site/book" rel="noopener noreferrer"&gt;book a call&lt;/a&gt; and I will review how it retrieves and answers, and tell you what to fix first.&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://aqibjavaid.site/blog/rag-chatbot-wrong-answers-fix" rel="noopener noreferrer"&gt;aqibjavaid.site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>llm</category>
      <category>chatbot</category>
    </item>
  </channel>
</rss>
