<?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: Moin Ul Haq</title>
    <description>The latest articles on DEV Community by Moin Ul Haq (@moinulhaq).</description>
    <link>https://dev.to/moinulhaq</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%2F3805420%2Fb2644961-74d5-48e3-a841-0a1f06a92214.png</url>
      <title>DEV Community: Moin Ul Haq</title>
      <link>https://dev.to/moinulhaq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moinulhaq"/>
    <language>en</language>
    <item>
      <title>Which link actually brought the sale?</title>
      <dc:creator>Moin Ul Haq</dc:creator>
      <pubDate>Thu, 05 Nov 2026 04:32:20 +0000</pubDate>
      <link>https://dev.to/moinulhaq/which-link-actually-brought-the-sale-1h32</link>
      <guid>https://dev.to/moinulhaq/which-link-actually-brought-the-sale-1h32</guid>
      <description>&lt;p&gt;Most of us share links everywhere. Instagram bio. WhatsApp groups. Email. Ads. A poster with a QR code. Then a sale happens, and someone asks a simple question. Which link did it?&lt;/p&gt;

&lt;p&gt;If you cannot answer that, you are guessing. And guessing is expensive.&lt;/p&gt;

&lt;p&gt;I learned this the hard way while building Fewly. I am Moinul Haq, the founder, and I built it because I was tired of short links that only told me a click happened. A click is not a sale. A click is not even a sign that someone liked your product. It just means someone tapped.&lt;/p&gt;

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

&lt;p&gt;Caption: The Fewly homepage, showing a clean starting point for creating short links.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one question
&lt;/h2&gt;

&lt;p&gt;Before you shorten anything, decide what you want to learn. Do you want to know which platform works best? Which ad creative works best? Which email subject line gets people to actually buy?&lt;/p&gt;

&lt;p&gt;One link per answer. That is the whole trick.&lt;/p&gt;

&lt;p&gt;If you use the same link on Instagram, WhatsApp, and email, your numbers will blend together. You will see 500 clicks and feel good. You will have no idea where those 500 people came from. Use a separate link for each place, even if they all go to the same page. It takes a few extra seconds, and it saves you weeks of guessing later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give every link a clear name
&lt;/h2&gt;

&lt;p&gt;A back-half is the part after the slash. Use it well.&lt;/p&gt;

&lt;p&gt;A link called /sale is fine. A link called /sale-instagram is better. A link called /sale-instagram-reel-nov is best, because six months later you will still know what it was.&lt;/p&gt;

&lt;p&gt;Keep names short, readable, and honest. Do not use random letters if you can avoid it. Future you will thank present you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add UTM tags, but keep them simple
&lt;/h2&gt;

&lt;p&gt;UTM tags are small labels you add to a link. They tell your analytics where a visit came from. Source, medium, and campaign are enough for most people.&lt;/p&gt;

&lt;p&gt;For example, source can be instagram, medium can be bio, and campaign can be winter-sale. That is it. You do not need ten tags. You need three tags you will actually use the same way every time.&lt;/p&gt;

&lt;p&gt;The common mistake is spelling the same thing five different ways. Instagram, insta, IG, and instagram-bio are four different campaigns in your reports, even though they are the same place. Pick one spelling and stick to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clicks are only step one
&lt;/h2&gt;

&lt;p&gt;Here is where most link tools stop, and where most confusion starts. You see clicks go up, but sales stay flat. What happened?&lt;/p&gt;

&lt;p&gt;Maybe people clicked, but the page was slow. Maybe they clicked from the wrong country for your shipping. Maybe they clicked at midnight, when your shop was closed for live support. Clicks alone cannot tell you that.&lt;/p&gt;

&lt;p&gt;You need to connect the click to what happened next. Did the person sign up? Did they add something to the cart? Did they pay? That is conversion tracking. In plain terms, it means your link tool and your checkout both agree on one thing: this sale came from this link.&lt;/p&gt;

&lt;p&gt;When you have that, your decisions get easy. You stop spending on the link that gets clicks but no sales. You put more effort into the link that gets fewer clicks but more buyers. That is a very different list, and it is usually a surprise.&lt;/p&gt;

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

&lt;p&gt;Caption: A blurred view of the Fewly analytics dashboard, showing clicks and sales without sharing private details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch device, country, and time
&lt;/h2&gt;

&lt;p&gt;Three details change everything, and they are easy to miss.&lt;/p&gt;

