diff --git a/packages/locales/lib/human/en.json b/packages/locales/lib/human/en.json
index fbda09d08..e753f3fa7 100644
--- a/packages/locales/lib/human/en.json
+++ b/packages/locales/lib/human/en.json
@@ -376,6 +376,7 @@
   "with_ar": "With AR",
   "both": "Both",
   "without_ar": "Without AR",
+  "shiny_probability": "Shiny: {{p}}",
   "exclude_quest_multi": "Exclude {{reward}}",
   "cluster_limit_0": "{{variable_0}} limit ({{variable_1}}) has been hit",
   "cluster_limit_1": "Please zoom in or narrow your filters",
diff --git a/packages/types/lib/scanner.d.ts b/packages/types/lib/scanner.d.ts
index 11fb72acc..ccb48ecb9 100644
--- a/packages/types/lib/scanner.d.ts
+++ b/packages/types/lib/scanner.d.ts
@@ -121,6 +121,7 @@ export interface Quest {
   quest_gender_id: Gender
   quest_costume_id: number
   quest_shiny: number
+  quest_shiny_probability?: number
   mega_pokemon_id: number
   mega_amount: number
   candy_pokemon_id: number
diff --git a/server/src/graphql/typeDefs/scanner.graphql b/server/src/graphql/typeDefs/scanner.graphql
index eee547c89..dc2cd829a 100644
--- a/server/src/graphql/typeDefs/scanner.graphql
+++ b/server/src/graphql/typeDefs/scanner.graphql
@@ -83,6 +83,7 @@ type Quest {
   quest_gender_id: Int
   quest_costume_id: Int
   quest_shiny: Int
+  quest_shiny_probability: Float
   mega_pokemon_id: Int
   mega_amount: Int
   candy_pokemon_id: Int
diff --git a/server/src/models/Pokestop.js b/server/src/models/Pokestop.js
index 1f3c28ef5..64bb7ef22 100644
--- a/server/src/models/Pokestop.js
+++ b/server/src/models/Pokestop.js
@@ -875,6 +875,7 @@ class Pokestop extends Model {
                   'quest_costume_id',
                   'quest_gender_id',
                   'quest_shiny',
+                  'quest_shiny_probability',
                 )
                 break
               case 9:
diff --git a/src/features/pokestop/PokestopPopup.jsx b/src/features/pokestop/PokestopPopup.jsx
index 1eb134cda..747cde9c4 100644
--- a/src/features/pokestop/PokestopPopup.jsx
+++ b/src/features/pokestop/PokestopPopup.jsx
@@ -120,7 +120,28 @@ export function PokestopPopup({
                       <Divider light flexItem className="popup-divider" />
                     ) : null}
                     <RewardInfo {...quest} />
-                    <QuestConditions {...quest} />
+                    <Grid
+                      xs={9}
+                      style={{
+                        textAlign: 'center',
+                        maxHeight: 150,
+                        overflow: 'auto',
+                      }}
+                    >
+                      <QuestConditions {...quest} />
+                      {quest.quest_shiny_probability && (
+                        <>
+                          <br />
+                          <Typography variant="caption">
+                            {t('shiny_probability', {
+                              p: readableProbability(
+                                quest.quest_shiny_probability,
+                              ),
+                            })}
+                          </Typography>
+                        </>
+                      )}
+                    </Grid>
                   </React.Fragment>
                 ))}
               {hasLure && (
@@ -512,6 +533,15 @@ const RewardInfo = ({ with_ar, ...quest }) => {
   )
 }
 
+const readableProbability = (x) => {
+  if (x <= 0) return '🚫'
+  const x_1 = Math.round(1 / x)
+  const percent = Math.round(x * 100)
+  return Math.abs(1 / x_1 - x) < Math.abs(percent * 0.01 - x)
+    ? `1/${x_1}`
+    : `${percent}%`
+}
+
 /**
  *
  * @param {Omit<import('@rm/types').Quest, 'key'>} props
@@ -528,22 +558,16 @@ const QuestConditions = ({
   const madQuestText = useStorage((s) => s.userSettings.pokestops.madQuestText)
 
   if (madQuestText && quest_task) {
-    return (
-      <Grid xs={9} textAlign="center">
-        <Typography variant="caption">{quest_task}</Typography>
-      </Grid>
-    )
+    return <Typography variant="caption">{quest_task}</Typography>
   }
 
   if (quest_title && !quest_title.includes('geotarget')) {
     const normalized = `quest_title_${quest_title.toLowerCase()}`
     if (i18n.exists(normalized)) {
       return (
-        <Grid xs={9} textAlign="center">
-          <Typography variant="caption">
-            <Trans i18nKey={normalized}>{{ amount_0: quest_target }}</Trans>
-          </Typography>
-        </Grid>
+        <Typography variant="caption">
+          <Trans i18nKey={normalized}>{{ amount_0: quest_target }}</Trans>
+        </Typography>
       )
     }
   }
@@ -615,10 +639,7 @@ const QuestConditions = ({
     }
   }
   return (
-    <Grid
-      xs={9}
-      style={{ textAlign: 'center', maxHeight: 150, overflow: 'auto' }}
-    >
+    <>
       {primaryCondition}
       {type1 && (
         <>
@@ -636,7 +657,7 @@ const QuestConditions = ({
           </Typography>
         </>
       )}
-    </Grid>
+    </>
   )
 }
 
diff --git a/src/services/queries/pokestop.js b/src/services/queries/pokestop.js
index 5fa31a047..a51be6f33 100644
--- a/src/services/queries/pokestop.js
+++ b/src/services/queries/pokestop.js
@@ -43,6 +43,7 @@ const quest = gql`
       quest_gender_id
       quest_costume_id
       quest_shiny
+      quest_shiny_probability
       mega_pokemon_id
       mega_amount
       candy_pokemon_id