PHP applications can retrieve historical weather, current conditions, and forecast data directly from the Visual Crossing Timeline Weather API.
In this tutorial, we’ll use PHP to build a Weather API request, retrieve the returned JSON data, parse it using json_decode(), and display daily weather values in an HTML table.
The Visual Crossing Weather API provides programmatic access to historical, current, and forecast weather data. If you want to explore or download weather datasets without writing code, see Visual Crossing Weather Data.
To follow the examples, sign up for a free Visual Crossing account and obtain your Weather API key.
For the complete API reference, see the Timeline Weather API documentation.
What we’ll build
We’ll create a simple PHP page that:
- Reads a location from the URL.
- Builds a Timeline Weather API request.
- Retrieves weather data using PHP.
- Parses the returned JSON.
- Displays daily weather values in a table.
- Supports either forecast or historical weather.
The same Timeline Weather API structure is used for both historical and forecast data, so we can reuse most of the same PHP code.
Get your Weather API key
Your Visual Crossing Weather API key authenticates requests made by your application.
Rather than placing the API key directly in the PHP source code, we’ll read it from an environment variable:
$apiKey = getenv('VISUAL_CROSSING_API_KEY');
if (!$apiKey) {
throw new RuntimeException(
'VISUAL_CROSSING_API_KEY is not set.'
);
}
How you configure environment variables depends on your web server and hosting environment.
For local command-line testing on Linux or macOS, for example:
export VISUAL_CROSSING_API_KEY="YOUR_API_KEY"
Visual Crossing plans include usage and request limits, so applications should be designed to operate within the limits of the account plan being used.
Understanding the Timeline Weather API URL
The Timeline Weather API uses the following basic structure:
/timeline/[location]/[date1]/[date2]
Only the location is required.
A forecast request for London can use:
/timeline/London%2C%20UK
A historical request for July 1, 2026 uses:
/timeline/London%2C%20UK/2026-07-01
A historical date range uses:
/timeline/London%2C%20UK/2026-07-01/2026-07-07
When dates are omitted, the API returns the available forecast data. When historical dates are supplied, the API returns weather data for the requested dates.
Complete PHP forecast example
The following example retrieves daily forecast data for a location and displays it in an HTML table.
Create a file named:
weather.php
and add:
<?php
declare(strict_types=1);
$apiKey = getenv('VISUAL_CROSSING_API_KEY');
if (!$apiKey) {
throw new RuntimeException(
'VISUAL_CROSSING_API_KEY is not set.'
);
}
$location = $_GET['location'] ?? 'London, UK';
$unitGroup = $_GET['unitGroup'] ?? 'metric';
$encodedLocation = rawurlencode($location);
$query = http_build_query([
'unitGroup' => $unitGroup,
'include' => 'days',
'elements' => implode(',', [
'datetime',
'tempmax',
'tempmin',
'precip',
'precipprob',
'windspeed',
'windgust',
'cloudcover',
'conditions'
]),
'key' => $apiKey,
'contentType' => 'json'
]);
$apiUrl =
'https://weather.visualcrossing.com/' .
'VisualCrossingWebServices/rest/services/timeline/' .
$encodedLocation .
'?' .
$query;
$jsonData = @file_get_contents($apiUrl);
if ($jsonData === false) {
$error = error_get_last();
throw new RuntimeException(
'Unable to retrieve weather data: ' .
($error['message'] ?? 'Unknown HTTP error')
);
}
$responseData = json_decode(
$jsonData,
false,
512,
JSON_THROW_ON_ERROR
);
$resolvedAddress =
$responseData->resolvedAddress ?? $location;
$days =
$responseData->days ?? [];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
<title>
Weather for
<?= htmlspecialchars(
$resolvedAddress,
ENT_QUOTES,
'UTF-8'
) ?>
</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 30px;
}
table {
border-collapse: collapse;
width: 100%;
max-width: 1100px;
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background: #f5f5f5;
}
</style>
</head>
<body>
<h1>
Weather for
<?= htmlspecialchars(
$resolvedAddress,
ENT_QUOTES,
'UTF-8'
) ?>
</h1>
<table>
<thead>
<tr>
<th>Date</th>
<th>Conditions</th>
<th>Max Temp</th>
<th>Min Temp</th>
<th>Precip</th>
<th>Precip Probability</th>
<th>Wind Speed</th>
<th>Wind Gust</th>
<th>Cloud Cover</th>
</tr>
</thead>
<tbody>
<?php foreach ($days as $day): ?>
<tr>
<td>
<?= htmlspecialchars(
(string)($day->datetime ?? ''),
ENT_QUOTES,
'UTF-8'
) ?>
</td>
<td>
<?= htmlspecialchars(
(string)($day->conditions ?? ''),
ENT_QUOTES,
'UTF-8'
) ?>
</td>
<td>
<?= htmlspecialchars(
(string)($day->tempmax ?? ''),
ENT_QUOTES,
'UTF-8'
) ?>
</td>
<td>
<?= htmlspecialchars(
(string)($day->tempmin ?? ''),
ENT_QUOTES,
'UTF-8'
) ?>
</td>
<td>
<?= htmlspecialchars(
(string)($day->precip ?? ''),
ENT_QUOTES,
'UTF-8'
) ?>
</td>
<td>
<?= htmlspecialchars(
(string)($day->precipprob ?? ''),
ENT_QUOTES,
'UTF-8'
) ?>
</td>
<td>
<?= htmlspecialchars(
(string)($day->windspeed ?? ''),
ENT_QUOTES,
'UTF-8'
) ?>
</td>
<td>
<?= htmlspecialchars(
(string)($day->windgust ?? ''),
ENT_QUOTES,
'UTF-8'
) ?>
</td>
<td>
<?= htmlspecialchars(
(string)($day->cloudcover ?? ''),
ENT_QUOTES,
'UTF-8'
) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
You can then request a location using:
weather.php?location=London%2C%20UK
or:
weather.php?location=New%20York%2C%20NY&unitGroup=us
Building the Weather API request
The example uses:
$encodedLocation = rawurlencode($location);
to safely encode the location before placing it in the Timeline URL.
Query parameters are created using:
$query = http_build_query([
'unitGroup' => $unitGroup,
'include' => 'days',
'elements' => 'datetime,tempmax,tempmin,conditions',
'key' => $apiKey,
'contentType' => 'json'
]);
Using http_build_query() is preferable to manually concatenating query-string values because PHP handles the required encoding.
Retrieving the Weather API response
PHP’s file_get_contents() can retrieve HTTP content when URL wrappers are enabled:
$jsonData = file_get_contents($apiUrl);
The full example checks whether the request was successful:
$jsonData = @file_get_contents($apiUrl);
if ($jsonData === false) {
$error = error_get_last();
throw new RuntimeException(
'Unable to retrieve weather data: ' .
($error['message'] ?? 'Unknown HTTP error')
);
}
For applications that already use cURL, Guzzle, or another HTTP client, those can also be used to make the same Timeline Weather API request.
The important part is the API URL and returned JSON structure; the PHP HTTP client can be changed without altering how the weather data itself is processed.
Parsing the JSON response
The Weather API returns JSON data.
PHP can convert that JSON into objects using:
$responseData = json_decode(
$jsonData,
false,
512,
JSON_THROW_ON_ERROR
);
Using:
JSON_THROW_ON_ERROR
causes malformed or otherwise invalid JSON to generate an exception instead of silently returning an unusable result.
The resolved location is available as:
$responseData->resolvedAddress
and daily weather records are available in:
$responseData->days
For example:
$firstDay = $responseData->days[0];
echo $firstDay->datetime;
echo $firstDay->tempmax;
echo $firstDay->tempmin;
echo $firstDay->conditions;
Retrieve historical weather with PHP
The same Timeline Weather API can retrieve historical weather by adding dates to the URL path.
For example, the following complete script accepts optional fromdate and todate URL parameters.
If dates are omitted, it retrieves the available forecast.
If dates are supplied, it retrieves historical weather for that period.
<?php
declare(strict_types=1);
$apiKey = getenv('VISUAL_CROSSING_API_KEY');
if (!$apiKey) {
throw new RuntimeException(
'VISUAL_CROSSING_API_KEY is not set.'
);
}
$location =
$_GET['location'] ?? 'London, UK';
$unitGroup =
$_GET['unitGroup'] ?? 'metric';
$startDate =
$_GET['fromdate'] ?? null;
$endDate =
$_GET['todate'] ?? null;
$encodedLocation =
rawurlencode($location);
$apiPath =
$encodedLocation;
if ($startDate) {
$apiPath .=
'/' . rawurlencode($startDate);
}
if ($startDate && $endDate) {
$apiPath .=
'/' . rawurlencode($endDate);
}
$query = http_build_query([
'unitGroup' => $unitGroup,
'include' => 'days',
'elements' => implode(',', [
'datetime',
'tempmax',
'tempmin',
'precip',
'precipprob',
'humidity',
'windspeed',
'conditions'
]),
'key' => $apiKey,
'contentType' => 'json'
]);
$apiUrl =
'https://weather.visualcrossing.com/' .
'VisualCrossingWebServices/rest/services/timeline/' .
$apiPath .
'?' .
$query;
$jsonData = @file_get_contents($apiUrl);
if ($jsonData === false) {
$error = error_get_last();
throw new RuntimeException(
'Unable to retrieve weather data: ' .
($error['message'] ?? 'Unknown HTTP error')
);
}
$weather = json_decode(
$jsonData,
false,
512,
JSON_THROW_ON_ERROR
);
echo '<h1>Weather for ' .
htmlspecialchars(
(string)$weather->resolvedAddress,
ENT_QUOTES,
'UTF-8'
) .
'</h1>';
echo '<table border="1" cellpadding="8">';
echo '
<thead>
<tr>
<th>Date</th>
<th>Conditions</th>
<th>Maximum Temperature</th>
<th>Minimum Temperature</th>
<th>Precipitation</th>
<th>Precipitation Probability</th>
<th>Humidity</th>
<th>Wind Speed</th>
</tr>
</thead>
';
echo '<tbody>';
foreach ($weather->days as $day) {
echo '<tr>';
echo '<td>' .
htmlspecialchars(
(string)($day->datetime ?? ''),
ENT_QUOTES,
'UTF-8'
) .
'</td>';
echo '<td>' .
htmlspecialchars(
(string)($day->conditions ?? ''),
ENT_QUOTES,
'UTF-8'
) .
'</td>';
echo '<td>' .
htmlspecialchars(
(string)($day->tempmax ?? ''),
ENT_QUOTES,
'UTF-8'
) .
'</td>';
echo '<td>' .
htmlspecialchars(
(string)($day->tempmin ?? ''),
ENT_QUOTES,
'UTF-8'
) .
'</td>';
echo '<td>' .
htmlspecialchars(
(string)($day->precip ?? ''),
ENT_QUOTES,
'UTF-8'
) .
'</td>';
echo '<td>' .
htmlspecialchars(
(string)($day->precipprob ?? ''),
ENT_QUOTES,
'UTF-8'
) .
'</td>';
echo '<td>' .
htmlspecialchars(
(string)($day->humidity ?? ''),
ENT_QUOTES,
'UTF-8'
) .
'</td>';
echo '<td>' .
htmlspecialchars(
(string)($day->windspeed ?? ''),
ENT_QUOTES,
'UTF-8'
) .
'</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
To retrieve historical weather for July 1 through July 7, 2026, you could request:
weather.php?location=London%2C%20UK&fromdate=2026-07-01&todate=2026-07-07
The returned JSON uses the same days structure as forecast weather, so the display and processing code does not need to change.
Retrieve a single historical date
You can also supply only a start date:
weather.php?location=London%2C%20UK&fromdate=2026-07-01
The Timeline URL becomes:
/timeline/London%2C%20UK/2026-07-01
and the API returns weather for that date.
Retrieve recent weather using dynamic date periods
The Timeline Weather API also supports dynamic date specifications for many common use cases.
For example:
last7days
can be included in the Timeline path:
$apiPath =
rawurlencode($location) .
'/last7days';
This can be useful when an application always needs a recent period rather than fixed calendar dates.
See the Timeline Weather API documentation for available date options.
Retrieve current conditions
To retrieve current weather conditions, change:
'include' => 'days'
to:
'include' => 'current,days'
Current conditions are then available in:
$responseData->currentConditions
For example:
$current =
$responseData->currentConditions;
echo $current->temp;
echo $current->humidity;
echo $current->conditions;
Retrieve hourly weather
To retrieve hourly weather, request:
'include' => 'hours'
or:
'include' => 'days,hours'
Hourly weather records are contained inside each daily record:
$hours =
$responseData->days[0]->hours;
You can then loop through them:
foreach ($hours as $hour) {
echo $hour->datetime;
echo ' ';
echo $hour->temp;
echo ' ';
echo $hour->conditions;
echo '<br>';
}
Request only the weather elements you need
The Timeline Weather API provides many weather fields.
Use the elements parameter to request only those required by your PHP application.
For example:
'elements' =>
'datetime,tempmax,tempmin,conditions'
is sufficient for a basic daily forecast.
You can add fields such as:
temp
feelslike
humidity
dew
precip
precipprob
snow
snowdepth
windspeed
windgust
winddir
pressure
cloudcover
visibility
solarradiation
solarenergy
uvindex
conditions
icon
depending on your application’s requirements.
For the full list, see the Timeline Weather API documentation.
Escaping weather data for HTML output
When values are inserted into HTML, they should be escaped before being displayed.
For example:
echo htmlspecialchars(
(string)$day->conditions,
ENT_QUOTES,
'UTF-8'
);
The examples in this tutorial use htmlspecialchars() for text values displayed in the resulting page.
This is a useful general practice whenever PHP applications generate HTML from data returned by an API or supplied by users.
Handling HTTP errors
A Weather API request may fail because of:
- An invalid API key
- An invalid location
- Invalid dates
- Invalid request parameters
- Account usage limits
- Network connectivity problems
The simple examples above use file_get_contents() and detect a failed request:
$jsonData = @file_get_contents($apiUrl);
if ($jsonData === false) {
$error = error_get_last();
throw new RuntimeException(
'Unable to retrieve weather data: ' .
($error['message'] ?? 'Unknown HTTP error')
);
}
For applications that need greater control over HTTP status codes, headers, timeouts, and error responses, PHP cURL or an HTTP client library can be used instead.
The same Timeline URL and JSON-processing logic still apply.
Using US or metric units
The examples support the Timeline unitGroup parameter.
For metric weather data:
$unitGroup = 'metric';
For US weather data:
$unitGroup = 'us';
The unit group affects temperatures, precipitation, wind speeds, visibility, and other weather measurements.
Going further
Once your PHP application can retrieve Timeline Weather API data, the same techniques can support:
- Historical weather analysis
- Current weather displays
- Hourly forecasts
- Daily forecasts
- Weather dashboards
- Business applications
- Agricultural applications
- Travel and event applications
- Energy applications
- Other weather-enabled websites and services
The Visual Crossing Weather API provides programmatic access to historical weather, current conditions, forecasts, alerts, and additional weather information.
If you want to explore or download weather datasets without writing API integration code, use Visual Crossing Weather Data.
If you haven’t created an account yet, you can sign up for a free Visual Crossing account.
For detailed API parameters and response structures, see the Timeline Weather API documentation.
Summary
PHP can retrieve Visual Crossing Weather API data using functionality available in the standard language.
The basic process is:
- Obtain a Visual Crossing Weather API key.
- Build a Timeline Weather API URL for the required location and dates.
- Use
http_build_query()to safely create the query parameters. - Retrieve the Weather API response.
- Parse the returned JSON using
json_decode(). - Read daily, hourly, or current weather values.
- Escape text values when displaying them in HTML.
- Handle request and JSON errors appropriately.
Because the Timeline Weather API uses a consistent structure for historical, current, and forecast weather, the same PHP integration can support a wide range of weather-enabled applications.