&lt;p&gt;First, device. iPhone users and Android users do not always behave the same way. If you sell an app, sending both groups to the same web page is a waste. Send iPhone users to the App Store and Android users to Google Play. It is a small change, and it removes a whole step for the buyer.&lt;/p&gt;

&lt;p&gt;Second, country. If you only ship to one country, clicks from other countries are not bad, they are just not buyers. Knowing that stops you from thinking a campaign failed when it was actually shown to the wrong audience.&lt;/p&gt;

&lt;p&gt;Third, time of day. Some links work best in the morning, some at night. If you post at the wrong hour, you are not testing the link, you are testing the clock. A busy-hour view helps here, because it shows when people actually tap, not when you think they will.&lt;/p&gt;

&lt;h2&gt;
  
  
  QR codes count too
&lt;/h2&gt;

&lt;p&gt;A QR code on a menu, a package, or a poster is also a link. Treat it the same way.&lt;/p&gt;

&lt;p&gt;Use a dynamic QR code, which points at a short link, if you want to change the destination later or count scans. A static QR code is fine for something simple like a WiFi password, because the data is inside the code itself and it never expires. For marketing, dynamic is better, because you can fix a mistake without reprinting everything.&lt;/p&gt;

&lt;p&gt;And please test the print size. A tiny QR code on a busy package is not a link, it is a decoration. People need to scan it from a normal distance, with normal light, on a normal phone.&lt;/p&gt;

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

&lt;p&gt;Caption: The Fewly free tools page, with simple tools that support QR codes and short links.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple weekly check
&lt;/h2&gt;

&lt;p&gt;You do not need a daily dashboard ritual. Once a week, answer these five questions.&lt;/p&gt;

&lt;p&gt;Which link got the most clicks? Which link got the most sales? Which link got clicks but no sales? Which platform sent the best buyers, not just the most visitors? What will you stop doing next week?&lt;/p&gt;

&lt;p&gt;That last question matters most. Most of us only add new links. We rarely stop the ones that do not work. Stopping is free, and it frees up time for the links that actually bring money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it honest
&lt;/h2&gt;

&lt;p&gt;One more thing. Do not chase a big click number for its own sake. A link with 10,000 clicks and zero sales is not a win. A link with 100 clicks and 10 sales is a business.&lt;/p&gt;

&lt;p&gt;Short links are not magic. They are just a way to see clearly. When you can see which link brought the sale, you can make a better next move. That is the whole point.&lt;/p&gt;

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

&lt;p&gt;Caption: The Fewly API docs, showing how conversion tracking and routing work in plain steps.&lt;/p&gt;

&lt;p&gt;I built Fewly to make that view simple, with real-time analytics, branded links, QR codes, and conversion tracking, starting free at &lt;a href="https://fewly.tech" rel="noopener noreferrer"&gt;fewly.tech&lt;/a&gt;. You can also read the docs at &lt;a href="https://fewly.tech/docs" rel="noopener noreferrer"&gt;fewly.tech/docs&lt;/a&gt; to see how conversion tracking and routing work in plain steps. No matter which tool you use, though, the habit is the same: one link per place, clear names, simple tags, and sales, not just clicks, as the final score.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>buildinpublic</category>
      <category>saas</category>
      <category>startup</category>
    </item>
    <item>
      <title>10 Browser-Based Tools That Replaced My Local Dev Utilities</title>
      <dc:creator>Moin Ul Haq</dc:creator>
      <pubDate>Sun, 19 Jul 2026 18:17:58 +0000</pubDate>
      <link>https://dev.to/moinulhaq/10-browser-based-tools-that-replaced-my-local-dev-utilities-19ji</link>
      <guid>https://dev.to/moinulhaq/10-browser-based-tools-that-replaced-my-local-dev-utilities-19ji</guid>
      <description>&lt;p&gt;I replaced my laptop in March and did the thing everyone threatens to do: no backup restore, fresh install, add software only when I genuinely miss it.&lt;/p&gt;

&lt;p&gt;Four months later, an entire category never came back. The one-job utilities — JSON prettifier, diff GUI, hash tool, the little menu-bar timestamp thing — are browser tabs now. Not web apps that ship my data to a server. Client-side pages: they load once, run entirely in the tab, and keep working when the Wi-Fi drops.&lt;/p&gt;

