Adding a weather forecast to a website can be as simple as retrieving forecast data from a weather API and displaying the results with HTML, CSS, and JavaScript.
In this tutorial, we’ll use the Visual Crossing Timeline Weather API to build a simple multi-day weather forecast for a web page. The example uses standard browser JavaScript with the Fetch API, so no JavaScript framework or additional library such as jQuery is required.
We’ll also discuss how to structure the solution for a production website so that your Weather API key remains secure.
What we’ll build
Our example will display a five-day forecast including:
- Date
- Weather conditions
- Maximum temperature
- Minimum temperature
- Chance of precipitation
The same approach can be extended to include hourly weather, current conditions, wind, humidity, snow, solar radiation, severe weather alerts, and other weather data available from the Timeline Weather API.
1. Get your Visual Crossing Weather API key
To use the Visual Crossing Weather API, first create a free Visual Crossing account and obtain your Weather API key from your account page.
Your API key authenticates Weather API requests and associates those requests with your Visual Crossing account and plan.
In the examples below, replace:
YOUR_API_KEY
with your own API key when testing the code.
Important: An API key included directly in browser-side JavaScript can be viewed by visitors to your website. This is useful for demonstrating how the API works, but a production public website should normally make Weather API requests through server-side code so that the API key is not exposed to the browser. We’ll discuss this in more detail later in the article.
2. Create a weather forecast API request
The Visual Crossing Timeline Weather API provides forecast, current, and historical weather data through the same API.
A basic forecast request has the following form:
https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/[location]?key=YOUR_API_KEY
For example, the following request retrieves the forecast for London using metric units:
https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/London%2CUK?unitGroup=metric&include=days&key=YOUR_API_KEY&contentType=json
When no date is supplied, the Timeline Weather API returns the available weather forecast.
For our web page, we only need daily forecast data, so we use:
include=days
We can reduce the response further by requesting only the weather elements that our page needs:
elements=datetime,tempmax,tempmin,precipprob,conditions,icon
Our final request therefore looks like:
https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/London%2CUK?unitGroup=metric&include=days&elements=datetime,tempmax,tempmin,precipprob,conditions,icon&key=YOUR_API_KEY&contentType=json
Requesting only the data that your application uses reduces the response size and makes the returned JSON easier to process.
You can also use the Visual Crossing Weather Query Builder to create requests interactively, inspect the returned data, and experiment with different locations, units, and weather elements.
3. Understanding the forecast response
The Timeline Weather API returns JSON data.
Daily forecast records are contained in the days array:
data.days
For example, the first forecast day can be accessed using:
data.days[0]
A daily record contains values such as:
day.datetime
day.tempmax
day.tempmin
day.precipprob
day.conditions
day.icon
For example, a simplified daily result might contain:
{
"datetime": "2026-08-21",
"tempmax": 24.8,
"tempmin": 16.2,
"precipprob": 35,
"conditions": "Partially cloudy",
"icon": "partly-cloudy-day"
}
We’ll use these values to create the forecast display.
4. Add a forecast container to your web page
First, create an HTML element where the forecast will appear:
<div id="weather-forecast"></div>
JavaScript will populate this element after retrieving the forecast from the Weather API.
5. Retrieve the forecast using JavaScript
Modern browsers provide the Fetch API for making HTTP requests, so no additional JavaScript library is required.
Here’s a simple request:
const apiKey = "YOUR_API_KEY";
const location = "London, UK";
const url =
"https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/" +
encodeURIComponent(location) +
"?unitGroup=metric" +
"&include=days" +
"&elements=datetime,tempmax,tempmin,precipprob,conditions,icon" +
"&key=" + encodeURIComponent(apiKey) +
"&contentType=json";
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error("Weather API request failed: " + response.status);
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
There are three main steps here.
First, we build the Weather API request. encodeURIComponent() ensures that locations containing spaces, commas, and other characters are correctly encoded for use in a URL.
Next:
fetch(url)
sends the request to the Timeline Weather API.
Finally:
response.json()
converts the returned JSON into a JavaScript object that we can use to build the forecast.
6. Display the forecast
Now let’s turn the returned daily weather data into HTML.
We’ll display the first five days of the forecast:
function displayForecast(data) {
const container = document.getElementById("weather-forecast");
container.innerHTML = "";
data.days.slice(0, 5).forEach(day => {
const forecastDay = document.createElement("div");
forecastDay.className = "forecast-day";
forecastDay.innerHTML = `
<div class="forecast-date">${formatDate(day.datetime)}</div>
<div class="forecast-icon">${weatherIcon(day.icon)}</div>
<div class="forecast-conditions">${day.conditions}</div>
<div class="forecast-temp">
<span class="forecast-high">${Math.round(day.tempmax)}°</span>
<span class="forecast-low">${Math.round(day.tempmin)}°</span>
</div>
<div class="forecast-precip">
Precipitation: ${Math.round(day.precipprob || 0)}%
</div>
`;
container.appendChild(forecastDay);
});
}
The call:
data.days.slice(0, 5)
selects the first five daily forecast records.
You can change 5 to display more or fewer days.
7. Format the forecast date
The datetime field for a daily forecast uses a date such as:
2026-08-21
We can convert this into a more readable value:
function formatDate(dateString) {
const date = new Date(dateString + "T00:00:00Z");
return new Intl.DateTimeFormat("en-US", {
weekday: "short",
month: "short",
day: "numeric",
timeZone: "UTC"
}).format(date);
}
For example:
2026-08-21
becomes:
Fri, Aug 21
Using the date as a calendar date rather than applying the user’s local time-zone offset helps avoid accidentally displaying the previous or following date.
8. Display weather icons
The Timeline Weather API provides an icon field that summarizes the expected weather conditions.
Typical values include:
clear-day
partly-cloudy-day
cloudy
rain
snow
fog
wind
For this simple example, we can map those values to Unicode weather symbols:
function weatherIcon(icon) {
const icons = {
"clear-day": "☀️",
"clear-night": "🌙",
"partly-cloudy-day": "🌤️",
"partly-cloudy-night": "☁️",
"cloudy": "☁️",
"rain": "🌧️",
"snow": "🌨️",
"fog": "🌫️",
"wind": "💨"
};
return icons[icon] || "🌤️";
}
For a production application, you can replace these symbols with your own SVG, PNG, icon font, or other weather icon set.
9. Put the JavaScript together
Our complete JavaScript now looks like this:
const apiKey = "YOUR_API_KEY";
const location = "London, UK";
const unitGroup = "metric";
const url =
"https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/" +
encodeURIComponent(location) +
"?unitGroup=" + encodeURIComponent(unitGroup) +
"&include=days" +
"&elements=datetime,tempmax,tempmin,precipprob,conditions,icon" +
"&key=" + encodeURIComponent(apiKey) +
"&contentType=json";
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error("Weather API request failed: " + response.status);
}
return response.json();
})
.then(data => {
displayForecast(data);
})
.catch(error => {
console.error(error);
document.getElementById("weather-forecast").textContent =
"Weather forecast is currently unavailable.";
});
function displayForecast(data) {
const container = document.getElementById("weather-forecast");
container.innerHTML = "";
data.days.slice(0, 5).forEach(day => {
const forecastDay = document.createElement("div");
forecastDay.className = "forecast-day";
forecastDay.innerHTML = `
<div class="forecast-date">${formatDate(day.datetime)}</div>
<div class="forecast-icon">${weatherIcon(day.icon)}</div>
<div class="forecast-conditions">${day.conditions}</div>
<div class="forecast-temp">
<span class="forecast-high">${Math.round(day.tempmax)}°</span>
<span class="forecast-low">${Math.round(day.tempmin)}°</span>
</div>
<div class="forecast-precip">
Precipitation: ${Math.round(day.precipprob || 0)}%
</div>
`;
container.appendChild(forecastDay);
});
}
function formatDate(dateString) {
const date = new Date(dateString + "T00:00:00Z");
return new Intl.DateTimeFormat("en-US", {
weekday: "short",
month: "short",
day: "numeric",
timeZone: "UTC"
}).format(date);
}
function weatherIcon(icon) {
const icons = {
"clear-day": "☀️",
"clear-night": "🌙",
"partly-cloudy-day": "🌤️",
"partly-cloudy-night": "☁️",
"cloudy": "☁️",
"rain": "🌧️",
"snow": "🌨️",
"fog": "🌫️",
"wind": "💨"
};
return icons[icon] || "🌤️";
}
10. Add some CSS
The forecast now contains all the information we need, but we still need to format it.
Here’s a simple responsive layout:
#weather-forecast {
display: grid;
grid-template-columns: repeat(5, minmax(120px, 1fr));
gap: 12px;
max-width: 900px;
font-family: Arial, sans-serif;
}
.forecast-day {
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
text-align: center;
}
.forecast-date {
font-weight: 600;
margin-bottom: 10px;
}
.forecast-icon {
font-size: 36px;
margin-bottom: 8px;
}
.forecast-conditions {
min-height: 40px;
margin-bottom: 10px;
}
.forecast-temp {
font-size: 20px;
margin-bottom: 8px;
}
.forecast-high {
font-weight: 600;
}
.forecast-low {
margin-left: 8px;
}
.forecast-precip {
font-size: 13px;
}
@media (max-width: 700px) {
#weather-forecast {
grid-template-columns: 1fr;
}
.forecast-day {
display: grid;
grid-template-columns: 1fr auto auto;
gap: 10px;
align-items: center;
text-align: left;
}
}
You can replace these styles with your own website’s design system.
11. Complete example
Putting everything together gives us a complete web page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Weather Forecast</title>
<style>
#weather-forecast {
display: grid;
grid-template-columns: repeat(5, minmax(120px, 1fr));
gap: 12px;
max-width: 900px;
font-family: Arial, sans-serif;
}
.forecast-day {
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
text-align: center;
}
.forecast-date {
font-weight: 600;
margin-bottom: 10px;
}
.forecast-icon {
font-size: 36px;
margin-bottom: 8px;
}
.forecast-conditions {
min-height: 40px;
margin-bottom: 10px;
}
.forecast-temp {
font-size: 20px;
margin-bottom: 8px;
}
.forecast-high {
font-weight: 600;
}
.forecast-low {
margin-left: 8px;
}
.forecast-precip {
font-size: 13px;
}
@media (max-width: 700px) {
#weather-forecast {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<h1>London Weather Forecast</h1>
<div id="weather-forecast">Loading forecast...</div>
<script>
const apiKey = "YOUR_API_KEY";
const location = "London, UK";
const unitGroup = "metric";
const url =
"https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/" +
encodeURIComponent(location) +
"?unitGroup=" + encodeURIComponent(unitGroup) +
"&include=days" +
"&elements=datetime,tempmax,tempmin,precipprob,conditions,icon" +
"&key=" + encodeURIComponent(apiKey) +
"&contentType=json";
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error("Weather API request failed: " + response.status);
}
return response.json();
})
.then(data => {
displayForecast(data);
})
.catch(error => {
console.error(error);
document.getElementById("weather-forecast").textContent =
"Weather forecast is currently unavailable.";
});
function displayForecast(data) {
const container = document.getElementById("weather-forecast");
container.innerHTML = "";
data.days.slice(0, 5).forEach(day => {
const forecastDay = document.createElement("div");
forecastDay.className = "forecast-day";
forecastDay.innerHTML = `
<div class="forecast-date">${formatDate(day.datetime)}</div>
<div class="forecast-icon">${weatherIcon(day.icon)}</div>
<div class="forecast-conditions">${day.conditions}</div>
<div class="forecast-temp">
<span class="forecast-high">${Math.round(day.tempmax)}°</span>
<span class="forecast-low">${Math.round(day.tempmin)}°</span>
</div>
<div class="forecast-precip">
Precipitation: ${Math.round(day.precipprob || 0)}%
</div>
`;
container.appendChild(forecastDay);
});
}
function formatDate(dateString) {
const date = new Date(dateString + "T00:00:00Z");
return new Intl.DateTimeFormat("en-US", {
weekday: "short",
month: "short",
day: "numeric",
timeZone: "UTC"
}).format(date);
}
function weatherIcon(icon) {
const icons = {
"clear-day": "☀️",
"clear-night": "🌙",
"partly-cloudy-day": "🌤️",
"partly-cloudy-night": "☁️",
"cloudy": "☁️",
"rain": "🌧️",
"snow": "🌨️",
"fog": "🌫️",
"wind": "💨"
};
return icons[icon] || "🌤️";
}
</script>
</body>
</html>
Save the page, open it in your browser, and the forecast will be loaded from the Timeline Weather API.
Using a different location
To change the forecast location, simply change:
const location = "London, UK";
For example:
const location = "New York, NY";
or:
const location = "38.9697,-77.385";
The Timeline Weather API accepts addresses, partial addresses, cities, postal codes, and latitude/longitude coordinates.
If your website already knows the user’s location, you can use that location when constructing the Weather API request.
Changing the units
The examples above use metric units:
const unitGroup = "metric";
For US units, use:
const unitGroup = "us";
You can therefore change the weather display based on your application’s location or user preferences.
Adding current weather
The Timeline Weather API can return current conditions as well as forecast data.
For example, request both current conditions and daily forecast information using:
include=current,days
The current conditions are then available under:
data.currentConditions
For example:
data.currentConditions.temp
data.currentConditions.humidity
data.currentConditions.conditions
data.currentConditions.icon
This makes it easy to add a “Current Weather” section above the multi-day forecast.
Adding hourly weather
Hourly forecast data is available by including:
include=hours
Each forecast day then contains an hours array:
data.days[0].hours
For example:
data.days[0].hours.forEach(hour => {
console.log(
hour.datetime,
hour.temp,
hour.precipprob,
hour.conditions
);
});
The same basic technique can therefore be used to create hourly forecast charts, tables, or interactive weather displays.
Protecting your API key on a public website
The browser-side example in this tutorial is useful for learning how a web page communicates with the Weather API. However, JavaScript downloaded by a visitor is visible to that visitor.
That means an API key stored like this:
const apiKey = "YOUR_API_KEY";
cannot be considered private.
For a production public website, the recommended architecture is:
Visitor's browser
↓
Your web server
↓
Visual Crossing Weather API
Your web server stores the Visual Crossing API key and makes the Weather API request. The browser calls your server rather than calling the Weather API with the API key directly.
For example, your website might request:
/api/weather?location=London
Your server then adds the private Visual Crossing API key, calls the Timeline Weather API, and returns the required weather data to the page.
This approach also gives you an opportunity to cache forecast results, validate requested locations, control how frequently users can request data, and reduce unnecessary API usage.
Be aware of your Weather API plan limits
Visual Crossing plans include usage and request limits that should be followed when building a website or application.
Consider how many locations your website requests, how frequently forecasts are refreshed, and how many visitors will use the application. Avoid repeatedly retrieving identical weather data when a previously retrieved result can be reused.
For public or high-traffic websites, server-side caching can significantly reduce duplicate requests and help keep Weather API usage predictable.
Using the Timeline Weather API for more than forecasts
The example in this article uses the Timeline Weather API to retrieve a weather forecast, but the same API can also provide:
- Historical weather observations
- Current conditions
- Hourly forecasts
- Daily forecasts
- Long-range statistical forecasts
- Weather alerts
- Air quality data
- Solar and radiation data
- Agricultural weather variables
- Additional weather and environmental information
Because historical, current, and forecast data use the same Timeline API structure, you can expand the example without adopting a different API.
Summary
Adding a weather forecast to a web page requires only a few basic steps:
- Create a Visual Crossing Weather account and obtain an API key.
- Create a Timeline Weather API request for your location.
- Retrieve the forecast using JavaScript or your server-side application.
- Read the forecast from the returned
daysarray. - Build the forecast display using HTML and CSS.
- For production public websites, keep your API key on the server and consider caching forecast results.
The example in this tutorial provides a simple starting point that can be expanded into anything from a small five-day forecast to a complete weather dashboard.
Use the Visual Crossing Weather Query Builder to explore available weather data and build requests, or see the Timeline Weather API documentation for the complete API reference.

