[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-permissions-api-query-before-prompt":44,"search-suggestions":60,"quiz-article-permissions-api-query-before-prompt":107},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a0ddae-4e21-758a-8161-dc29024117b3","permissions-api-query-before-prompt","PRACTICE_QUIZ","The Permissions API: Reading State Without Asking","Checks whether you understood why the feature APIs can't safely 'peek' at permission state, and how navigator.permissions.query() actually works.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},7,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,85,89,93,97,100,104],{"slug":62,"name":63,"articles":64},"webdev","Webdev",111,{"slug":66,"name":67,"articles":68},"javascript","Javascript",93,{"slug":70,"name":71,"articles":72},"frontend","Frontend",73,{"slug":74,"name":75,"articles":76},"tutorial","Tutorial",39,{"slug":78,"name":79,"articles":80},"css","Css",36,{"slug":82,"name":83,"articles":84},"typescript","Typescript",16,{"slug":86,"name":87,"articles":88},"performance","Performance",14,{"slug":90,"name":91,"articles":92},"react","React",13,{"slug":94,"name":95,"articles":96},"browser","Browser",10,{"slug":98,"name":99,"articles":96},"node","Node",{"slug":101,"name":102,"articles":103},"html","Html",8,{"slug":105,"name":106,"articles":51},"accessibility","Accessibility",{"id":108,"slug":46,"title":109,"subtitle":52,"excerpt":110,"coverUrl":111,"locale":13,"readingMinutes":112,"publishedAt":113,"viewCount":114,"likeCount":19,"commentCount":19,"author":115,"vertical":120,"topic":121,"tags":123,"_count":128,"playground":130,"body":132,"bodyMd":261,"seo":262,"translationGroupId":265,"series":52,"podcastUrl":52,"verticalId":5,"thread":266,"assessments":268,"translations":271,"quiz":273},"01a0ddae-4ca5-7432-9f7d-559b2267c5dd","getCurrentPosition() Doesn't Just Check — It Prompts","You want to peek at whether location access is already granted before showing your own 'why we need this' banner. Calling getCurrentPosition() to find out fires the real system prompt. navigator.permissions.query() reads the state without ever asking.","\u002Fmedia\u002Fcovers\u002Fpermissions-api-query-before-prompt.png",5,"2026-09-26T12:26:10.756Z",63,{"id":116,"name":117,"username":118,"avatarUrl":52,"headline":119},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":66,"name":122},"JavaScript",[124,125,126,127],{"slug":66,"name":67,"color":52},{"slug":62,"name":63,"color":52},{"slug":94,"name":95,"color":52},{"slug":74,"name":75,"color":52},{"assessments":129},1,{"slug":46,"title":131},"Permissions API — check without asking",{"blocks":133,"version":129},[134,138,141,147,150,153,158,161,164,167,170,173,177,180,183,187,190,193,196,200,203,206,209,212,215,218,221,224,227,230,233,236,239,242,245,248,251,254],{"id":135,"html":136,"type":137},"b1","\u003Cp>You&#39;re building a &quot;find stores near you&quot; banner. The right move, UX-wise, is to explain \u003Cem>why\u003C\u002Fem> you want the visitor&#39;s location before the browser&#39;s blunt little system dialog interrupts them — but only if that dialog hasn&#39;t already fired. If they already said yes, just get the location and skip the banner. If they already said no, don&#39;t ask again; show a manual city picker instead.\u003C\u002Fp>","paragraph",{"id":139,"html":140,"type":137},"b2","\u003Cp>So before you render anything, you write a quick check: has this visitor already decided?\u003C\u002Fp>",{"id":142,"code":143,"type":144,"language":145,"highlight":146},"b3","navigator.geolocation.getCurrentPosition(\n  () => showStoresNearMe(),\n  (err) => {\n    if (err.code === err.PERMISSION_DENIED) showManualPicker();\n  }\n);","code","js",[],{"id":148,"html":149,"type":137},"b4","\u003Cp>You test it in a fresh incognito window. The system permission dialog pops up immediately — the exact one you were trying to gate behind your own friendly copy. Not after your banner. Instead of it.\u003C\u002Fp>",{"id":151,"html":152,"type":137},"b5","\u003Cp>\u003Cstrong>Guess before you scroll:\u003C\u002Fstrong> the code above isn&#39;t buggy. It&#39;s doing exactly what \u003Ccode>getCurrentPosition\u003C\u002Fcode> is supposed to do. The bug is in the assumption that calling it was a way to \u003Cem>check\u003C\u002Fem> something.\u003C\u002Fp>",{"id":154,"html":155,"text":156,"type":157,"level":31},"b6","There&#39;s no &quot;just checking&quot; with the Geolocation API","There's no \"just checking\" with the Geolocation API","heading",{"id":159,"html":160,"type":137},"b7","\u003Cp>\u003Ccode>getCurrentPosition\u003C\u002Fcode> doesn&#39;t have a peek mode. Every call is a real request for the visitor&#39;s location, and the browser treats it that way: if the permission state for this origin is still undecided, it shows the system prompt right then, synchronously with your call. If the visitor already granted permission, it skips straight to fetching a position. If they already denied it, your error callback fires with \u003Ccode>PERMISSION_DENIED\u003C\u002Fcode> — no dialog, but you only find that out \u003Cem>after\u003C\u002Fem> asking.\u003C\u002Fp>",{"id":162,"html":163,"type":137},"b8","\u003Cp>That last case is the closest thing to a &quot;check&quot; the API gives you, and it&#39;s a bad one: to learn the state, you have to make the same call you&#39;d make to actually use the feature, and if the state happens to be &quot;undecided,&quot; making that call \u003Cem>is\u003C\u002Fem> deciding it — badly, with zero context, at a moment you didn&#39;t choose.\u003C\u002Fp>",{"id":165,"html":166,"type":137},"b9","\u003Cp>There&#39;s a reason this feels like a design gap and not a skill issue: the thing you actually want to read — &quot;has this origin already been asked, and what did the visitor say?&quot; — isn&#39;t geolocation-specific data at all. It&#39;s account-keeping the browser does for every permission-gated feature, and the Geolocation API was never built to expose it. You were reaching for a getter through a function whose entire job is to \u003Cem>act\u003C\u002Fem>.\u003C\u002Fp>",{"id":168,"html":169,"text":169,"type":157,"level":31},"b10","The API that only reads",{"id":171,"html":172,"type":137},"b11","\u003Cp>The \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FPermissions_API\">Permissions API\u003C\u002Fa> is that getter. \u003Ccode>navigator.permissions.query()\u003C\u002Fcode> asks the browser &quot;what&#39;s the current state of this permission for this origin?&quot; and resolves with the answer — without ever triggering the system dialog itself, regardless of what the answer turns out to be:\u003C\u002Fp>",{"id":174,"code":175,"type":144,"language":145,"highlight":176},"b12","const status = await navigator.permissions.query({ name: \"geolocation\" });\n\nconsole.log(status.state); \u002F\u002F \"granted\" | \"denied\" | \"prompt\"\n\nif (status.state === \"granted\") {\n  showStoresNearMe(); \u002F\u002F safe to call getCurrentPosition silently\n} else if (status.state === \"prompt\") {\n  showWhyWeAskBanner(); \u002F\u002F explain first, THEN call getCurrentPosition\n} else {\n  showManualPicker(); \u002F\u002F already denied — don't ask again\n}",[],{"id":178,"html":179,"type":137},"b13","\u003Cp>\u003Ccode>prompt\u003C\u002Fcode> means &quot;undecided&quot; — the browser hasn&#39;t shown this visitor a dialog for this permission on this origin yet. That&#39;s your one and only safe moment to show your own explanation first. Miss it — by calling the real API to &quot;check&quot; — and the browser&#39;s dialog wins the moment instead of yours.\u003C\u002Fp>",{"id":181,"html":182,"type":137},"b14","\u003Cp>The \u003Ccode>status\u003C\u002Fcode> object isn&#39;t a one-shot snapshot, either. It&#39;s a live handle you can subscribe to:\u003C\u002Fp>",{"id":184,"code":185,"type":144,"language":145,"highlight":186},"b15","status.addEventListener(\"change\", () => {\n  console.log(\"permission is now:\", status.state);\n});",[],{"id":188,"html":189,"type":137},"b16","\u003Cp>If the visitor opens your site&#39;s permission settings via the browser&#39;s UI (the little padlock icon) and flips geolocation while your tab is still open, \u003Ccode>change\u003C\u002Fcode> fires and \u003Ccode>status.state\u003C\u002Fcode> updates — no reload, no re-query. That&#39;s useful for exactly the kind of banner you&#39;re building: if someone denies it mid-session, you can swap the banner for the manual picker on the spot instead of waiting for their next visit.\u003C\u002Fp>",{"id":191,"html":192,"text":192,"type":157,"level":31},"b17","The catch: not every browser recognizes every name",{"id":194,"html":195,"type":137},"b18","\u003Cp>The Permissions API supports more than geolocation — \u003Ccode>notifications\u003C\u002Fcode>, \u003Ccode>camera\u003C\u002Fcode>, \u003Ccode>microphone\u003C\u002Fcode>, \u003Ccode>persistent-storage\u003C\u002Fcode>, \u003Ccode>clipboard-read\u003C\u002Fcode>, and others, each queried by its string name. But which names a given browser recognizes varies, and \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FPermissions\u002Fquery\">MDN is explicit\u003C\u002Fa> about what happens when you ask for one it doesn&#39;t: the promise doesn&#39;t resolve to some &quot;unknown&quot; state — it \u003Cstrong>rejects with a \u003Ccode>TypeError\u003C\u002Fcode>\u003C\u002Fstrong>.\u003C\u002Fp>",{"id":197,"code":198,"type":144,"language":145,"highlight":199},"b19","try {\n  const status = await navigator.permissions.query({ name: \"camera\" });\n  console.log(status.state);\n} catch (err) {\n  \u002F\u002F Some browsers don't recognize \"camera\" as a queryable name.\n  \u002F\u002F Fall back to your default UX rather than assuming a state.\n  console.log(\"couldn't read that permission here:\", err.message);\n}",[],{"id":201,"html":202,"type":137},"b20","\u003Cp>Skip the \u003Ccode>try\u002Fcatch\u003C\u002Fcode> and an unsupported name doesn&#39;t quietly do nothing — it throws an unhandled rejection in the middle of your permission logic. Given how much this varies by browser and by permission name, treat every \u003Ccode>query()\u003C\u002Fcode> call as something that can fail, not just something that can resolve to three states.\u003C\u002Fp>",{"id":204,"html":205,"text":205,"type":157,"level":31},"b21","The lesson",{"id":207,"html":208,"type":137},"b22","\u003Cp>&quot;Check first, then ask&quot; is good instinct. The mistake is reaching for the feature&#39;s own API to do the checking — \u003Ccode>getCurrentPosition\u003C\u002Fcode>, \u003Ccode>Notification.requestPermission\u003C\u002Fcode>, \u003Ccode>navigator.mediaDevices.getUserMedia\u003C\u002Fcode> — when each of those is built to \u003Cem>act\u003C\u002Fem>, and for a permission still in \u003Ccode>prompt\u003C\u002Fcode> state, acting and asking are the same button. The Permissions API is the one part of the platform whose entire job is to answer without acting. If you find yourself calling a feature just to see what it does before deciding whether to really call it, that&#39;s the tell: you wanted a read, and you reached for a write.\u003C\u002Fp>",{"id":210,"html":211,"type":137},"b23","\u003Cp>Numbers and prose only get you so far here — the difference between &quot;reading&quot; and &quot;doing&quot; is more convincing when you can trigger both yourself and watch which one leaves a system dialog behind.\u003C\u002Fp>",{"id":213,"html":214,"type":137},"b24","\u003C!-- playground:start -->",{"id":216,"html":217,"text":217,"type":157,"level":31},"b25","🎮 Try it yourself",{"id":219,"html":220,"type":137},"b26","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpermissions-api-query-before-prompt\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":222,"html":223,"type":137},"b27","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":225,"html":226,"type":137},"b28","\u003C!-- playground:end -->",{"id":228,"html":229,"type":137},"b29","\u003C!-- quiz:start -->",{"id":231,"html":232,"text":232,"type":157,"level":31},"b30","🧠 Test yourself",{"id":234,"html":235,"type":137},"b31","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpermissions-api-query-before-prompt\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":237,"html":238,"type":137},"b32","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":240,"html":241,"type":137},"b33","\u003C!-- quiz:end -->",{"id":243,"html":244,"type":137},"b34","\u003Cp>Go check the permission-gated features already in your app — location, notifications, the clipboard, the camera. If any of them decide whether to show your own explanation by calling the real API first, that&#39;s a prompt firing at a moment you didn&#39;t pick. What&#39;s the worst time your app&#39;s permission prompt has ever popped up on someone?\u003C\u002Fp>",{"id":246,"type":247},"b35","divider",{"id":249,"html":250,"type":137},"b36","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":252,"html":253,"type":137},"b37","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":255,"type":256,"items":257,"ordered":18},"b38","list",[258,259,260],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","You're building a \"find stores near you\" banner. The right move, UX-wise, is to explain *why* you want the visitor's location before the browser's blunt little system dialog interrupts them — but only if that dialog hasn't already fired. If they already said yes, just get the location and skip the banner. If they already said no, don't ask again; show a manual city picker instead.\n\nSo before you render anything, you write a quick check: has this visitor already decided?\n\n```js\nnavigator.geolocation.getCurrentPosition(\n  () => showStoresNearMe(),\n  (err) => {\n    if (err.code === err.PERMISSION_DENIED) showManualPicker();\n  }\n);\n```\n\nYou test it in a fresh incognito window. The system permission dialog pops up immediately — the exact one you were trying to gate behind your own friendly copy. Not after your banner. Instead of it.\n\n**Guess before you scroll:** the code above isn't buggy. It's doing exactly what `getCurrentPosition` is supposed to do. The bug is in the assumption that calling it was a way to *check* something.\n\n## There's no \"just checking\" with the Geolocation API\n\n`getCurrentPosition` doesn't have a peek mode. Every call is a real request for the visitor's location, and the browser treats it that way: if the permission state for this origin is still undecided, it shows the system prompt right then, synchronously with your call. If the visitor already granted permission, it skips straight to fetching a position. If they already denied it, your error callback fires with `PERMISSION_DENIED` — no dialog, but you only find that out *after* asking.\n\nThat last case is the closest thing to a \"check\" the API gives you, and it's a bad one: to learn the state, you have to make the same call you'd make to actually use the feature, and if the state happens to be \"undecided,\" making that call *is* deciding it — badly, with zero context, at a moment you didn't choose.\n\nThere's a reason this feels like a design gap and not a skill issue: the thing you actually want to read — \"has this origin already been asked, and what did the visitor say?\" — isn't geolocation-specific data at all. It's account-keeping the browser does for every permission-gated feature, and the Geolocation API was never built to expose it. You were reaching for a getter through a function whose entire job is to *act*.\n\n## The API that only reads\n\nThe [Permissions API](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FPermissions_API) is that getter. `navigator.permissions.query()` asks the browser \"what's the current state of this permission for this origin?\" and resolves with the answer — without ever triggering the system dialog itself, regardless of what the answer turns out to be:\n\n```js\nconst status = await navigator.permissions.query({ name: \"geolocation\" });\n\nconsole.log(status.state); \u002F\u002F \"granted\" | \"denied\" | \"prompt\"\n\nif (status.state === \"granted\") {\n  showStoresNearMe(); \u002F\u002F safe to call getCurrentPosition silently\n} else if (status.state === \"prompt\") {\n  showWhyWeAskBanner(); \u002F\u002F explain first, THEN call getCurrentPosition\n} else {\n  showManualPicker(); \u002F\u002F already denied — don't ask again\n}\n```\n\n`prompt` means \"undecided\" — the browser hasn't shown this visitor a dialog for this permission on this origin yet. That's your one and only safe moment to show your own explanation first. Miss it — by calling the real API to \"check\" — and the browser's dialog wins the moment instead of yours.\n\nThe `status` object isn't a one-shot snapshot, either. It's a live handle you can subscribe to:\n\n```js\nstatus.addEventListener(\"change\", () => {\n  console.log(\"permission is now:\", status.state);\n});\n```\n\nIf the visitor opens your site's permission settings via the browser's UI (the little padlock icon) and flips geolocation while your tab is still open, `change` fires and `status.state` updates — no reload, no re-query. That's useful for exactly the kind of banner you're building: if someone denies it mid-session, you can swap the banner for the manual picker on the spot instead of waiting for their next visit.\n\n## The catch: not every browser recognizes every name\n\nThe Permissions API supports more than geolocation — `notifications`, `camera`, `microphone`, `persistent-storage`, `clipboard-read`, and others, each queried by its string name. But which names a given browser recognizes varies, and [MDN is explicit](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FPermissions\u002Fquery) about what happens when you ask for one it doesn't: the promise doesn't resolve to some \"unknown\" state — it **rejects with a `TypeError`**.\n\n```js\ntry {\n  const status = await navigator.permissions.query({ name: \"camera\" });\n  console.log(status.state);\n} catch (err) {\n  \u002F\u002F Some browsers don't recognize \"camera\" as a queryable name.\n  \u002F\u002F Fall back to your default UX rather than assuming a state.\n  console.log(\"couldn't read that permission here:\", err.message);\n}\n```\n\nSkip the `try\u002Fcatch` and an unsupported name doesn't quietly do nothing — it throws an unhandled rejection in the middle of your permission logic. Given how much this varies by browser and by permission name, treat every `query()` call as something that can fail, not just something that can resolve to three states.\n\n## The lesson\n\n\"Check first, then ask\" is good instinct. The mistake is reaching for the feature's own API to do the checking — `getCurrentPosition`, `Notification.requestPermission`, `navigator.mediaDevices.getUserMedia` — when each of those is built to *act*, and for a permission still in `prompt` state, acting and asking are the same button. The Permissions API is the one part of the platform whose entire job is to answer without acting. If you find yourself calling a feature just to see what it does before deciding whether to really call it, that's the tell: you wanted a read, and you reached for a write.\n\nNumbers and prose only get you so far here — the difference between \"reading\" and \"doing\" is more convincing when you can trigger both yourself and watch which one leaves a system dialog behind.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpermissions-api-query-before-prompt\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpermissions-api-query-before-prompt\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\nGo check the permission-gated features already in your app — location, notifications, the clipboard, the camera. If any of them decide whether to show your own explanation by calling the real API first, that's a prompt firing at a moment you didn't pick. What's the worst time your app's permission prompt has ever popped up on someone?\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":109,"canonical":263,"description":264},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpermissions-api-query-before-prompt","You want to peek at whether location access is already granted before showing your own 'why we need this' banner. Calling getCurrentPosition() to find out fires the real system pro","01a0ddae-4ca5-7432-9f7d-5ab019ef5ba4",{"id":267,"locked":18},"01a0ddae-4df6-71fd-918c-0ff8fe7cf8df",[269],{"id":45,"slug":46,"title":48,"_count":270},{"questions":51},[272],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":274,"questionCount":51},{"questions":51}]