&lt;p&gt;That distinction is the whole point. Half of what I paste into these tools is production-adjacent — webhook payloads with customer emails, staging configs, API responses. So my filter is blunt: open DevTools, watch the Network tab while I paste, and close the tab forever if anything leaves. Everything below passes. Fair warning: the tool links here point at fewly, which is my own project. Every tool on this list has alternatives elsewhere; the client-side pattern is what I'm actually recommending.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. JSON Formatter
&lt;/h3&gt;

&lt;p&gt;Webhook debugging is where this earns its keep. Stripe hands you something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"evt_3PZk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"invoice.paid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"amount_paid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"cus_QRt8"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Readable? Barely, and that's the short version. I paste it into &lt;a href="https://fewly.tech/tools/json-formatter" rel="noopener noreferrer"&gt;fewly's JSON formatter&lt;/a&gt;, get proper indentation, and — the underrated part — validation for free, since the tool has to parse before it can pretty-print. Trailing comma in a hand-edited fixture file? It points at the line instead of letting me squint.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Text Diff
&lt;/h3&gt;

&lt;p&gt;A feature worked on staging and 500'd in production. The entire diagnosis was two &lt;code&gt;.env&lt;/code&gt; files pasted side by side: prod was missing &lt;code&gt;REDIS_TLS=true&lt;/code&gt; and carried a stale &lt;code&gt;API_BASE&lt;/code&gt;. Ninety seconds. No git, no vimdiff over SSH. Real code diffs still happen in the PR, obviously — this is for config-shaped text that never reaches version control: env files, nginx blocks, two versions of an email template. It also settles "did anything change on your side?" disputes with vendors, gently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Hash Generator
&lt;/h3&gt;

&lt;p&gt;Two habits. Checksums first: when a vendor publishes a SHA-256 next to their SDK download, I actually verify it now, because verifying costs ten seconds. Second, quick digests while sketching — hashing a canonical string to design a cache key or a dedupe ID. Yes, &lt;code&gt;shasum -a 256&lt;/code&gt; exists and I still use it in scripts. But when the string is already on my clipboard and a browser is already open, the tab wins on pure motion economics.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Timestamp Converter
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;1752868800&lt;/code&gt; means nothing to me and never will. Logs speak epoch; incident reports speak "Friday around 9 p.m." A converter translates both directions and — the part I actually get wrong — handles timezones. When a user in Karachi reports something broke "last night," turning that into a UTC range I can grep is the real task, and doing offset math in my head at 1 a.m. has burned me more than once. The ISO 8601 output drops straight into API query params too, which ends the format guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Cron Generator
&lt;/h3&gt;

&lt;p&gt;I've written cron expressions for a decade and still don't fully trust myself with field order under pressure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 3 * * 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three a.m. every Monday. Or every March? Or the 1st of the month? (It's Monday. But you double-checked, didn't you.) A generator that renders the schedule back into plain English before it lands in a crontab has prevented at least two "why did this run 31 times" incidents that I know of.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Encoder / Decoder
&lt;/h3&gt;

&lt;p&gt;OAuth debugging is nesting-doll decoding: a &lt;code&gt;redirect_uri&lt;/code&gt; URL-encoded inside a query string, a base64 blob inside an &lt;code&gt;Authorization&lt;/code&gt; header, occasionally both at once. Decode, read, fix, re-encode. If base64 still feels slightly mystical, &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Base64" rel="noopener noreferrer"&gt;MDN's glossary entry&lt;/a&gt; is the best ten-minute explanation I've found. After that, a paste box beats typing &lt;code&gt;atob()&lt;/code&gt; into a console you don't have open.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. YAML to JSON (and back)
&lt;/h3&gt;

&lt;p&gt;GitHub Actions taught me that I cannot see YAML indentation errors after 6 p.m. A matrix build kept skipping one OS; the file looked fine. Converted to JSON, the bug was instant — my entry sat one level too high, a sibling of &lt;code&gt;matrix&lt;/code&gt; instead of a child. YAML-to-JSON gives you the parser's-eye view, which is the view that matters. The reverse direction earns its spot whenever a machine-generated JSON config needs to become something humans can review without wincing. (The &lt;a href="https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions" rel="noopener noreferrer"&gt;workflow syntax reference&lt;/a&gt; and I remain on speaking terms regardless.)&lt;/p&gt;

&lt;h3&gt;
  
  
  8. CSV Viewer
&lt;/h3&gt;

