Hello everyone!
A lot has changed in recent weeks regarding external integrations: new version of the JavaScript SDK (@gladysassistant/integration-sdk v0.16.0), new fields in the manifest, new validation rules in the store… This post gathers everything you need to do to update your integration, and what you can do additionally if you wish.
Good news: there is no mandatory breaking change. An integration published today remains indexed. But a few points deserve a check, and the new features are worth a look ![]()
The Quick Checklist
- Upgrade the SDK to
^0.16.0(Node.js ≥ 20) - Check your
poll_frequency: in milliseconds, and only among the allowed values - Use
publishChangedStatesif you publish many states (limit of 300 states/minute) - Add
categories(and optionallykeywords/countries) to the manifest - Check that
gladys_versioncovers all the fields you declare - Have
docs/en.mdanddocs/fr.md(300 characters minimum each) - Check the cover: JPEG or PNG, 800×534 px exactly, 150 Ko maximum
- Run the local validator:
npx github:GladysAssistant/integration-store - Publish: increment
version+docker_image, pushing the image before the manifest
The details of each point are below.
1. Updating the SDK
npm install @gladysassistant/integration-sdk@^0.16.0
The SDK requires Node.js ≥ 20. Make sure to check the base image of your Dockerfile.
What has changed since v0.11:
| Version | Main New Features |
|---|---|
| 0.12 | Sync with Gladys 4.86: electric/gas network sensors, PTZ cameras, wakeOnLan(), account_link config field |
| 0.13 | Six new weather phenomena in the pivot format, sirens, battery recharge, scan result format |
| 0.14 | Scene triggers and actions, dashboard widgets, getHouses(), network carbon and smoke sensors |
| 0.15 | DEVICE_POLL_FREQUENCIES, Fake Gladys for your tests, parseMdnsTxt(), publishChangedStates(), calendar type, energy contracts, forms in widget buttons, house selector, publishEnergyConsumption() |
| 0.16 | Music players, ai and tts types, should_poll becomes optional |
The full changelog is on GitHub.
2. Points to Check in Your Code
poll_frequency: in milliseconds, and only certain values
This is the most common pitfall. poll_frequency is expressed in milliseconds, and only these values are accepted: 1 s, 2 s, 10 s, 15 s, 30 s or 1 min. Any other value (300 thought in seconds, 300000 for 5 minutes…) makes publishDiscoveredDevices fail with a 400 error.
Use the constants exported by the SDK:
import { DEVICE_POLL_FREQUENCIES } from '@gladysassistant/integration-sdk';
await gladys.publishDiscoveredDevices([
{
name: 'Weather Station',
external_id: ids.device,
should_poll: true,
poll_frequency: DEVICE_POLL_FREQUENCIES.EVERY_MINUTES, // 60000 ms
features: [/* … */],
},
]);
A few clarifications:
- One minute is the slowest pace. To go slower (cloud API with quota, user setting in minutes), keep
EVERY_MINUTESand ignore unnecessary polls inonPoll, or use your own timer. - Since Gladys 5.3,
poll_frequencyis enough: the core activatesshould_pollon its own. If yourgladys_versionaccepts older versions, keepshould_poll: true. If you setshould_poll, it must be a boolean. - These two fields are read when the device is created: modifying them later does not update an already created device.
300 states per minute limit: publishChangedStates
The host API limits POST /state to 300 states per minute and per integration. If you query many devices, only publish what has changed:
// replaces publishStates: only modified values go to Gladys
await gladys.publishChangedStates(states);
// and we still return an unchanged value every 30 minutes
await gladys.publishChangedStates(states, { heartbeat: 30 * 60 * 1000 });
The SDK splits batches of more than 100 states, returns what Gladys refused, and forgets the values of a device when the user creates, modifies, or deletes it.
Use constants, not hardcoded strings
DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES and DEVICE_FEATURE_UNITS are a copy of server/utils/constants.js in Gladys, and they are kept up to date with each version. If you manually copied strings, now is the time to switch to constants: you avoid typos and have autocompletion.
Weather integrations: night is deprecated
The night condition is still accepted but it is deprecated. A rainy night is written weather: 'rain', is_day: false. Also think about the new conditions of the pivot format and the type field of alerts (WEATHER_ALERT_TYPES).
Scene triggers and actions: keys are final
If you declare scene_triggers / scene_actions, a published key must never be renamed. And adding a required field without default to an existing action breaks the scenes that use it.
3. Updating the Manifest
The golden rule: gladys_version must cover what you declare
Old versions of Gladys reject fields they don’t know. The store therefore checks that the minimum of your gladys_version covers each declared field, otherwise the integration is rejected. This way, the user simply sees « requires Gladys ≥ X » in the catalog, instead of an installation that fails without explanation.
| You declare… | Minimum gladys_version |
|---|---|
categories |
>=4.86.0 |
type: "provider", widgets, scene_triggers, scene_actions |
>=5.1.0 |
type: "calendar", account_schema, energy_contracts, a field with source: "houses" |
>=5.2.0 |
type: "ai", type: "tts", music, keywords, countries |
>=5.3.0 |
Conversely, do not increase gladys_version without reason: your integration would be hidden from users who have not yet updated Gladys.
categories: your place in the catalog
Between 1 and 3 categories among: climate, lighting, energy, security, multimedia, appliances, environment, protocols, network, notifications, assistants, services, transport, vehicles, servers, leisure.
The last four (transport, vehicles, servers, leisure) have just been added. Without categories, your integration remains visible in « All » and in the search, but does not appear in any section. For already published integrations, the store applies in the meantime a fallback mapping (data/category-fallback.json). Declaring your own categories is the best way to be well placed. An unknown category is simply ignored, with a warning.
keywords and countries (Gladys ≥ 5.3)
{
"gladys_version": ">=5.3.0",
"categories": ["energy"],
"keywords": ["linky", "enedis", "téléinfo"],
"countries": ["FR"]
}
keywords: 1 to 10 terms of 2 to 30 characters, never displayed, used only by search (brands, models, synonyms, cities or networks served…).countries: 1 to 20 ISO country codes in uppercase. Do not include this if your integration works everywhere (this is the default behavior). If you declare it, instances located in another country will no longer see the integration in the catalog sections (it remains findable via search). Think about this for a regional integration: national weather service, transport network, energy provider, ISP…
Do not forget: declaring keywords or countries requires gladys_version ≥ 5.3.0.
4. Documentation and cover
- Mandatory documentation :
docs/en.mdanddocs/fr.mdat the root of the repository, 300 characters minimum each, following the sections of the template (Presentation / Prerequisites / Configuration / Troubleshooting). If one is missing, the integration is rejected. The files are hosted by the store and displayed in the installation and configuration screens of Gladys. - New :
docs/de.mdanddocs/es.mdare optional. If they are present (300 characters minimum), they are displayed to German and Spanish users. - Cover : JPEG or PNG, exactly 800×534 px, 150 Ko maximum, direct
httpsURL (without redirection). The simplest: araw.githubusercontent.comURL to a file in your repository. An invalid cover does not block indexing, but you will have a default image in the catalog.
5. Validate locally before publishing
No need to wait for hourly indexing to discover a rejection! At the root of your repository:
npx github:GladysAssistant/integration-store
The validator performs exactly the same checks as the store robot (schema, gladys_version rules, categories, documentation, existence of Docker images, cover) and displays all problems at once. Exit code 0 = the integration would be indexed, 1 = it would be rejected.
In the CI of your pull requests
In a PR that increments the version, the Docker image is generally not yet published: add --skip-image-check to skip only this check.
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npx github:GladysAssistant/integration-store --skip-image-check
Run it without the option once the images are published, for example in the last step of your release workflow.
6. Publish the new version
- Increment
version(strict semver) anddocker_image(with an explicit tag) in the manifest. - Publish the Docker image before the manifest that references it arrives on the default branch: the store checks that the image exists and is downloadable without authentication. If the image is missing, the integration is removed from the catalog until the next indexing.
- Push. At the next indexing (every hour), Gladys will display “update available” to your users.
Your integration does not appear? Look at the public rejected.json file of the store: each rejection is listed there with its reason (error = not indexed, warning = indexed with a degradation, for example the default cover).
7. Test your integration with the fake Gladys
The SDK now provides an in-memory Gladys for your unit tests: it’s a real GladysIntegration, connected to a fake Gladys instead of the network.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createFakeGladys } from '@gladysassistant/integration-sdk/testing';
import { registerHandlers } from '../src/app.js';
test('publishes the temperature on poll', async () => {
const gladys = createFakeGladys({ config: { api_key: 'key' } });
registerHandlers(gladys);
await gladys.connect();
const [device] = gladys.fake.discoveredDevices;
assert.deepEqual(await gladys.fake.poll(device), { success: true });
assert.equal(gladys.fake.lastState(`${device.external_id}:temperature`), 21.5);
});
It applies the same checks as the host API (prefix of external_id, known categories/types/units, poll_frequency…) and responds with the same 400 errors. You therefore see the errors in your tests rather than with your users.
8. To go further (optional)
If you want to enrich your integration, here is everything that is now possible:
| Feature | Manifest | SDK | Gladys |
|---|---|---|---|
| Dashboard widgets | widgets |
onWidgetGet, onWidgetAction, requestWidgetRefresh |
≥ 5.1 |
| Scene triggers / actions | scene_triggers, scene_actions |
publishSceneEvent, onSceneAction |
≥ 5.1 |
| “Capabilities only” integration | type: "provider" |
— | ≥ 5.1 |
| Calendar provider | type: "calendar", account_schema |
publishCalendars, publishCalendarEvents… |
≥ 5.2 |
| Energy contracts and calendars | energy_contracts |
publishEnergyCalendar, onEnergyPrice… |
≥ 5.2 |
| Dated consumption (readings received later) | — | publishEnergyConsumption |
≥ 5.2 |
| House selection in a form | source: "houses" |
— | ≥ 5.2 |
| AI provider (compatible with OpenAI) | type: "ai" |
onAiChat |
≥ 5.3 |
| Text-to-speech | type: "tts" |
onTtsSynthesize |
≥ 5.3 |
| Music players (navigation, search, queue) | music |
publishNowPlaying, onMusicBrowse, onMusicPlay… |
≥ 5.3 |
| Wake-on-LAN | network_wake: true |
wakeOnLan |
≥ 4.86 |
| Connection to an account without redirection (QR code…) | account_link field |
onOAuthAuthorizeUrl |
≥ 4.86 |
Everything is documented in the SDK README, and all the manifest rules are in the store README.
If you are stuck on a migration or if a validation rule seems strange to you, reply in this topic or open an issue on integration-sdk-js / integration-store.
Thanks to everyone who develops integrations, the catalog is growing very quickly! ![]()