WordPress shortcodes provide an easy way to add dynamic content to posts and pages. In this tutorial, we’ll build a simple WordPress plugin that retrieves a weather forecast from the Visual Crossing Timeline Weather API and displays it using a shortcode.
Once installed, a content author will be able to add a forecast using:
[weather location="London, UK"]
or customize the location, number of days, and units:
[weather location="New York, NY" days="5" unit="us"]
The plugin will make the Weather API request on the WordPress server and generate the resulting HTML.
The Visual Crossing Weather API provides historical weather, current conditions, and forecasts through the Timeline Weather API.
To follow this tutorial, sign up for a free Visual Crossing account and obtain your API key.
For the complete API reference, see the Timeline Weather API documentation.
What we’ll build
Our plugin will:
- Register a
[weather]shortcode. - Accept shortcode attributes for location, forecast days, and units.
- Retrieve forecast data using the WordPress HTTP API.
- Parse the Timeline Weather API JSON response.
- Generate a simple weather forecast using HTML.
- Handle API and network errors safely.
We will use WordPress’s built-in functions rather than external PHP or JavaScript libraries.
Create the plugin directory
In your WordPress installation, navigate to:
wp-content/plugins/
Create a directory named:
visual-crossing-weather
Inside that directory create:
visual-crossing-weather.php
Your plugin will therefore have this structure:
wp-content/
└── plugins/
└── visual-crossing-weather/
└── visual-crossing-weather.php
For this simple plugin, a single PHP file is sufficient.
Add the plugin header
Open visual-crossing-weather.php and add:
<?php
/**
* Plugin Name: Visual Crossing Weather
* Description: Display weather forecasts using the Visual Crossing Weather API and a WordPress shortcode.
* Version: 1.0.0
* Author: Your Name
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
The plugin header allows WordPress to recognize the file as a plugin.
The ABSPATH check prevents the PHP file from being executed directly outside WordPress.
Store your Weather API key
For this tutorial, we’ll define the API key in wp-config.php rather than putting it directly in the plugin source.
Open your site’s wp-config.php file and add:
define(
'VISUAL_CROSSING_API_KEY',
'YOUR_API_KEY'
);
Replace:
YOUR_API_KEY
with your Visual Crossing Weather API key.
Keeping the key outside the plugin file means that the plugin code can be placed in source control or reused on another site without including the account credential.
Visual Crossing plans include usage and request limits, so WordPress applications should operate within the limits of the account plan being used.
Register the weather shortcode
WordPress plugins can register shortcodes using add_shortcode().
Add this to the plugin file:
add_shortcode(
'weather',
'visual_crossing_weather_shortcode'
);
This tells WordPress to call:
visual_crossing_weather_shortcode()
whenever it encounters:
[weather]
in page or post content.
Define the shortcode attributes
We’ll support three shortcode attributes:
location— the forecast locationdays— the number of forecast days to displayunit—metricorus
For example:
[weather location="Paris, France" days="5" unit="metric"]
Inside our shortcode function, WordPress’s shortcode_atts() function lets us define defaults:
$atts = shortcode_atts(
array(
'location' => 'London, UK',
'days' => 7,
'unit' => 'metric',
),
$atts,
'weather'
);
If the content author supplies a value, it overrides the default.
Build the Timeline Weather API request
The Timeline Weather API uses this basic structure:
/timeline/[location]
When no dates are supplied, the endpoint returns the available forecast.
We’ll request daily forecast values using:
include=days
and limit the response to the fields needed by the plugin:
datetime
tempmax
tempmin
precipprob
conditions
icon
WordPress provides add_query_arg() for safely constructing query parameters.
The request code is:
$location = rawurlencode( $location );
$api_url =
'https://weather.visualcrossing.com/' .
'VisualCrossingWebServices/rest/services/timeline/' .
$location;
$api_url = add_query_arg(
array(
'unitGroup' => $unit,
'include' => 'days',
'elements' =>
'datetime,tempmax,tempmin,' .
'precipprob,conditions,icon',
'key' => VISUAL_CROSSING_API_KEY,
'contentType' => 'json',
),
$api_url
);
Retrieve the forecast using the WordPress HTTP API
WordPress provides its own HTTP API, including wp_remote_get().
Use:
$response = wp_remote_get(
$api_url,
array(
'timeout' => 15,
)
);
The result can either be an HTTP response or a WP_Error.
First check for a network-level error:
if ( is_wp_error( $response ) ) {
return '<p class="vc-weather-error">' .
esc_html(
'Unable to retrieve weather data.'
) .
'</p>';
}
Then check the HTTP status code:
$status_code =
wp_remote_retrieve_response_code(
$response
);
if ( $status_code < 200 ||
$status_code >= 300 ) {
return '<p class="vc-weather-error">' .
esc_html(
'The Weather API returned an error.'
) .
'</p>';
}
Finally retrieve the response body:
$body =
wp_remote_retrieve_body(
$response
);
$weather =
json_decode(
$body,
true
);
Validate the Weather API response
Before attempting to display the weather, make sure the expected days array exists:
if (
! is_array( $weather ) ||
empty( $weather['days'] )
) {
return '<p class="vc-weather-error">' .
esc_html(
'Weather data is unavailable.'
) .
'</p>';
}
The daily forecast records are available in:
$weather['days']
The resolved location is available as:
$weather['resolvedAddress']
Limit the number of forecast days
The API may return more days than we want to display.
We’ll restrict the shortcode’s days value:
$days =
absint(
$atts['days']
);
if ( $days < 1 ) {
$days = 1;
}
if ( $days > 15 ) {
$days = 15;
}
Then limit the returned array:
$forecast_days =
array_slice(
$weather['days'],
0,
$days
);
Generate the forecast HTML
We’ll generate a simple HTML forecast using each daily record.
Start an output buffer:
ob_start();
Then render the location and daily forecast:
?>
<div class="vc-weather">
<h3 class="vc-weather-title">
<?php
echo esc_html(
$weather['resolvedAddress']
?? $atts['location']
);
?>
</h3>
<div class="vc-weather-days">
<?php foreach ( $forecast_days as $day ) : ?>
<div class="vc-weather-day">
<div class="vc-weather-date">
<?php
echo esc_html(
$day['datetime'] ?? ''
);
?>
</div>
<div class="vc-weather-conditions">
<?php
echo esc_html(
$day['conditions'] ?? ''
);
?>
</div>
<div class="vc-weather-temp">
<span class="vc-weather-high">
High:
<?php
echo esc_html(
$day['tempmax'] ?? ''
);
?>
</span>
<span class="vc-weather-low">
Low:
<?php
echo esc_html(
$day['tempmin'] ?? ''
);
?>
</span>
</div>
<?php
if (
isset(
$day['precipprob']
)
) :
?>
<div class="vc-weather-precip">
Precipitation:
<?php
echo esc_html(
$day['precipprob']
);
?>%
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
Then return the generated HTML:
return ob_get_clean();
Complete WordPress weather plugin
Here is the complete plugin.
Place this entire code sample into:
wp-content/plugins/visual-crossing-weather/visual-crossing-weather.php
<?php
/**
* Plugin Name: Visual Crossing Weather
* Description: Display weather forecasts using the Visual Crossing Weather API and a WordPress shortcode.
* Version: 1.0.0
* Author: Your Name
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Register the [weather] shortcode.
*/
add_shortcode(
'weather',
'visual_crossing_weather_shortcode'
);
/**
* Render a Visual Crossing weather forecast.
*
* Example:
*
* [weather location="London, UK" days="5" unit="metric"]
*
* @param array $atts Shortcode attributes.
*
* @return string
*/
function visual_crossing_weather_shortcode(
$atts
) {
if (
! defined(
'VISUAL_CROSSING_API_KEY'
) ||
! VISUAL_CROSSING_API_KEY
) {
return (
'<p class="vc-weather-error">' .
esc_html(
'Weather API key is not configured.'
) .
'</p>'
);
}
/*
* Apply shortcode defaults.
*/
$atts = shortcode_atts(
array(
'location' => 'London, UK',
'days' => 7,
'unit' => 'metric',
),
$atts,
'weather'
);
/*
* Sanitize shortcode values.
*/
$location =
sanitize_text_field(
$atts['location']
);
$days =
absint(
$atts['days']
);
$unit =
strtolower(
sanitize_text_field(
$atts['unit']
)
);
/*
* Validate number of days.
*/
if ( $days < 1 ) {
$days = 1;
}
if ( $days > 15 ) {
$days = 15;
}
/*
* Validate unit group.
*/
if (
'metric' !== $unit &&
'us' !== $unit
) {
$unit = 'metric';
}
/*
* Build the Timeline Weather API URL.
*/
$encoded_location =
rawurlencode(
$location
);
$api_url =
'https://weather.visualcrossing.com/' .
'VisualCrossingWebServices/rest/services/' .
'timeline/' .
$encoded_location;
$api_url = add_query_arg(
array(
'unitGroup' => $unit,
'include' => 'days',
'elements' =>
'datetime,tempmax,tempmin,' .
'precipprob,conditions,icon',
'key' =>
VISUAL_CROSSING_API_KEY,
'contentType' =>
'json',
),
$api_url
);
/*
* Request the forecast using
* the WordPress HTTP API.
*/
$response = wp_remote_get(
$api_url,
array(
'timeout' => 15,
)
);
/*
* Handle network errors.
*/
if (
is_wp_error(
$response
)
) {
return (
'<p class="vc-weather-error">' .
esc_html(
'Unable to retrieve weather data.'
) .
'</p>'
);
}
/*
* Check the HTTP response.
*/
$status_code =
wp_remote_retrieve_response_code(
$response
);
if (
$status_code < 200 ||
$status_code >= 300
) {
return (
'<p class="vc-weather-error">' .
esc_html(
'The Weather API returned an error.'
) .
'</p>'
);
}
/*
* Decode the Weather API JSON.
*/
$body =
wp_remote_retrieve_body(
$response
);
$weather =
json_decode(
$body,
true
);
/*
* Validate the response.
*/
if (
! is_array(
$weather
) ||
empty(
$weather['days']
)
) {
return (
'<p class="vc-weather-error">' .
esc_html(
'Weather data is unavailable.'
) .
'</p>'
);
}
/*
* Restrict the forecast to
* the requested number of days.
*/
$forecast_days =
array_slice(
$weather['days'],
0,
$days
);
/*
* Generate the HTML.
*/
ob_start();
?>
<div class="vc-weather">
<h3 class="vc-weather-title">
<?php
echo esc_html(
$weather[
'resolvedAddress'
] ?? $location
);
?>
</h3>
<div class="vc-weather-days">
<?php
foreach (
$forecast_days
as $day
) :
?>
<div class="vc-weather-day">
<div class="vc-weather-date">
<?php
echo esc_html(
$day[
'datetime'
] ?? ''
);
?>
</div>
<div class="vc-weather-conditions">
<?php
echo esc_html(
$day[
'conditions'
] ?? ''
);
?>
</div>
<div class="vc-weather-temp">
<span
class="vc-weather-high"
>
High:
<?php
echo esc_html(
$day[
'tempmax'
] ?? ''
);
?>
</span>
<span
class="vc-weather-low"
>
Low:
<?php
echo esc_html(
$day[
'tempmin'
] ?? ''
);
?>
</span>
</div>
<?php
if (
isset(
$day[
'precipprob'
]
)
) :
?>
<div
class="vc-weather-precip"
>
Precipitation:
<?php
echo esc_html(
$day[
'precipprob'
]
);
?>%
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
return ob_get_clean();
}
Activate the plugin
After saving the PHP file:
- Sign in to the WordPress administration area.
- Open Plugins.
- Find Visual Crossing Weather.
- Select Activate.
The [weather] shortcode is now available throughout your site.
Add a forecast to a WordPress page
Edit a page or post and insert:
[weather]
This displays the default forecast for London.
To choose another location:
[weather location="New York, NY"]
For Paris:
[weather location="Paris, France"]
Locations can be cities, addresses, postal codes, or latitude/longitude values supported by the Timeline Weather API.
Change the number of forecast days
Use the days attribute:
[weather location="London, UK" days="3"]
or:
[weather location="London, UK" days="7"]
The plugin limits the value supplied through the shortcode before displaying the returned forecast.
Use US or metric units
For metric units:
[weather location="London, UK" unit="metric"]
For US units:
[weather location="New York, NY" unit="us"]
The Timeline Weather API unitGroup option controls units including temperature, precipitation, wind speed, and visibility.
Add simple CSS styling
The plugin generates CSS classes that you can style using your theme or WordPress custom CSS.
For example:
.vc-weather {
max-width: 900px;
margin: 20px 0;
}
.vc-weather-days {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.vc-weather-day {
flex: 1 1 140px;
padding: 16px;
border: 1px solid #ddd;
border-radius: 6px;
}
.vc-weather-date {
font-weight: 600;
margin-bottom: 6px;
}
.vc-weather-conditions {
margin-bottom: 10px;
}
.vc-weather-temp {
display: flex;
gap: 10px;
margin-bottom: 6px;
}
.vc-weather-error {
padding: 10px;
border: 1px solid #ddd;
}
This styling is deliberately simple so that the forecast inherits the overall appearance of your WordPress theme.
Add current conditions
The same plugin can also request current weather.
Change:
'include' => 'days',
to:
'include' => 'current,days',
and include the required current fields in elements.
Current conditions will then be available in:
$weather['currentConditions']
For example:
$current =
$weather[
'currentConditions'
];
echo esc_html(
$current['temp']
);
echo esc_html(
$current['conditions']
);
For more information, see How to Get Current Weather Conditions from the Weather API.
Add hourly weather
Hourly weather can also be returned by changing:
'include' => 'days',
to:
'include' => 'days,hours',
Hourly records are returned within each day:
$weather['days'][0]['hours']
You can then build another shortcode or extend the existing output to show hourly forecasts.
Why make the API request from WordPress?
This example retrieves the weather on the WordPress server rather than placing the Weather API key into front-end JavaScript.
If a Weather API request is made directly from JavaScript in the browser, the API key included in that request can generally be viewed by visitors using browser developer tools.
Server-side PHP keeps the key out of the generated page and gives the WordPress plugin control over the request and returned HTML.
Handle errors without breaking the page
A shortcode should normally return useful fallback content instead of allowing an API problem to interrupt the rest of the WordPress page.
The plugin checks for:
- Missing API configuration
- Network errors
- HTTP errors
- Invalid JSON or missing data
For example:
if (
is_wp_error(
$response
)
) {
return (
'<p class="vc-weather-error">' .
esc_html(
'Unable to retrieve weather data.'
) .
'</p>'
);
}
For development and troubleshooting, you may also want to log the WordPress error or Weather API response so that administrators can see the underlying cause.
Request only the weather fields you need
The plugin uses:
elements=datetime,tempmax,tempmin,precipprob,conditions,icon
rather than retrieving every available Weather API field.
You can modify the list depending on your weather display.
Other useful fields include:
temp
feelslike
humidity
dew
precip
snow
snowdepth
windspeed
windgust
winddir
pressure
cloudcover
visibility
uvindex
sunrise
sunset
See the Timeline Weather API documentation for the complete list.
Shortcodes and user input
Shortcode attributes should always be treated as user-provided input.
In this example we use WordPress functions such as:
sanitize_text_field()
absint()
esc_html()
shortcode_atts()
to validate, normalize, and escape values.
This is particularly important if your plugin will be distributed or used on sites where multiple users can create or edit content.
Going further
This tutorial deliberately creates a small plugin that is easy to understand and customize.
You could extend it to support:
- Current conditions
- Hourly forecasts
- Weather icons
- Precipitation
- Wind
- Sunrise and sunset
- Weather alerts
- Custom titles
- Additional shortcode attributes
- Multiple display layouts
For example:
[weather
location="Miami, FL"
days="5"
unit="us"
]
could be extended with attributes controlling the exact weather elements or presentation style.
The Visual Crossing Weather API provides programmatic access to historical weather, current conditions, forecasts, alerts, and additional weather information.
For interactive access to historical and forecast datasets, see Visual Crossing Weather Data.
If you haven’t created an account yet, sign up for a free Visual Crossing account.
For detailed request parameters and response structures, see the Timeline Weather API documentation.
Summary
A WordPress shortcode plugin provides a straightforward way to add dynamic weather forecasts to posts and pages.
The basic process is:
- Create a WordPress plugin.
- Register a
[weather]shortcode withadd_shortcode(). - Read and validate shortcode attributes using
shortcode_atts(). - Build a Timeline Weather API request.
- Retrieve the forecast using
wp_remote_get(). - Check the HTTP response.
- Decode the returned JSON.
- Escape the weather values when generating HTML.
- Return the forecast markup from the shortcode function.
The result is a small server-side WordPress integration that can display weather for any supported location without requiring a JavaScript weather library or third-party WordPress dependency.

