I’m a keen sailor and have been using a sailserver device on my boat to log my trips. As sailserver uploads its data to a cloud, I’ve always played with the idea to connect it to HomeAssistant. Today I’ve gone a step further, by also exporting some of the data to my boats website.
I hope there are some fellow sailserver owners here that can benefit from this work, so decided to share.
Have fun!
Integrating SailServer with Home Assistant and WordPressA practical, secure pattern for publishing live boat telemetry
Publication-ready community guide
All examples use BoatName as a replaceable placeholder
Disclaimer.This community guide is provided as-is and is not official documentation from SailServer, Home Assistant, or WordPress. APIs and platform behavior may change. Test the configuration and never publish your SailServer API key or Home Assistant token.
Overview
This guide connects SailServer, Home Assistant and WordPress to retrieve vessel telemetry, transform it into readable Home Assistant entities, show a combined status-and-map card, and publish a deliberately limited status view on a website.
Goal and architecture
SailServer remains the telemetry source. Home Assistant polls SailServer and converts the response into sensors. A WordPress plugin reads one selected Home Assistant entity over HTTPS, caches the rendered result for five minutes, and exposes it through a shortcode.
Naming convention used in this guide
Every example uses BoatName as a placeholder. Replace BoatName with your vessel name. The examples assume that Home Assistant creates the lowercase entity prefix boatname. Verify the actual IDs in Developer Tools > States after reloading or restarting.
Before you start
- A working SailServer account and a vessel visible in that account.
- A Home Assistant installation that can reach the SailServer endpoint .
- A WordPress site that can reach your external Home Assistant URL over HTTPS .
- Access to configuration.yaml, secrets.yaml, wp-config.php, and WordPress plugin management.
Getting a SailServer API key
An API key is required for the getboat request. API access is controlled by SailServer. If your account does not expose an API or integration option, contact the SailServer owner or support channel, request API access for your vessel, and ask for confirmation that the getboat command is enabled.

Part 1: Configure Home Assistant
1. Add the SailServer REST sensor to configuration.yaml
If a top-level rest: section already exists, merge this item into the existing list rather than creating a second rest: key.
################################################
# SailServer data integration
################################################
rest:
- resource: "https://app.sailserver.com/mod/api.php"
method: POST
scan_interval: 300
payload: !secret sailserver_getboat_payload
headers:
Content-Type: application/json
sensor:
- name: BoatName Status
value_template: >
{{ value_json.data.inst.sog | float(0) }}
json_attributes_path: "$.data"
json_attributes:
- boatname
- inst
- weather
2. Add the template entities to configuration.yaml
If a top-level template: section already exists, merge the list items beneath it.
################################################
# Boat telemetry template entities
################################################
template:
- sensor:
- name: BoatName Latitude
state: >
{% set inst = state_attr('sensor.boatname_status', 'inst') %}
{{ inst.get('lat') if inst is mapping else none }}
- name: BoatName Longitude
state: >
{% set inst = state_attr('sensor.boatname_status', 'inst') %}
{{ inst.get('lon') if inst is mapping else none }}
- name: BoatName Wind Speed
unit_of_measurement: "kn"
icon: mdi:weather-windy
state: >
{% set inst = state_attr('sensor.boatname_status', 'inst') %}
{{ inst.get('tws') if inst is mapping else none }}
- name: BoatName Water Temperature
unit_of_measurement: "°C"
device_class: temperature
state: >
{% set inst = state_attr('sensor.boatname_status', 'inst') %}
{{ inst.get('watmp') if inst is mapping else none }}
- name: BoatName Location
icon: mdi:sail-boat
state: >
{% if is_state('binary_sensor.boatname_underway', 'on') %}
Underway
{% else %}
Moored
{% endif %}
attributes:
latitude: >
{{ states('sensor.boatname_latitude') }}
longitude: >
{{ states('sensor.boatname_longitude') }}
entity_picture: /local/boat_icon.png
- binary_sensor:
- name: BoatName Underway
state: >
{% set inst = state_attr('sensor.boatname_status', 'inst') %}
{{ inst is mapping and inst.get('sog', 0) | float(0) > 0.5 }}
- name: BoatName Moored
state: >
{% set inst = state_attr('sensor.boatname_status', 'inst') %}
{{ inst is mapping and inst.get('sog', 0) | float(0) <= 0.5 }}