&lt;p&gt;The 18 MB export problem: a spreadsheet app will open it, then immediately start improving it — dates reinterpreted for your locale, encodings guessed, columns reformatted. A read-only viewer does none of that. I skim, confirm the column names, spot-check that row 40,000 looks like row 4, close. Most of the time I never needed to edit the file. I needed to see it, and seeing shouldn't mutate.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Markdown and HTML, both directions
&lt;/h3&gt;

&lt;p&gt;Release notes get written once, in Markdown. Then the email tool wants HTML, a CMS field wants Markdown back, and someone in marketing wants the announcement "as a web page." Round-tripping through a converter beats hand-repairing &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; soup in either direction. A recent favorite: a client sent brand copy as raw HTML scraped from their old site, and one paste turned it into Markdown I could commit to the project wiki. Small tool, weekly use, zero drama.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. File Converter
&lt;/h3&gt;

&lt;p&gt;The surprise entry. Product managers send specs as &lt;code&gt;.docx&lt;/code&gt;; our docs live in the repo as Markdown. The file converter on the same hub does DOCX-to-Markdown in the tab — a 14-page spec becomes a reviewable pull request in about a minute, headings and lists intact. It goes the other way too, Markdown to PDF, for the people who want "a document" back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the terminal still wins
&lt;/h2&gt;

&lt;p&gt;None of this retires &lt;a href="https://jqlang.org" rel="noopener noreferrer"&gt;jq&lt;/a&gt;. Anything repeated, scheduled, or larger than ~50 MB belongs in a script, and jq's filter language is untouchable for surgical extraction mid-pipeline. The browser tools won a different niche: interactive, one-off, paste-shaped problems where startup cost dominates the actual work. Knowing which niche you're standing in is the skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual takeaway
&lt;/h2&gt;

&lt;p&gt;My Homebrew list is a third of its former size and I haven't missed the utility folder once. The full set of tabs I use lives at &lt;a href="https://fewly.tech/tools" rel="noopener noreferrer"&gt;fewly.tech/tools&lt;/a&gt; — 50-odd tools, free, client-side, no signup, and they work offline once loaded. But steal the pattern even if you skip the site: before trusting any "free online tool," open the Network tab and watch what leaves. If your data ships out for a task your laptop could do locally, find a better tab. My running total after four months: zero dollars, zero licenses, zero update prompts.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Suggested author bio:&lt;/strong&gt; Moin is the founder of &lt;a href="https://fewly.tech" rel="noopener noreferrer"&gt;fewly&lt;/a&gt;, a free URL shortener with 50+ free browser-based tools. He builds the kind of small utilities he wishes came pre-installed.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>productivity</category>
      <category>tooling</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Fix “Authentication Credentials Were Not Provided” Error in Django REST Framework JWT</title>
      <dc:creator>Moin Ul Haq</dc:creator>
      <pubDate>Sun, 08 Mar 2026 20:02:56 +0000</pubDate>
      <link>https://dev.to/moinulhaq/how-to-fix-authentication-credentials-were-not-provided-error-in-django-rest-framework-jwt-elo</link>
      <guid>https://dev.to/moinulhaq/how-to-fix-authentication-credentials-were-not-provided-error-in-django-rest-framework-jwt-elo</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;You’ve set up JWT authentication in your Django REST Framework API. Your token generation works perfectly. You’re sending the Authorization header with every request. But somehow, you keep getting this frustrating error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "detail": "Authentication credentials were not provided."
}

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

&lt;/div&gt;



&lt;p&gt;If you’ve been pulling your hair out over this issue, you’re not alone. This is one of the most common authentication problems Django backend developers face, and I’ve encountered it countless times while building production APIs at Elevabel and in my personal projects.&lt;/p&gt;

&lt;p&gt;The good news? This error usually has straightforward solutions once you understand what’s actually happening behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Problem
&lt;/h2&gt;

&lt;p&gt;The “Authentication credentials were not provided” error occurs when Django REST Framework’s authentication system cannot find or validate the JWT token in your request. Despite what the error message suggests, this doesn’t always mean you forgot to send credentials—it often means the credentials aren’t being recognized properly.&lt;/p&gt;

&lt;p&gt;Here are the most common causes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Incorrect Authorization header format&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;- Missing authentication classes in settings or views&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;- CORS issues blocking headers&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;- Middleware interference&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;- Token prefix mismatch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s walk through each solution systematically.&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution 1: Verify Your Authorization Header Format
&lt;/h2&gt;

