[{"data":1,"prerenderedAt":1424},["ShallowReactive",2],{"/blog/understanding-cors-for-api-developers":3},{"id":4,"title":5,"body":6,"date":1414,"description":1415,"extension":1416,"head":1417,"image":20,"meta":1418,"navigation":1216,"ogImage":1417,"path":1419,"robots":1417,"schemaOrg":1417,"seo":1420,"sitemap":1421,"stem":1422,"tags":1417,"__hash__":1423},"content/blog/understanding-cors-for-api-developers.md","Understanding CORS: What Actually Blocks Your API Requests",{"type":7,"value":8,"toc":1382},"minimal",[9,13,21,24,55,63,66,71,79,86,136,139,142,203,213,215,219,222,227,234,278,285,289,292,319,330,333,335,339,346,352,355,359,388,392,435,442,448,450,454,464,471,478,494,497,511,514,516,520,524,538,541,553,557,563,588,592,606,630,632,636,639,654,660,663,665,669,672,679,684,687,689,693,697,706,714,716,720,728,731,736,785,804,807,853,857,867,869,873,880,890,893,908,912,981,983,987,990,999,1002,1036,1042,1045,1047,1051,1054,1058,1085,1089,1093,1115,1119,1177,1181,1291,1293,1297,1300,1333,1338,1340,1344,1347,1364,1367,1375,1378],[10,11,5],"h1",{"id":12},"understanding-cors-what-actually-blocks-your-api-requests",[14,15,16],"p",{},[17,18],"img",{"alt":19,"src":20},"Understanding CORS for API Developers","/blog/understanding-cors-for-api-developers.webp",[14,22,23],{},"You've seen it. You write a perfectly fine fetch call, your backend returns 200 OK, and then the browser throws this at you:",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-html shiki shiki-themes github-dark","Access to fetch at 'https://api.example.com/users' from origin 'http://localhost:3000'\nhas been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present\non the requested resource.\n","html","",[32,33,34,43,49],"code",{"__ignoreMap":30},[35,36,39],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"s95oV","Access to fetch at 'https://api.example.com/users' from origin 'http://localhost:3000'\n",[35,44,46],{"class":37,"line":45},2,[35,47,48],{"class":41},"has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present\n",[35,50,52],{"class":37,"line":51},3,[35,53,54],{"class":41},"on the requested resource.\n",[14,56,57,58,62],{},"Most developers read this and think: ",[59,60,61],"em",{},"\"CORS is blocking my request.\""," That's understandable — but it's not quite right. To fix it reliably, you need to understand what's actually happening under the hood.",[64,65],"hr",{},[67,68,70],"h2",{"id":69},"what-is-same-origin-policy-sop","What is Same-Origin Policy (SOP)?",[14,72,73,74,78],{},"Before CORS even enters the picture, there's ",[75,76,77],"strong",{},"Same-Origin Policy (SOP)"," — the real gatekeeper.",[14,80,81,82,85],{},"SOP is a browser security rule that restricts how JavaScript on one origin can interact with resources from another origin. An ",[75,83,84],{},"origin"," is defined by three things:",[87,88,89,102],"table",{},[90,91,92],"thead",{},[93,94,95,99],"tr",{},[96,97,98],"th",{},"Component",[96,100,101],{},"Example",[103,104,105,116,126],"tbody",{},[93,106,107,111],{},[108,109,110],"td",{},"Protocol",[108,112,113],{},[32,114,115],{},"https://",[93,117,118,121],{},[108,119,120],{},"Domain",[108,122,123],{},[32,124,125],{},"api.example.com",[93,127,128,131],{},[108,129,130],{},"Port",[108,132,133],{},[32,134,135],{},":443",[14,137,138],{},"All three must match. If any one of them differs, you're dealing with a cross-origin request and SOP kicks in.",[14,140,141],{},"Some examples:",[87,143,144,157],{},[90,145,146],{},[93,147,148,151,154],{},[96,149,150],{},"From",[96,152,153],{},"To",[96,155,156],{},"Same origin?",[103,158,159,174,189],{},[93,160,161,166,171],{},[108,162,163],{},[32,164,165],{},"http://localhost:3000",[108,167,168],{},[32,169,170],{},"http://localhost:8080",[108,172,173],{},"❌ Different port",[93,175,176,181,186],{},[108,177,178],{},[32,179,180],{},"https://app.com",[108,182,183],{},[32,184,185],{},"https://api.app.com",[108,187,188],{},"❌ Different subdomain",[93,190,191,195,200],{},[108,192,193],{},[32,194,180],{},[108,196,197],{},[32,198,199],{},"http://app.com",[108,201,202],{},"❌ Different protocol",[14,204,205,208,209,212],{},[75,206,207],{},"Why does SOP exist?"," Imagine you're logged into your bank. A malicious site you visit in another tab tries to make a fetch request to ",[32,210,211],{},"https://yourbank.com/account/transfer",". Without SOP, the browser would happily send your session cookies along and expose your data. SOP prevents that.",[64,214],{},[67,216,218],{"id":217},"simple-vs-non-simple-requests","Simple vs Non-Simple Requests",[14,220,221],{},"Not all cross-origin requests are handled the same way. The browser splits them into two categories, and this distinction matters a lot for debugging.",[223,224,226],"h3",{"id":225},"simple-requests","Simple Requests",[14,228,229,230,233],{},"A request is \"simple\" if it meets ",[75,231,232],{},"all"," of the following:",[235,236,237,252,272],"ul",{},[238,239,240,241,244,245,248,249],"li",{},"Method is ",[32,242,243],{},"GET",", ",[32,246,247],{},"POST",", or ",[32,250,251],{},"HEAD",[238,253,254,255,244,258,261,262,244,265,248,268,271],{},"Headers are limited to a safe set (",[32,256,257],{},"Accept",[32,259,260],{},"Content-Type"," with value ",[32,263,264],{},"text/plain",[32,266,267],{},"multipart/form-data",[32,269,270],{},"application/x-www-form-urlencoded",", etc.)",[238,273,274,275],{},"No custom headers like ",[32,276,277],{},"Authorization",[14,279,280,281,284],{},"Simple requests are sent ",[75,282,283],{},"directly"," — no extra handshake.",[223,286,288],{"id":287},"non-simple-requests","Non-Simple Requests",[14,290,291],{},"Anything outside those constraints is non-simple. Common examples:",[235,293,294,306,311],{},[238,295,296,297,244,300,244,303],{},"Methods like ",[32,298,299],{},"PUT",[32,301,302],{},"DELETE",[32,304,305],{},"PATCH",[238,307,308],{},[32,309,310],{},"Content-Type: application/json",[238,312,313,314,244,316,271],{},"Any custom header (",[32,315,277],{},[32,317,318],{},"X-Api-Key",[14,320,321,322,325,326,329],{},"Non-simple requests trigger a ",[75,323,324],{},"preflight"," — the browser sends an ",[32,327,328],{},"OPTIONS"," request first to ask the server for permission before sending the real request.",[14,331,332],{},"Understanding which category your request falls into is the first step when debugging. We'll come back to this in the practical guide.",[64,334],{},[67,336,338],{"id":337},"how-browsers-enforce-same-origin-policy","How Browsers Enforce Same-Origin Policy",[14,340,341,342,345],{},"Here's the part most developers don't fully internalize: ",[75,343,344],{},"the browser doesn't block the request from leaving your machine."," The request goes out. The server processes it. The server responds.",[14,347,348,349],{},"What the browser controls is whether ",[75,350,351],{},"your JavaScript can read the response.",[14,353,354],{},"The flow differs depending on your request type:",[223,356,358],{"id":357},"simple-request-flow","Simple Request Flow",[25,360,364],{"className":361,"code":362,"language":363,"meta":30,"style":30},"language-yaml shiki shiki-themes github-dark","Browser → sends request directly → Server responds\nBrowser checks Access-Control-Allow-Origin in response\n  ✅ Match → JS can read the response\n  ❌ No match → browser blocks access\n","yaml",[32,365,366,372,377,382],{"__ignoreMap":30},[35,367,368],{"class":37,"line":38},[35,369,371],{"class":370},"sU2Wk","Browser → sends request directly → Server responds\n",[35,373,374],{"class":37,"line":45},[35,375,376],{"class":370},"Browser checks Access-Control-Allow-Origin in response\n",[35,378,379],{"class":37,"line":51},[35,380,381],{"class":370},"  ✅ Match → JS can read the response\n",[35,383,385],{"class":37,"line":384},4,[35,386,387],{"class":370},"  ❌ No match → browser blocks access\n",[223,389,391],{"id":390},"non-simple-request-flow","Non-Simple Request Flow",[25,393,395],{"className":361,"code":394,"language":363,"meta":30,"style":30},"Browser → sends OPTIONS preflight → Server responds\nBrowser checks CORS headers in preflight response\n  ❌ Missing or wrong → stops here, actual request never sent\n  ✅ OK → Browser sends the actual request → Server responds\n         Browser checks CORS headers again on actual response\n           ✅ Match → JS can read the response\n           ❌ No match → browser blocks access\n",[32,396,397,402,407,412,417,423,429],{"__ignoreMap":30},[35,398,399],{"class":37,"line":38},[35,400,401],{"class":370},"Browser → sends OPTIONS preflight → Server responds\n",[35,403,404],{"class":37,"line":45},[35,405,406],{"class":370},"Browser checks CORS headers in preflight response\n",[35,408,409],{"class":37,"line":51},[35,410,411],{"class":370},"  ❌ Missing or wrong → stops here, actual request never sent\n",[35,413,414],{"class":37,"line":384},[35,415,416],{"class":370},"  ✅ OK → Browser sends the actual request → Server responds\n",[35,418,420],{"class":37,"line":419},5,[35,421,422],{"class":370},"         Browser checks CORS headers again on actual response\n",[35,424,426],{"class":37,"line":425},6,[35,427,428],{"class":370},"           ✅ Match → JS can read the response\n",[35,430,432],{"class":37,"line":431},7,[35,433,434],{"class":370},"           ❌ No match → browser blocks access\n",[14,436,437,438,441],{},"Notice that for non-simple requests, the browser checks CORS headers ",[75,439,440],{},"twice"," — once on the preflight, and again on the actual response. Both must pass.",[443,444,445],"blockquote",{},[14,446,447],{},"Your backend worked. Your browser just refused to show you the result.",[64,449],{},[67,451,453],{"id":452},"what-is-cors","What is CORS?",[14,455,456,457,460,461],{},"CORS — ",[75,458,459],{},"Cross-Origin Resource Sharing"," — is the mechanism that lets a server tell browsers: ",[59,462,463],{},"\"It's okay, this origin is allowed to read my responses.\"",[14,465,466,467,470],{},"It doesn't block anything. It ",[75,468,469],{},"relaxes"," the Same-Origin Policy.",[14,472,473,474,477],{},"When a browser makes a cross-origin request, it automatically attaches an ",[32,475,476],{},"Origin"," header:",[25,479,481],{"className":361,"code":480,"language":363,"meta":30,"style":30},"Origin: http://localhost:3000\n",[32,482,483],{"__ignoreMap":30},[35,484,485,488,491],{"class":37,"line":38},[35,486,476],{"class":487},"s4JwU",[35,489,490],{"class":41},": ",[35,492,493],{"class":370},"http://localhost:3000\n",[14,495,496],{},"The server can then respond with:",[25,498,500],{"className":361,"code":499,"language":363,"meta":30,"style":30},"Access-Control-Allow-Origin: http://localhost:3000\n",[32,501,502],{"__ignoreMap":30},[35,503,504,507,509],{"class":37,"line":38},[35,505,506],{"class":487},"Access-Control-Allow-Origin",[35,508,490],{"class":41},[35,510,493],{"class":370},[14,512,513],{},"If that header is present and matches, the browser allows your JavaScript to access the response. If it's missing or doesn't match, SOP blocks it — regardless of what HTTP status code the server returned.",[64,515],{},[67,517,519],{"id":518},"how-cors-headers-work","How CORS Headers Work",[223,521,523],{"id":522},"the-essential-header","The Essential Header",[25,525,527],{"className":361,"code":526,"language":363,"meta":30,"style":30},"Access-Control-Allow-Origin: https://app.com\n",[32,528,529],{"__ignoreMap":30},[35,530,531,533,535],{"class":37,"line":38},[35,532,506],{"class":487},[35,534,490],{"class":41},[35,536,537],{"class":370},"https://app.com\n",[14,539,540],{},"Or to allow any origin (use carefully — avoid with credentials):",[25,542,544],{"className":361,"code":543,"language":363,"meta":30,"style":30},"Access-Control-Allow-Origin: *\n",[32,545,546],{"__ignoreMap":30},[35,547,548,550],{"class":37,"line":38},[35,549,506],{"class":487},[35,551,552],{"class":41},": *\n",[223,554,556],{"id":555},"preflight-specific-headers","Preflight-Specific Headers",[14,558,559,560,562],{},"For non-simple requests, the ",[32,561,328],{}," preflight response must also include:",[25,564,566],{"className":361,"code":565,"language":363,"meta":30,"style":30},"Access-Control-Allow-Methods: GET, POST, PUT, DELETE\nAccess-Control-Allow-Headers: Authorization, Content-Type\n",[32,567,568,578],{"__ignoreMap":30},[35,569,570,573,575],{"class":37,"line":38},[35,571,572],{"class":487},"Access-Control-Allow-Methods",[35,574,490],{"class":41},[35,576,577],{"class":370},"GET, POST, PUT, DELETE\n",[35,579,580,583,585],{"class":37,"line":45},[35,581,582],{"class":487},"Access-Control-Allow-Headers",[35,584,490],{"class":41},[35,586,587],{"class":370},"Authorization, Content-Type\n",[223,589,591],{"id":590},"a-note-on-credentials","A Note on Credentials",[14,593,594,595,597,598,601,602,605],{},"If your request includes cookies or an ",[32,596,277],{}," header, you ",[75,599,600],{},"cannot"," use ",[32,603,604],{},"*",". You must specify the exact origin:",[25,607,609],{"className":361,"code":608,"language":363,"meta":30,"style":30},"Access-Control-Allow-Origin: https://app.com\nAccess-Control-Allow-Credentials: true\n",[32,610,611,619],{"__ignoreMap":30},[35,612,613,615,617],{"class":37,"line":38},[35,614,506],{"class":487},[35,616,490],{"class":41},[35,618,537],{"class":370},[35,620,621,624,626],{"class":37,"line":45},[35,622,623],{"class":487},"Access-Control-Allow-Credentials",[35,625,490],{"class":41},[35,627,629],{"class":628},"sDLfK","true\n",[64,631],{},[67,633,635],{"id":634},"the-biggest-misunderstanding","The Biggest Misunderstanding",[14,637,638],{},"Let's put it plainly:",[443,640,641],{},[14,642,643,646,647,650,653],{},[75,644,645],{},"\"CORS blocked my request\""," — what devs say",[648,649],"br",{},[75,651,652],{},"\"SOP blocked access to the response, because CORS headers were missing\""," — what actually happened",[14,655,656,657,659],{},"The request reached your server. Your server responded. The browser saw no ",[32,658,506],{}," header (or the wrong value), and blocked your JavaScript from reading it.",[14,661,662],{},"CORS is not an attacker. It's just the server's way of opting in to cross-origin access — and if the server hasn't opted in, the browser enforces the default deny.",[64,664],{},[67,666,668],{"id":667},"why-postman-curl-and-postpilot-dont-have-this-problem","Why Postman, curl, and PostPilot Don't Have This Problem",[14,670,671],{},"If you've ever tested an API in Postman or curl and it worked fine, but it broke in the browser — this is why.",[14,673,674,675,678],{},"SOP is a ",[75,676,677],{},"browser-only"," rule. When curl sends a request, there's no browser enforcing same-origin checks. The response comes back and you see it directly.",[443,680,681],{},[14,682,683],{},"If it works in PostPilot but not in the browser → you have a CORS misconfiguration, not a backend bug.",[14,685,686],{},"This is a useful first signal. It tells you your API is healthy, and the fix belongs on the backend (adding the right CORS headers) or in your server config.",[64,688],{},[67,690,692],{"id":691},"practical-guide-debugging-cors-issues","Practical Guide: Debugging CORS Issues",[223,694,696],{"id":695},"before-you-start-simple-or-non-simple","Before You Start: Simple or Non-Simple?",[14,698,699,700,702,703,705],{},"Check your request: what method are you using? Do you have ",[32,701,277],{}," or ",[32,704,310],{},"? If yes — it's non-simple and a preflight is involved. This determines which steps below apply to you.",[14,707,708,709,713],{},"Not sure? Jump back to ",[710,711,218],"a",{"href":712},"#simple-vs-non-simple-requests",".",[64,715],{},[223,717,719],{"id":718},"step-1-send-the-options-preflight-request","Step 1: Send the OPTIONS Preflight Request",[443,721,722],{},[14,723,724,727],{},[75,725,726],{},"Simple requests:"," skip this step — no preflight is sent.",[14,729,730],{},"For non-simple requests, manually fire the preflight to see exactly what the server returns.",[732,733,735],"h4",{"id":734},"using-curl","Using curl:",[25,737,741],{"className":738,"code":739,"language":740,"meta":30,"style":30},"language-bash shiki shiki-themes github-dark","curl -X OPTIONS https://api.example.com/users \\\n  -H \"Origin: http://localhost:3000\" \\\n  -H \"Access-Control-Request-Method: GET\" \\\n  -v\n","bash",[32,742,743,761,771,780],{"__ignoreMap":30},[35,744,745,749,752,755,758],{"class":37,"line":38},[35,746,748],{"class":747},"svObZ","curl",[35,750,751],{"class":628}," -X",[35,753,754],{"class":370}," OPTIONS",[35,756,757],{"class":370}," https://api.example.com/users",[35,759,760],{"class":628}," \\\n",[35,762,763,766,769],{"class":37,"line":45},[35,764,765],{"class":628},"  -H",[35,767,768],{"class":370}," \"Origin: http://localhost:3000\"",[35,770,760],{"class":628},[35,772,773,775,778],{"class":37,"line":51},[35,774,765],{"class":628},[35,776,777],{"class":370}," \"Access-Control-Request-Method: GET\"",[35,779,760],{"class":628},[35,781,782],{"class":37,"line":384},[35,783,784],{"class":628},"  -v\n",[235,786,787,792,798],{},[238,788,789,791],{},[32,790,476],{}," — your frontend's origin",[238,793,794,797],{},[32,795,796],{},"Access-Control-Request-Method"," — the method of your actual request",[238,799,800,803],{},[32,801,802],{},"-v"," — verbose, so you can see response headers",[14,805,806],{},"If you're sending custom headers in the real request, add them too:",[25,808,810],{"className":738,"code":809,"language":740,"meta":30,"style":30},"curl -X OPTIONS https://api.example.com/users \\\n  -H \"Origin: http://localhost:3000\" \\\n  -H \"Access-Control-Request-Method: GET\" \\\n  -H \"Access-Control-Request-Headers: Authorization\" \\\n  -v\n",[32,811,812,824,832,840,849],{"__ignoreMap":30},[35,813,814,816,818,820,822],{"class":37,"line":38},[35,815,748],{"class":747},[35,817,751],{"class":628},[35,819,754],{"class":370},[35,821,757],{"class":370},[35,823,760],{"class":628},[35,825,826,828,830],{"class":37,"line":45},[35,827,765],{"class":628},[35,829,768],{"class":370},[35,831,760],{"class":628},[35,833,834,836,838],{"class":37,"line":51},[35,835,765],{"class":628},[35,837,777],{"class":370},[35,839,760],{"class":628},[35,841,842,844,847],{"class":37,"line":384},[35,843,765],{"class":628},[35,845,846],{"class":370}," \"Access-Control-Request-Headers: Authorization\"",[35,848,760],{"class":628},[35,850,851],{"class":37,"line":419},[35,852,784],{"class":628},[732,854,856],{"id":855},"prefer-a-ui","Prefer a UI?",[14,858,859,860,866],{},"Paste the curl command directly into ",[710,861,865],{"href":862,"rel":863},"https://app.postpilot.dev",[864],"nofollow","PostPilot"," — it parses the command and builds the request for you. You can then inspect response headers in a clean table view without squinting at terminal output.",[64,868],{},[223,870,872],{"id":871},"step-2-what-to-look-for-in-the-preflight-response","Step 2: What to Look For in the Preflight Response",[14,874,875,876,879],{},"You ",[75,877,878],{},"must"," see:",[25,881,884],{"className":882,"code":499,"language":883,"meta":30,"style":30},"language-http shiki shiki-themes github-dark","http",[32,885,886],{"__ignoreMap":30},[35,887,888],{"class":37,"line":38},[35,889,499],{},[14,891,892],{},"Depending on your request, you may also need:",[25,894,896],{"className":882,"code":895,"language":883,"meta":30,"style":30},"Access-Control-Allow-Methods: GET, POST, PUT\nAccess-Control-Allow-Headers: Authorization, Content-Type\n",[32,897,898,903],{"__ignoreMap":30},[35,899,900],{"class":37,"line":38},[35,901,902],{},"Access-Control-Allow-Methods: GET, POST, PUT\n",[35,904,905],{"class":37,"line":45},[35,906,907],{},"Access-Control-Allow-Headers: Authorization, Content-Type\n",[732,909,911],{"id":910},"common-reasons-the-preflight-fails","Common reasons the preflight fails:",[87,913,914,924],{},[90,915,916],{},[93,917,918,921],{},[96,919,920],{},"Problem",[96,922,923],{},"Symptom",[103,925,926,936,950,958,970],{},[93,927,928,933],{},[108,929,930,931],{},"Missing ",[32,932,506],{},[108,934,935],{},"Header not present in response",[93,937,938,941],{},[108,939,940],{},"Origin mismatch",[108,942,943,946,947,949],{},[32,944,945],{},"http://"," vs ",[32,948,115],{},", wrong port",[93,951,952,955],{},[108,953,954],{},"Backend not handling OPTIONS",[108,956,957],{},"Returns 404 or 405 for OPTIONS",[93,959,960,963],{},[108,961,962],{},"Custom header not whitelisted",[108,964,965,967,968],{},[32,966,277],{}," not in ",[32,969,582],{},[93,971,972,978],{},[108,973,974,975,977],{},"Using ",[32,976,604],{}," with credentials",[108,979,980],{},"Browser rejects wildcard when cookies are involved",[64,982],{},[223,984,986],{"id":985},"step-3-send-the-actual-target-request","Step 3: Send the Actual Target Request",[14,988,989],{},"Once the preflight looks good (or if you're dealing with a simple request), send the actual request and check its response headers too.",[14,991,992,995,996,998],{},[75,993,994],{},"This is important:"," the browser checks ",[32,997,506],{}," on the actual response as well — not just the preflight. Both must return the correct header.",[14,1000,1001],{},"You can do this with curl:",[25,1003,1005],{"className":738,"code":1004,"language":740,"meta":30,"style":30},"curl https://api.example.com/users \\\n  -H \"Origin: http://localhost:3000\" \\\n  -H \"Authorization: Bearer your-token\" \\\n  -v\n",[32,1006,1007,1015,1023,1032],{"__ignoreMap":30},[35,1008,1009,1011,1013],{"class":37,"line":38},[35,1010,748],{"class":747},[35,1012,757],{"class":370},[35,1014,760],{"class":628},[35,1016,1017,1019,1021],{"class":37,"line":45},[35,1018,765],{"class":628},[35,1020,768],{"class":370},[35,1022,760],{"class":628},[35,1024,1025,1027,1030],{"class":37,"line":51},[35,1026,765],{"class":628},[35,1028,1029],{"class":370}," \"Authorization: Bearer your-token\"",[35,1031,760],{"class":628},[35,1033,1034],{"class":37,"line":384},[35,1035,784],{"class":628},[14,1037,1038,1039,1041],{},"Or in PostPilot, build out the full request with your real headers and inspect the response. Look for the same ",[32,1040,506],{}," header you confirmed in Step 2.",[14,1043,1044],{},"If the actual response is missing it — that's your bug. The preflight can pass and the real request can still be blocked.",[64,1046],{},[67,1048,1050],{"id":1049},"configuring-cors-in-your-backend","Configuring CORS in Your Backend",[14,1052,1053],{},"Once you've confirmed what's missing, here's how to add it.",[223,1055,1057],{"id":1056},"quarkus","Quarkus",[25,1059,1063],{"className":1060,"code":1061,"language":1062,"meta":30,"style":30},"language-properties shiki shiki-themes github-dark","quarkus.http.cors=true\nquarkus.http.cors.origins=http://localhost:3000\nquarkus.http.cors.methods=GET,POST,PUT,DELETE\nquarkus.http.cors.headers=Authorization,Content-Type\n","properties",[32,1064,1065,1070,1075,1080],{"__ignoreMap":30},[35,1066,1067],{"class":37,"line":38},[35,1068,1069],{},"quarkus.http.cors=true\n",[35,1071,1072],{"class":37,"line":45},[35,1073,1074],{},"quarkus.http.cors.origins=http://localhost:3000\n",[35,1076,1077],{"class":37,"line":51},[35,1078,1079],{},"quarkus.http.cors.methods=GET,POST,PUT,DELETE\n",[35,1081,1082],{"class":37,"line":384},[35,1083,1084],{},"quarkus.http.cors.headers=Authorization,Content-Type\n",[223,1086,1088],{"id":1087},"spring-boot","Spring Boot",[732,1090,1092],{"id":1091},"per-endpoint-with-annotation","Per-endpoint with annotation:",[25,1094,1098],{"className":1095,"code":1096,"language":1097,"meta":30,"style":30},"language-java shiki shiki-themes github-dark","@CrossOrigin(origins = \"http://localhost:3000\")\n@GetMapping(\"/users\")\npublic List\u003CUser> getUsers() { ... }\n","java",[32,1099,1100,1105,1110],{"__ignoreMap":30},[35,1101,1102],{"class":37,"line":38},[35,1103,1104],{},"@CrossOrigin(origins = \"http://localhost:3000\")\n",[35,1106,1107],{"class":37,"line":45},[35,1108,1109],{},"@GetMapping(\"/users\")\n",[35,1111,1112],{"class":37,"line":51},[35,1113,1114],{},"public List\u003CUser> getUsers() { ... }\n",[732,1116,1118],{"id":1117},"global-configuration","Global configuration:",[25,1120,1122],{"className":1095,"code":1121,"language":1097,"meta":30,"style":30},"@Configuration\npublic class WebConfig implements WebMvcConfigurer {\n    @Override\n    public void addCorsMappings(CorsRegistry registry) {\n        registry.addMapping(\"/**\")\n            .allowedOrigins(\"http://localhost:3000\")\n            .allowedMethods(\"GET\", \"POST\", \"PUT\", \"DELETE\")\n            .allowedHeaders(\"*\");\n    }\n}\n",[32,1123,1124,1129,1134,1139,1144,1149,1154,1159,1165,1171],{"__ignoreMap":30},[35,1125,1126],{"class":37,"line":38},[35,1127,1128],{},"@Configuration\n",[35,1130,1131],{"class":37,"line":45},[35,1132,1133],{},"public class WebConfig implements WebMvcConfigurer {\n",[35,1135,1136],{"class":37,"line":51},[35,1137,1138],{},"    @Override\n",[35,1140,1141],{"class":37,"line":384},[35,1142,1143],{},"    public void addCorsMappings(CorsRegistry registry) {\n",[35,1145,1146],{"class":37,"line":419},[35,1147,1148],{},"        registry.addMapping(\"/**\")\n",[35,1150,1151],{"class":37,"line":425},[35,1152,1153],{},"            .allowedOrigins(\"http://localhost:3000\")\n",[35,1155,1156],{"class":37,"line":431},[35,1157,1158],{},"            .allowedMethods(\"GET\", \"POST\", \"PUT\", \"DELETE\")\n",[35,1160,1162],{"class":37,"line":1161},8,[35,1163,1164],{},"            .allowedHeaders(\"*\");\n",[35,1166,1168],{"class":37,"line":1167},9,[35,1169,1170],{},"    }\n",[35,1172,1174],{"class":37,"line":1173},10,[35,1175,1176],{},"}\n",[223,1178,1180],{"id":1179},"express-nodejs","Express (Node.js)",[25,1182,1186],{"className":1183,"code":1184,"language":1185,"meta":30,"style":30},"language-js shiki shiki-themes github-dark","const cors = require('cors');\n\napp.use(cors({\n  origin: 'http://localhost:3000',\n  methods: ['GET', 'POST', 'PUT', 'DELETE'],\n  allowedHeaders: ['Authorization', 'Content-Type'],\n}));\n","js",[32,1187,1188,1212,1218,1234,1245,1271,1286],{"__ignoreMap":30},[35,1189,1190,1194,1197,1200,1203,1206,1209],{"class":37,"line":38},[35,1191,1193],{"class":1192},"snl16","const",[35,1195,1196],{"class":628}," cors",[35,1198,1199],{"class":1192}," =",[35,1201,1202],{"class":747}," require",[35,1204,1205],{"class":41},"(",[35,1207,1208],{"class":370},"'cors'",[35,1210,1211],{"class":41},");\n",[35,1213,1214],{"class":37,"line":45},[35,1215,1217],{"emptyLinePlaceholder":1216},true,"\n",[35,1219,1220,1223,1226,1228,1231],{"class":37,"line":51},[35,1221,1222],{"class":41},"app.",[35,1224,1225],{"class":747},"use",[35,1227,1205],{"class":41},[35,1229,1230],{"class":747},"cors",[35,1232,1233],{"class":41},"({\n",[35,1235,1236,1239,1242],{"class":37,"line":384},[35,1237,1238],{"class":41},"  origin: ",[35,1240,1241],{"class":370},"'http://localhost:3000'",[35,1243,1244],{"class":41},",\n",[35,1246,1247,1250,1253,1255,1258,1260,1263,1265,1268],{"class":37,"line":419},[35,1248,1249],{"class":41},"  methods: [",[35,1251,1252],{"class":370},"'GET'",[35,1254,244],{"class":41},[35,1256,1257],{"class":370},"'POST'",[35,1259,244],{"class":41},[35,1261,1262],{"class":370},"'PUT'",[35,1264,244],{"class":41},[35,1266,1267],{"class":370},"'DELETE'",[35,1269,1270],{"class":41},"],\n",[35,1272,1273,1276,1279,1281,1284],{"class":37,"line":425},[35,1274,1275],{"class":41},"  allowedHeaders: [",[35,1277,1278],{"class":370},"'Authorization'",[35,1280,244],{"class":41},[35,1282,1283],{"class":370},"'Content-Type'",[35,1285,1270],{"class":41},[35,1287,1288],{"class":37,"line":431},[35,1289,1290],{"class":41},"}));\n",[64,1292],{},[67,1294,1296],{"id":1295},"mental-model-recap","Mental Model Recap",[14,1298,1299],{},"When something breaks, run through this checklist in your head:",[1301,1302,1303,1309,1315,1321,1327],"ol",{},[238,1304,1305,1308],{},[75,1306,1307],{},"SOP is the default"," — browsers block cross-origin response access unless told otherwise",[238,1310,1311,1314],{},[75,1312,1313],{},"CORS is the opt-in"," — your server adds headers to grant permission",[238,1316,1317,1320],{},[75,1318,1319],{},"Non-simple requests have a preflight"," — OPTIONS runs first, and must succeed before the real request is sent",[238,1322,1323,1326],{},[75,1324,1325],{},"Both responses need CORS headers"," — preflight response and actual response",[238,1328,1329,1332],{},[75,1330,1331],{},"API clients bypass this entirely"," — no browser, no SOP, no CORS checks",[443,1334,1335],{},[14,1336,1337],{},"\"CORS doesn't block — your browser does. CORS is how your server tells the browser to stand down.\"",[64,1339],{},[67,1341,1343],{"id":1342},"conclusion","Conclusion",[14,1345,1346],{},"The next time you see that CORS error, don't reach for a Chrome extension to disable CORS checks. Instead:",[1301,1348,1349,1352,1355,1361],{},[238,1350,1351],{},"Check if it's a simple or non-simple request",[238,1353,1354],{},"For non-simple requests, manually fire the OPTIONS preflight and read the response",[238,1356,1357,1358,1360],{},"Confirm ",[32,1359,506],{}," appears on both the preflight and actual response",[238,1362,1363],{},"Fix the missing headers on your backend",[14,1365,1366],{},"CORS errors are predictable once you understand the model. The browser is doing exactly what it's supposed to do — your job is to configure the server to grant permission explicitly.",[14,1368,1369,1370,1374],{},"Tools like ",[710,1371,865],{"href":1372,"rel":1373},"https://postpilot.app",[864]," make this faster: send the OPTIONS request, inspect the headers visually, confirm the actual request also returns the right headers — all without leaving your browser.",[14,1376,1377],{},"Once it clicks, CORS stops feeling like a random wall and starts feeling like a system you're in control of.",[1379,1380,1381],"style",{},"html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":30,"searchDepth":45,"depth":45,"links":1383},[1384,1385,1389,1393,1394,1399,1400,1401,1407,1412,1413],{"id":69,"depth":45,"text":70},{"id":217,"depth":45,"text":218,"children":1386},[1387,1388],{"id":225,"depth":51,"text":226},{"id":287,"depth":51,"text":288},{"id":337,"depth":45,"text":338,"children":1390},[1391,1392],{"id":357,"depth":51,"text":358},{"id":390,"depth":51,"text":391},{"id":452,"depth":45,"text":453},{"id":518,"depth":45,"text":519,"children":1395},[1396,1397,1398],{"id":522,"depth":51,"text":523},{"id":555,"depth":51,"text":556},{"id":590,"depth":51,"text":591},{"id":634,"depth":45,"text":635},{"id":667,"depth":45,"text":668},{"id":691,"depth":45,"text":692,"children":1402},[1403,1404,1405,1406],{"id":695,"depth":51,"text":696},{"id":718,"depth":51,"text":719},{"id":871,"depth":51,"text":872},{"id":985,"depth":51,"text":986},{"id":1049,"depth":45,"text":1050,"children":1408},[1409,1410,1411],{"id":1056,"depth":51,"text":1057},{"id":1087,"depth":51,"text":1088},{"id":1179,"depth":51,"text":1180},{"id":1295,"depth":45,"text":1296},{"id":1342,"depth":45,"text":1343},"2026-04-11T00:00:00.000Z","Stop blaming CORS. Learn how Same-Origin Policy and CORS actually work together, understand simple vs non-simple requests, and debug CORS issues step by step.","md",null,{},"/blog/understanding-cors-for-api-developers",{"title":5,"description":1415},{"loc":1419},"blog/understanding-cors-for-api-developers","X4LngLUnXLIkohRX8WjyFC4KO979Ow3uvvMv5eC_Few",1775922260765]