3. Store the SailServer key in secrets.yaml
# SailServer
sailserver_getboat_payload: >
{
"apikey": "YOUR_SAILSERVER_API_KEY",
"cmd": "getboat"
}
4. Validate and reload
Validate the Home Assistant configuration.
- Restart Home Assistant or reload the relevant YAML integrations where supported .
- Verify all eight entity IDs in Developer Tools > State s.
- Inspect sensor.boatname_status and confirm that inst contains sog, lat, lon, tws, and wat mp.
- If Home Assistant generated different IDs, update every reference be low.
5. Add the complete dashboard card
The following example uses the custom:vertical-stack-in-card frontend component to combine the status Markdown card and a map into one visual card. Replace BoatName and boatname with your own display name and generated entity prefix.
type: custom:vertical-stack-in-card
cards:
- type: markdown
content: >-
BoatName:
{% if is_state(‘binary_sensor.boatname_underway’, ‘off’) %}
Moored | Water
{{ states(‘sensor.boatname_water_temperature’) }} °C
{% else %}
{{ states(‘sensor.boatname_status’) }} kn |
Wind {{ states(‘sensor.boatname_wind_speed’) }} kn
{% endif %} - type: map
entities:- entity: sensor.boatname_location
aspect_ratio: ‘2’
default_zoom: 12
theme_mode: dark
- entity: sensor.boatname_location
Part 2: Publish a limited status in WordPress
The WordPress plugin below requests sensor.boatname_status from Home Assistant, renders selected attributes, and caches the HTML for five minutes.

- Create a long-lived Home Assistant access token
Open your Home Assistant user profile, create a Long-Lived Access Token, copy it once, and store it securely. WordPress sends it in the Authorization: Bearer header.
Official reference: Home Assistant REST API documentation
2. Store the endpoint and token in wp-config.php
define( 'BOAT_TRACKER_HA_URL', 'https://your-home-assistant-host' );
define( 'BOAT_TRACKER_HA_TOKEN', 'YOUR_LONG_LIVED_ACCESS_TOKEN' );

3. Create the WordPress plugin
Create wp-content/plugins/boat-tracker/boat-tracker.php with the following code
<?php /\*\* \* Plugin Name: Boat Tracker \* Description: Displays selected boat telemetry from Home Assistant. \* Version: 1.0.0 \*/ if ( ! defined( 'ABSPATH' ) ) { exit; } function boat_tracker_shortcode() { $cache_key = 'boat_tracker_markup_v1'; $cached = get_transient( $cache_key ); if ( false !== $cached ) { return $cached; } if ( ! defined( 'BOAT_TRACKER_HA_URL' ) || ! defined( 'BOAT_TRACKER_HA_TOKEN' ) ) { return 'Boat data is not configured.
'; } $entity_id = 'sensor.boatname_status'; $url = trailingslashit( BOAT_TRACKER_HA_URL ) . 'api/states/' . rawurlencode( $entity_id ); $response = wp_remote_get( $url, array( 'headers' => array( 'Authorization' => 'Bearer ' . BOAT_TRACKER_HA_TOKEN, 'Content-Type' => 'application/json', ), 'timeout' => 10, ) ); if ( is_wp_error( $response ) ) { return 'Boat data is temporarily unavailable.
'; } $status_code = wp_remote_retrieve_response_code( $response ); $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( 200 !== $status_code || ! is_array( $data ) ) { return 'Boat data is temporarily unavailable.
'; } $speed = isset( $data\['state'\] ) ? (float) $data\['state'\] : 0; $inst = isset( $data\['attributes'\]\['inst'\] ) && is_array( $data\['attributes'\]\['inst'\] ) ? $data\['attributes'\]\['inst'\] : array(); $wind = isset( $inst\['tws'\] ) ? $inst\['tws'\] : 'unknown'; $water = isset( $inst\['watmp'\] ) ? $inst\['watmp'\] : 'unknown'; $movement = $speed > 0.5 ? 'Underway' : 'Moored'; $icon = $speed > 0.5 ? '⛵' : '⚓'; $ts = isset( $data\['last_updated'\] ) ? strtotime( $data\['last_updated'\] ) : false; $updated = $ts ? wp_date( 'd-m-Y H:i', $ts ) : 'unknown'; $markup = ''; $markup .= esc_html( $icon . ' ' . $movement ) . '
'; $markup .= 'Speed: ' . esc_html( number_format_i18n( $speed, 1 ) ) . ' kn
'; $markup .= 'Wind: ' . esc_html( $wind ) . ' kn
'; $markup .= 'Water: ' . esc_html( $water ) . ' °C
'; $markup .= 'Last update: ' . esc_html( $updated ) . ''; $markup .= '


