// Get moon data for New York today
const response = await fetch("https://worlddataapi.com/v1/moon/40.71,-74.01", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await response.json();
console.log(data.phase_name); // "Waning Gibbous"
console.log(data.illumination); // 72.5
console.log(data.moonrise); // "2026-01-15T21:45:00-05:00"
import requests
response = requests.get(
"https://worlddataapi.com/v1/moon/40.71,-74.01",
headers={"X-API-Key": "YOUR_API_KEY"}
)
data = response.json()
print(data["phase_name"]) # "Waning Gibbous"
print(data["illumination"]) # 72.5
print(data["moonrise"]) # "2026-01-15T21:45:00-05:00"
curl -X GET "https://worlddataapi.com/v1/moon/40.71,-74.01" \
-H "X-API-Key: YOUR_API_KEY"
This guide covers building a moon phase display for your application, from fetching data to creating visual representations of lunar phases.
The Challenge#
Displaying accurate moon phases requires understanding the lunar cycle's complexity. The moon progresses through eight principal phases over approximately 29.5 days, but the illumination percentage changes continuously. Different applications need different levels of precision: a simple calendar might only show the four major phases, while a photography app needs exact illumination percentages and moonrise times.
Location matters too. Moonrise and moonset times vary by latitude and longitude, and sometimes the moon doesn't rise or set at all on a given day. Your display logic needs to handle these edge cases gracefully.
Prerequisites#
Before you start, you need:
A World Data API key (get one at worlddataapi.com)
Basic knowledge of JavaScript or Python
Understanding of async/await patterns for API calls
A location (latitude and longitude) for your queries
API Response Structure#
The moon endpoint returns:
{
"location": { "latitude": 40.71, "longitude": -74.01 },
"date": "2026-01-15",
"timezone": "America/New_York",
"moonrise": "2026-01-15T21:45:00-05:00",
"moonset": "2026-01-16T10:32:00-05:00",
"phase": "waning_gibbous",
"phase_name": "Waning Gibbous",
"illumination": 72.5,
"age_days": 18.3
}
Phase Values#
| phase | phase_name | Illumination Range |
|---|---|---|
new | New Moon | 0% |
waxing_crescent | Waxing Crescent | 1-49% (increasing) |
first_quarter | First Quarter | 50% (right half lit) |
waxing_gibbous | Waxing Gibbous | 51-99% (increasing) |
full | Full Moon | 100% |
waning_gibbous | Waning Gibbous | 99-51% (decreasing) |
last_quarter | Last Quarter | 50% (left half lit) |
waning_crescent | Waning Crescent | 49-1% (decreasing) |
Basic Implementation#
async function getMoonPhase(latitude, longitude, date = null) {
const url = date
? `https://worlddataapi.com/v1/moon/${latitude},${longitude}?date=${date}`
: `https://worlddataapi.com/v1/moon/${latitude},${longitude}`;
const response = await fetch(url, {
headers: { "X-API-Key": API_KEY },
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
// Get today's moon phase
const moon = await getMoonPhase(40.71, -74.01);
console.log(`${moon.phase_name} - ${moon.illumination}% illuminated`);
Visual Representation#
Using Emoji#
The quickest approach — map phases to moon emoji:
const MOON_EMOJI = {
new: "\u{1F311}", // new moon
waxing_crescent: "\u{1F312}",
first_quarter: "\u{1F313}",
waxing_gibbous: "\u{1F314}",
full: "\u{1F315}", // full moon
waning_gibbous: "\u{1F316}",
last_quarter: "\u{1F317}",
waning_crescent: "\u{1F318}",
};
function getMoonEmoji(phase) {
return MOON_EMOJI[phase] || "\u{1F319}";
}
// Usage
const moon = await getMoonPhase(40.71, -74.01);
console.log(`${getMoonEmoji(moon.phase)} ${moon.phase_name}`);
// Output: [moon emoji] Waning Gibbous
CSS Moon#
Create a moon using CSS gradients:
function CSSMoon({ phase, illumination, size = 100 }) {
const getGradient = () => {
// Simplified: actual implementation needs hemisphere calculation
if (phase === "new") {
return "radial-gradient(circle, #1a1a2e 100%, #1a1a2e 100%)";
}
if (phase === "full") {
return "radial-gradient(circle, #f5f5dc 100%, #f5f5dc 100%)";
}
const isWaxing = phase.includes("waxing") || phase === "first_quarter";
const litPercent = illumination;
if (isWaxing) {
// Right side lit
return `linear-gradient(90deg, #1a1a2e ${100 - litPercent}%, #f5f5dc ${100 - litPercent}%)`;
} else {
// Left side lit
return `linear-gradient(90deg, #f5f5dc ${litPercent}%, #1a1a2e ${litPercent}%)`;
}
};
return (
<div
className="css-moon"
style={{
width: size,
height: size,
borderRadius: "50%",
background: getGradient(),
boxShadow: "inset 0 0 20px rgba(0,0,0,0.3)",
}}
/>
);
}
SVG Moon with Accurate Illumination#
More accurate visual using SVG clipping:
function SVGMoon({ illumination, phase, size = 100 }) {
const isWaxing =
phase.includes("waxing") ||
phase === "first_quarter" ||
phase === "full";
// Calculate the curve of the terminator (shadow edge)
const terminatorX = (illumination / 100) * size;
// For accurate rendering, we need to account for the spherical shape
const curveOffset = Math.sin((illumination / 100) * Math.PI) * (size * 0.2);
return (
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
{/* Dark side of the moon */}
<circle
cx={size / 2}
cy={size / 2}
r={size / 2 - 2}
fill="#1a1a2e"
/>
{/* Lit portion - simplified as ellipse */}
<clipPath id="moonClip">
<circle cx={size / 2} cy={size / 2} r={size / 2 - 2} />
</clipPath>
<ellipse
cx={
isWaxing
? size - (size * illumination) / 100 / 2
: (size * illumination) / 100 / 2
}
cy={size / 2}
rx={(size * illumination) / 100 / 2}
ry={size / 2 - 2}
fill="#f5f5dc"
clipPath="url(#moonClip)"
/>
{/* Subtle crater texture */}
<circle
cx={size * 0.3}
cy={size * 0.4}
r={size * 0.08}
fill="rgba(0,0,0,0.1)"
/>
<circle
cx={size * 0.6}
cy={size * 0.6}
r={size * 0.12}
fill="rgba(0,0,0,0.08)"
/>
<circle
cx={size * 0.45}
cy={size * 0.75}
r={size * 0.06}
fill="rgba(0,0,0,0.1)"
/>
</svg>
);
}
Using Moon Phase Images#
For the most accurate visuals, use pre-rendered images:
function ImageMoon({ phase, size = 100 }) {
// Assumes you have 8 moon phase images
const imagePath = `/images/moon-phases/${phase}.png`;
return (
<img
src={imagePath}
alt={phase.replace("_", " ")}
width={size}
height={size}
className="moon-image"
/>
);
}
Building a Complete Moon Widget#
function MoonWidget({ latitude, longitude }) {
const [moonData, setMoonData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
getMoonPhase(latitude, longitude)
.then(setMoonData)
.finally(() => setLoading(false));
}, [latitude, longitude]);
if (loading) return <div className="moon-widget loading">Loading...</div>;
if (!moonData) return null;
return (
<div className="moon-widget">
<SVGMoon
illumination={moonData.illumination}
phase={moonData.phase}
size={120}
/>
<div className="moon-info">
<h3>{moonData.phase_name}</h3>
<p className="illumination">
{Math.round(moonData.illumination)}% illuminated
</p>
<p className="age">
Day {Math.round(moonData.age_days)} of lunar cycle
</p>
</div>
<div className="moon-times">
{moonData.moonrise && (
<p>
<span className="label">Moonrise:</span>
<time>{formatTime(moonData.moonrise)}</time>
</p>
)}
{moonData.moonset && (
<p>
<span className="label">Moonset:</span>
<time>{formatTime(moonData.moonset)}</time>
</p>
)}
</div>
</div>
);
}
function formatTime(isoString) {
return new Date(isoString).toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
});
}
Moon Calendar#
Display a month of moon phases:
function MoonCalendar({ latitude, longitude, year, month }) {
const [phases, setPhases] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchMonth = async () => {
const daysInMonth = new Date(year, month + 1, 0).getDate();
const promises = [];
for (let day = 1; day <= daysInMonth; day++) {
const date = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
promises.push(getMoonPhase(latitude, longitude, date));
}
const results = await Promise.all(promises);
setPhases(results);
setLoading(false);
};
fetchMonth();
}, [latitude, longitude, year, month]);
if (loading) return <div>Loading moon calendar...</div>;
return (
<div className="moon-calendar">
{phases.map((day, i) => (
<div key={i} className="calendar-day">
<span className="day-number">{i + 1}</span>
<span className="moon-emoji">
{getMoonEmoji(day.phase)}
</span>
<span className="illumination">
{Math.round(day.illumination)}%
</span>
</div>
))}
</div>
);
}
Finding Key Lunar Events#
Next Full Moon#
async function findNextFullMoon(latitude, longitude, startDate = new Date()) {
let currentDate = new Date(startDate);
// The lunar cycle is ~29.5 days, so check the next 30 days
for (let i = 0; i < 30; i++) {
const dateStr = currentDate.toISOString().split("T")[0];
const moon = await getMoonPhase(latitude, longitude, dateStr);
if (moon.phase === "full") {
return moon;
}
currentDate.setDate(currentDate.getDate() + 1);
}
return null;
}
All Key Phases in a Month#
async function getKeyPhasesInMonth(latitude, longitude, year, month) {
const daysInMonth = new Date(year, month + 1, 0).getDate();
const keyPhases = [];
let previousPhase = null;
for (let day = 1; day <= daysInMonth; day++) {
const date = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
const moon = await getMoonPhase(latitude, longitude, date);
// Detect phase transitions
if (moon.phase !== previousPhase) {
if (
["new", "first_quarter", "full", "last_quarter"].includes(
moon.phase,
)
) {
keyPhases.push({
date: moon.date,
phase: moon.phase,
phase_name: moon.phase_name,
});
}
}
previousPhase = moon.phase;
}
return keyPhases;
}
// Usage
const phases = await getKeyPhasesInMonth(40.71, -74.01, 2026, 0);
// [
// { date: '2026-01-03', phase: 'first_quarter', phase_name: 'First Quarter' },
// { date: '2026-01-10', phase: 'full', phase_name: 'Full Moon' },
// { date: '2026-01-17', phase: 'last_quarter', phase_name: 'Last Quarter' },
// { date: '2026-01-25', phase: 'new', phase_name: 'New Moon' }
// ]
Handling Missing Moonrise/Moonset#
The moon doesn't always rise and set every day — sometimes it rises just before midnight and sets after the next midnight:
function describeMoonVisibility(moonData) {
const { moonrise, moonset } = moonData;
if (!moonrise && !moonset) {
return {
status: "always_visible",
description: "The moon is above the horizon all day",
};
}
if (!moonrise) {
return {
status: "no_rise",
description: `Moon sets at ${formatTime(moonset)}, does not rise today`,
note: "Moonrise occurs after midnight",
};
}
if (!moonset) {
return {
status: "no_set",
description: `Moon rises at ${formatTime(moonrise)}, does not set today`,
note: "Moonset occurs after midnight",
};
}
return {
status: "normal",
description: `Rises ${formatTime(moonrise)}, sets ${formatTime(moonset)}`,
};
}
Use Cases#
Fishing/Hunting Apps#
Solunar theory suggests fish and game are more active during certain lunar periods:
function getSolunarRating(moonData) {
const { phase, illumination } = moonData;
// Major periods: around moonrise and moonset
// Minor periods: moon overhead and underfoot
// Full and new moons are considered best
if (phase === "full" || phase === "new") {
return { rating: 5, description: "Excellent — major solunar period" };
}
// Quarter moons are moderate
if (phase === "first_quarter" || phase === "last_quarter") {
return { rating: 3, description: "Average activity expected" };
}
// In-between phases
if (illumination > 75 || illumination < 25) {
return { rating: 4, description: "Good activity expected" };
}
return { rating: 2, description: "Below average activity" };
}
Gardening Apps#
Some gardeners plant according to moon phases:
function getGardeningAdvice(moonData) {
const { phase } = moonData;
const advice = {
new: {
activity: "Rest period",
crops: "Not ideal for planting",
tasks: "Good for weeding, pest control, and soil preparation",
},
waxing_crescent: {
activity: "Increasing moonlight",
crops: "Plant leafy greens, cabbage, spinach",
tasks: "Good for planting above-ground crops",
},
first_quarter: {
activity: "Strong growth period",
crops: "Plant tomatoes, peppers, squash",
tasks: "Continue planting above-ground crops",
},
waxing_gibbous: {
activity: "Approaching full moon",
crops: "Plant fruit-bearing plants",
tasks: "Good for transplanting",
},
full: {
activity: "Peak energy",
crops: "Plant root vegetables",
tasks: "Harvest, prune, fertilize",
},
waning_gibbous: {
activity: "Decreasing moonlight",
crops: "Plant root crops, bulbs",
tasks: "Good for planting below-ground crops",
},
last_quarter: {
activity: "Rest approaching",
crops: "Continue root vegetables",
tasks: "Harvest, cultivate, weed",
},
waning_crescent: {
activity: "Dormant period approaching",
crops: "Avoid planting",
tasks: "Clear beds, prepare for next cycle",
},
};
return advice[phase] || advice.new;
}
Astronomy Apps#
Combine with sun data for complete night sky planning:
async function getNightSkyConditions(latitude, longitude, date) {
const [sun, moon] = await Promise.all([
getSunData(latitude, longitude, date),
getMoonPhase(latitude, longitude, date),
]);
const conditions = {
astronomicalDarkness: {
start: sun.twilight.astronomical.dusk,
end: sun.twilight.astronomical.dawn,
},
moonInterference: getMoonInterference(moon),
bestViewing: null,
};
// Calculate best deep-sky viewing window
if (moon.illumination < 25) {
conditions.bestViewing = {
start: sun.twilight.astronomical.dusk,
end: sun.twilight.astronomical.dawn,
quality: "Excellent — minimal moon interference",
};
} else if (
moon.moonset &&
new Date(moon.moonset) < new Date(sun.twilight.astronomical.dusk)
) {
conditions.bestViewing = {
start: sun.twilight.astronomical.dusk,
end: sun.twilight.astronomical.dawn,
quality: "Good — moon sets before darkness",
};
} else {
conditions.bestViewing = {
start: moon.moonset || sun.twilight.astronomical.dusk,
end: moon.moonrise || sun.twilight.astronomical.dawn,
quality: "Limited — plan around moon visibility",
};
}
return conditions;
}
function getMoonInterference(moonData) {
const { illumination, phase } = moonData;
if (illumination > 80) {
return {
level: "high",
description: "Bright moon — deep sky objects hard to see",
};
}
if (illumination > 50) {
return {
level: "moderate",
description: "Some interference — focus on bright objects",
};
}
if (illumination > 25) {
return {
level: "low",
description: "Minor interference — good viewing conditions",
};
}
return {
level: "minimal",
description: "Dark skies — excellent for deep sky",
};
}
Caching#
Moon data for a specific location and date is deterministic:
const cache = new Map();
async function getCachedMoonPhase(latitude, longitude, date) {
const key = `${latitude.toFixed(2)},${longitude.toFixed(2)}:${date}`;
if (!cache.has(key)) {
const data = await getMoonPhase(latitude, longitude, date);
cache.set(key, data);
}
return cache.get(key);
}
Common Pitfalls#
Assuming moonrise and moonset always occur. The moon's orbit means it sometimes doesn't rise or set on a given calendar day. Always check for null values before displaying times.
Ignoring hemisphere differences. The visual appearance of moon phases is inverted in the Southern Hemisphere. A waxing crescent appears on the right in the Northern Hemisphere but on the left in the Southern Hemisphere. Your SVG or CSS rendering should account for user location.
Not caching deterministic data. Moon phase data for a specific location and date doesn't change. Cache aggressively to reduce API calls, especially when building calendar views that fetch many dates at once.
Confusing illumination with phase. A 50% illumination can be either First Quarter (waxing) or Last Quarter (waning). Always use the phase field to determine which half of the lunar cycle you're in, not just the illumination percentage.
Hardcoding the lunar cycle length. The synodic month averages 29.53 days but varies between 29.27 and 29.83 days. Don't assume exactly 29.5 days when calculating future phases.
Summary#
Building a moon phase display involves fetching data from the API, understanding the eight principal phases, and choosing the right visual representation for your use case. Key implementation points:
Use the
phasefield for categorical display (emoji, icons) andilluminationfor precise visual renderingHandle missing moonrise/moonset times gracefully
Cache responses since moon data for a given location and date is deterministic
Consider your users' hemisphere when rendering visual representations
Ready to add moon phases to your application? Get your API key and start building.
Related guides:
Adding Sunrise and Sunset Times to Your Application — Complete astronomy data
Building a Golden Hour Calculator for Photography Apps — Photography features
How to Build a Timezone-Aware Application — Display times correctly