&lt;p&gt;The most common culprit is an incorrectly formatted Authorization header. Django REST Framework’s SimpleJWT expects a specific format.&lt;/p&gt;
&lt;h4&gt;
  
  
  Correct Format:
&lt;/h4&gt;

&lt;p&gt;Authorization: Bearer &lt;/p&gt;
&lt;h4&gt;
  
  
  Common Mistakes:
&lt;/h4&gt;

&lt;p&gt;Wrong: &lt;code&gt;Authorization: JWT &amp;lt;token&amp;gt;&lt;/code&gt;&lt;br&gt;
Wrong: &lt;code&gt;Authorization: Token &amp;lt;token&amp;gt;&lt;/code&gt;&lt;br&gt;
Wrong: &lt;code&gt;Authorization: &amp;lt;token&amp;gt; (missing prefix)&lt;/code&gt;&lt;br&gt;
Wrong: &lt;code&gt;Authorization: Bearer&amp;lt;token&amp;gt; (missing space)&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing with cURL:
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc..." \&lt;br&gt;
     http://localhost:8000/api/protected-endpoint/&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing with JavaScript (fetch):
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fetch('http://localhost:8000/api/protected-endpoint/', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Testing with Python requests:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests

headers = {
    'Authorization': f'Bearer {access_token}',
}

response = requests.get(
    'http://localhost:8000/api/protected-endpoint/',
    headers=headers
)

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

&lt;/div&gt;

&lt;h2&gt;
  
  
  Solution 2: Configure Authentication Classes Properly
&lt;/h2&gt;

&lt;p&gt;Django REST Framework needs to know which authentication backend to use. You can configure this globally or per-view.&lt;/p&gt;
&lt;h4&gt;
  
  
  Global Configuration (settings.py):
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
}

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

&lt;/div&gt;

&lt;h4&gt;
  
  
  Per-View Configuration:
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication

class ProtectedView(generics.ListAPIView):
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]
    queryset = MyModel.objects.all()
    serializer_class = MySerializer

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

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Important: If you set authentication_classes on a view, it overrides the global setting. Make sure JWT authentication is included.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution 3: Handle CORS Properly
&lt;/h2&gt;

&lt;p&gt;When building a separate frontend (React, Vue, etc.), CORS can block your Authorization header from reaching Django.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install django-cors-headers:

pip install django-cors-headers


Configure CORS (settings.py):

INSTALLED_APPS = [
    # ...
    'corsheaders',
    # ...
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # Must be at the top
    'django.middleware.common.CommonMiddleware',
    # ... other middleware
]

# Development settings
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]

# Allow credentials (important for JWT)
CORS_ALLOW_CREDENTIALS = True

# Explicitly allow Authorization header
CORS_ALLOW_HEADERS = [
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
]

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Production Note: Never use CORS_ALLOW_ALL_ORIGINS = True in production. Always specify exact origins.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 4: Check for Middleware Conflicts
&lt;/h2&gt;

&lt;p&gt;Custom middleware can sometimes interfere with authentication. If you have custom authentication middleware, ensure it’s not conflicting with DRF’s authentication.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example of problematic middleware:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# DON'T DO THIS
class CustomAuthMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # This might strip the Authorization header
        if 'Authorization' in request.headers:
            # Custom processing that breaks JWT
            pass
        return self.get_response(request)

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

&lt;/div&gt;



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

&lt;p&gt;Either remove conflicting middleware or exclude API endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CustomAuthMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # Skip middleware for API endpoints
        if request.path.startswith('/api/'):
            return self.get_response(request)

        # Your custom logic for other endpoints
        return self.get_response(request)

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Solution 5: Verify Token Prefix Settings
&lt;/h2&gt;

&lt;p&gt;SimpleJWT uses “Bearer” as the default prefix, but this can be customized. Make sure your frontend and backend agree on the prefix.&lt;/p&gt;

&lt;h4&gt;
  
  
  Check your JWT settings (settings.py):
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from datetime import timedelta

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'AUTH_HEADER_TYPES': ('Bearer',),  # This must match your frontend
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you changed AUTH_HEADER_TYPES to something like ('JWT',), your Authorization header must use that prefix:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Authorization: JWT &amp;lt;token&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Best Practices for Production JWT Authentication&lt;/p&gt;

&lt;p&gt;Based on my experience building scalable backend systems, here are some best practices:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Use Short-Lived Access Tokens
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),  # Short-lived
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),     # Longer-lived
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Implement Token Blacklisting
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSTALLED_APPS = [
    # ...
    'rest_framework_simplejwt.token_blacklist',
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run migrations:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;python manage.py migrate token_blacklist&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Add Proper Error Handling
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from rest_framework.views import exception_handler
from rest_framework.response import Response

def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if response is not None and response.status_code == 401:
        response.data = {
            'error': 'Authentication failed',
            'detail': 'Please provide valid credentials',
            'code': 'authentication_failed'
        }

    return response

# In settings.py
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'myapp.utils.custom_exception_handler',
}


4. Log Authentication Failures

import logging

logger = logging.getLogger(__name__)

class ProtectedView(generics.ListAPIView):
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

    def handle_exception(self, exc):
        if isinstance(exc, AuthenticationFailed):
            logger.warning(
                f"Authentication failed for {self.request.path} "
                f"from IP {self.request.META.get('REMOTE_ADDR')}"
            )
        return super().handle_exception(exc)

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Real-World Insight
&lt;/h4&gt;

&lt;p&gt;In my work at Elevabel, I’ve debugged this exact error dozens of times across different client projects. The most common issue? Frontend developers using Authorization: Token  because they’re used to Django’s built-in token authentication.&lt;/p&gt;

&lt;p&gt;One project had a particularly tricky case: the Authorization header was being stripped by an AWS Application Load Balancer security rule. Always test your authentication in the actual deployment environment, not just locally.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Another lesson learned: when building APIs consumed by mobile apps, implement detailed error responses. A generic “credentials not provided” message doesn’t help mobile developers debug whether the token is malformed, expired, or missing entirely.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging Checklist&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you encounter this error, work through this checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify Authorization header format: Bearer &lt;/li&gt;
&lt;li&gt;Check that rest_framework_simplejwt is installed&lt;/li&gt;
&lt;li&gt;Confirm JWTAuthentication is in &lt;code&gt;DEFAULT_AUTHENTICATION_CLASSES&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test the token is valid (not expired)&lt;/li&gt;
&lt;li&gt;Verify CORS is configured correctly&lt;/li&gt;
&lt;li&gt;Check for middleware conflicts&lt;/li&gt;
&lt;li&gt;Test with a simple cURL request to isolate frontend issues&lt;/li&gt;
&lt;li&gt;Review Django logs for authentication errors&lt;/li&gt;
&lt;li&gt;Confirm the endpoint requires authentication &lt;code&gt;(has IsAuthenticated permission)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;“Authentication credentials were not provided”&lt;/strong&gt; error in Django REST Framework JWT authentication is frustrating but solvable. In most cases, it comes down to header formatting, configuration issues, or CORS problems.&lt;/p&gt;

&lt;p&gt;Start with the basics: verify your Authorization header format and ensure JWT authentication is properly configured. If those don’t work, systematically check CORS settings and middleware conflicts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Remember, authentication is the foundation of API security. Taking time to implement it correctly—with proper error handling, logging, and token management—will save you countless debugging hours down the road.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Moin Ul Haq is a backend web developer from Pakistan specializing in Django, REST APIs, and scalable backend architecture. As the Founder of Elevabel, a software and web development company, he builds production-ready web platforms and backend systems for clients globally. Moin shares his development insights and projects on GitHub at github.com/moin-ul-haq.&lt;/p&gt;

</description>
      <category>django</category>
      <category>djangorestframework</category>
      <category>restapi</category>
      <category>jwt</category>
    </item>
    <item>
      <title>LinkedIn OAuth in Django Using Allauth and OpenID Connect: A Practical Guide</title>
      <dc:creator>Moin Ul Haq</dc:creator>
      <pubDate>Thu, 05 Mar 2026 22:34:34 +0000</pubDate>
      <link>https://dev.to/moinulhaq/linkedin-oauth-in-django-using-allauth-and-openid-connect-a-practical-guide-38od</link>
      <guid>https://dev.to/moinulhaq/linkedin-oauth-in-django-using-allauth-and-openid-connect-a-practical-guide-38od</guid>
      <description>&lt;p&gt;&lt;em&gt;By Moin ul Haq – Software Engineer | Web Developer | Django Enthusiast&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Integrating LinkedIn OAuth in Django can be tricky, especially when you want smooth authentication and proper user data handling. In this guide, I’ll walk you through using Django Allauth with OpenID Connect for LinkedIn login, tackling real-world issues I faced while building client-facing web apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wohmg6y3oz6acmefi01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wohmg6y3oz6acmefi01.png" alt="Django allauth linkedin openid connect"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Why LinkedIn OAuth?&lt;/strong&gt;&lt;br&gt;
LinkedIn login is perfect when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users to sign up quickly using their LinkedIn profiles.&lt;/li&gt;
&lt;li&gt;Secure authentication without managing passwords.&lt;/li&gt;
&lt;li&gt;Access to professional user data like email, name, and profile URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most tutorials cover Google or Facebook, but LinkedIn’s OpenID Connect implementation often confuses developers. Let’s solve it step by step.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Install Dependencies
&lt;/h3&gt;

&lt;p&gt;Start with Django and Allauth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install django
pip install django-allauth[openidconnect]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure your Django project is set up with INSTALLED_APPS updated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSTALLED_APPS = [
    ...
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.openid_connect',
]
SITE_ID = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Configure LinkedIn as OpenID Connect Provider
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to LinkedIn Developer Portaland create a new app.&lt;/li&gt;
&lt;li&gt;Add OAuth 2.0 redirect URLs, e.g.,
&lt;code&gt;http://localhost:8000/accounts/openid/login/callback/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Note your Client ID and Client Secret.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Add Provider in Django Admin
&lt;/h3&gt;

&lt;p&gt;Django Allauth allows dynamic configuration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Login to Django Admin → Social Applications → Add LinkedIn.&lt;/li&gt;
&lt;li&gt;Select Site → &lt;code&gt;example.com&lt;/code&gt; (or localhost for testing).&lt;/li&gt;
&lt;li&gt;Enter Client ID and Secret Key.&lt;/li&gt;
&lt;li&gt;Use OpenID Connect as Provider type.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 4: Update URLs
&lt;/h3&gt;

&lt;p&gt;Add Allauth URLs to your &lt;code&gt;urls.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.urls import path, include

urlpatterns = [
    path('accounts/', include('allauth.urls')),
    ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Frontend Integration
&lt;/h3&gt;

&lt;p&gt;Add a simple login button in your template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="{% provider_login_url 'openid_connect' %}?process=login"&amp;gt;
    Login with LinkedIn
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Tip: Always use &lt;code&gt;{% provider_login_url %}&lt;/code&gt; instead of hardcoding URLs to avoid callback issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Handling Common Problems
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. “Invalid redirect_uri”&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure the redirect URI in LinkedIn app matches exactly with the one Django Allauth uses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. User Email Not Returned&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn requires r_emailaddress permission. Ensure your Allauth provider scope includes:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SOCIALACCOUNT_PROVIDERS = {
    'openid_connect': {
        'SCOPE': ['openid', 'email', 'profile']
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Tokens Not Saved&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allauth stores tokens in SocialToken. Always check the SocialApp and SocialAccount entries in Django Admin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 7: Verify Everything Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Run server:&lt;br&gt;
&lt;code&gt;python manage.py runserver&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Login with LinkedIn → authorize → redirected to dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check &lt;code&gt;request.user&lt;/code&gt; → should have LinkedIn email, name, and token.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Personal Branding Angle
&lt;/h3&gt;

&lt;p&gt;While implementing this, I realized professional networking login flows not only save development time but also improve user trust. As a developer, mastering OAuth integrations with Django is a strong portfolio highlight, showing clients that you can handle enterprise-grade authentication.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;💡 Pro Tip: Post your projects integrating OAuth on GitHub with proper README and a live demo. Recruiters and clients love this.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Using &lt;strong&gt;Django Allauth&lt;/strong&gt; + &lt;strong&gt;OpenID&lt;/strong&gt; Connect makes LinkedIn OAuth integration straightforward if you handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provider scopes correctly.&lt;/li&gt;
&lt;li&gt;Redirect URIs precisely.&lt;/li&gt;
&lt;li&gt;Tokens and user data securely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup not only improves user experience but also demonstrates your capability to solve complex authentication challenges professionally.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗 About Me
&lt;/h3&gt;

&lt;p&gt;I’m Moin ul Haq, a software engineer building scalable Django applications. I write about real-world solutions, web development challenges, and software best practices. Check out my projects on &lt;a href="https://github.com/moin-ul-haq" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>allauth</category>
      <category>oauth</category>
      <category>linkedin</category>
    </item>
    <item>
      <title>Fixing “Got AttributeError: 'NoneType' object has no attribute …” in Django REST Framework</title>
      <dc:creator>Moin Ul Haq</dc:creator>
      <pubDate>Wed, 04 Mar 2026 10:43:43 +0000</pubDate>
      <link>https://dev.to/moinulhaq/fixing-got-attributeerror-nonetype-object-has-no-attribute-in-django-rest-framework-4ln2</link>
      <guid>https://dev.to/moinulhaq/fixing-got-attributeerror-nonetype-object-has-no-attribute-in-django-rest-framework-4ln2</guid>
      <description>&lt;p&gt;Hi! I’m Moin Ul Haq, a Software Engineer from Bahawalpur specializing in Django backend development.&lt;/p&gt;

&lt;p&gt;If you’ve ever worked with Django REST Framework (DRF), you may have seen this frustrating error:&lt;br&gt;
&lt;code&gt;AttributeError: 'NoneType' object has no attribute 'id'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This usually happens when Serializer.save() or a nested serializer tries to access a field that doesn’t exist or is not properly passed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this tutorial, I’ll show you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why this error occurs&lt;/li&gt;
&lt;li&gt;How to reproduce it&lt;/li&gt;
&lt;li&gt;How to fix it cleanly in DRF&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, your serializers will handle missing data safely, and this error will become a thing of the past.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Understanding the Error
&lt;/h3&gt;

&lt;p&gt;This error typically occurs in DRF when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A nested serializer expects a related object but receives None&lt;/li&gt;
&lt;li&gt;A required field is missing in validated_data&lt;/li&gt;
&lt;li&gt;You call .id or access attributes on a NoneType object&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from rest_framework import serializers
from .models import Task, Project

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ['id', 'title', 'project']

    def create(self, validated_data):
        project = validated_data.get('project')
        print(project.id)  # ❌ Error occurs here if project is None
        return Task.objects.create(**validated_data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a client POSTs without project, DRF will raise:&lt;br&gt;
&lt;code&gt;AttributeError: 'NoneType' object has no attribute 'id'&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Proper Validation in Serializer
&lt;/h3&gt;

&lt;p&gt;The clean way to fix this is to validate the field before using it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ['id', 'title', 'project']

    def create(self, validated_data):
        project = validated_data.get('project')
        if project is None:
            raise serializers.ValidationError({
                'project': 'Project must be provided.'
            })
        return Task.objects.create(**validated_data)

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

&lt;/div&gt;



&lt;p&gt;✅ Now, if the client forgets project, they get a clear DRF validation error instead of AttributeError.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Optional Nested Objects
&lt;/h3&gt;

&lt;p&gt;Sometimes, the field may be optional. In that case, handle it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ['id', 'title', 'project']

    def create(self, validated_data):
        project = validated_data.get('project')
        task = Task.objects.create(
            title=validated_data.get('title'),
            project=project if project else None
        )
        return task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;If project is missing, the serializer still works&lt;/li&gt;
&lt;li&gt;No more “NoneType object has no attribute …” error&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Always Test Your API
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Example POST request:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;{&lt;br&gt;
  "title": "Finish DRF Tutorial"&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With validation: returns 400 Bad Request with message: "Project must be provided."&lt;/li&gt;
&lt;li&gt;Without validation: crashes with AttributeError&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ This ensures clean error handling and a better developer experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The “Got AttributeError: 'NoneType' object has no attribute …” error is one of the most common issues in Django REST Framework, especially for beginners dealing with nested serializers or required fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always validate required fields before accessing attributes&lt;/li&gt;
&lt;li&gt;Use serializers.ValidationError for meaningful messages&lt;/li&gt;
&lt;li&gt;Optional fields should be handled with None checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  About the Author
&lt;/h3&gt;

&lt;p&gt;I’m Moin Ul Haq, a Software Engineer from Bahawalpur specializing in Django &amp;amp; Python backend development. I share tutorials, tips, and real-world solutions to help developers build scalable web applications.&lt;/p&gt;

&lt;p&gt;Follow me on Dev.to or check my GitHub: &lt;a href="https://github.com/moin-ul-haq" rel="noopener noreferrer"&gt;moin-ul-haq&lt;/a&gt;&lt;br&gt;
 for more backend projects.&lt;/p&gt;

</description>
      <category>django</category>
      <category>djangorest</category>
      <category>restapi</category>
      <category>python</category>
    </item>
  </channel>
</rss>
