ONVIF node.js implementation
TypeScript-first ONVIF client for Node.js.
[!TIP] Looking for stable 0.x / 0.8? This page is for 1.x (release candidate).
README and docs for the default npm install: branch v0.x.
Staying on 0.x while trying 1.x? Use the 0.x compatibility API.
Requires Node.js 18+.
For new projects, use 1.x.
For existing 0.x projects, keep using the 0.x compatibility API or migrate to the 1.x Onvif API —
see innerDocs/migration.md.
npm install onvif@rcThis README describes 1.x. The package is still on the release-candidate channel until 1.0 is published.
npm install onvifDefault npm install onvif still resolves to stable 0.x. README for that line:
branch v0.x.
API reference (TypeDoc): https://agsh.github.io/onvif/ —
start from the Onvif class.
The main entry is Onvif. After connect(), call methods on service namespaces. Most namespaces are
lazy-loaded on first use; events is constructed eagerly.
…Also exported: Discovery (WS-Discovery on the LAN),
and the separate 0.x compatibility entry points.
Version 1.x is a redesign of the original JavaScript API:
xs:any / xs:anyAttribute) — innerDocs/vendor-extensions.mdNew API ≠ compatibility API. Pick one surface and stick to it.
| 1.x API | 0.x compatibility API | |
|---|---|---|
| Import | import { Onvif } from 'onvif' |
require('onvif/compatibility') or …/promises |
| Type | Onvif + service namespaces |
Cam / Discovery (v0.x shape) |
| For | new projects | existing Cam-based apps |
import { Onvif } from 'onvif';
const onvif = new Onvif({ hostname: '192.168.1.13', port: 8000, username: 'admin', password: 'admin' });
await onvif.connect();
await onvif.media.getProfiles();// Callbacks — onvif/compatibility
const { Cam } = require('onvif/compatibility');
const cam = new Cam(
{ hostname: '192.168.1.13', port: 8000, username: 'admin', password: 'admin' },
(error) => {
if (error) throw error;
cam.getProfiles((err, profiles) => {
if (err) throw err;
console.log(profiles);
});
},
);// Promises — onvif/compatibility/promises
const { Cam } = require('onvif/compatibility/promises');
const cam = new Cam({ hostname: '192.168.1.13', port: 8000, username: 'admin', password: 'admin' });
await cam.connect();
console.log(await cam.getProfiles());Compatibility import paths, full examples, and known behavioral differences: innerDocs/migration.md.
import { Onvif } from 'onvif';
const onvif = new Onvif({ hostname: '192.168.1.13', port: 8000, username: 'admin', password: 'admin' });
await onvif.connect();
const info = await onvif.device.getDeviceInformation();
console.log(info);Same import works from CommonJS (require('onvif')) and ESM. Call connect() before service methods
(or pass autoConnect: true).
[!NOTE] Node.js — works with plain JavaScript (
require/import). TypeScript is optional; types ship with the package when you want them. Here is an example with the cjs-style.
A small example showing how to use ONVIF with FFmpeg, RTSP and Socket.IO (http://localhost:6147) with 1 ffmpeg and 3 node.js libraries:
sudo apt install ffmpeg
npm install onvif@rc socket.io rtsp-ffmpeg…EventEmitter — see innerDocs/events.mdDevice, Events, Media, Media2, PTZ, Imaging, Analytics,
AnalyticsDevice, Recording, Replay, Search, Receiver, DeviceIO, Display, Action Engine,
Thermal, DoorControl, AccessControl, Credential, AccessRules, Schedule, Provisioning,
AdvancedSecurityBefore most methods work, call connect() on your Onvif instance. It handshakes with the device and fills
internal state so later SOAP requests are authenticated and routed to the correct endpoints.
connect() runs these steps in order:
getSystemDateAndTime() first. ONVIF WS-Security includes a timestamp in the nonce
digest, so the client needs the clock offset (timeShift). The library tries an unauthenticated request first
(allowed by the spec) and retries with credentials when needed (some Panasonic and Digital Barriers models).GetServices (Profile T) via a small connection helper, without loading the full
device module. On older devices it falls back to GetCapabilities. Both populate onvif.uri with media, PTZ,
events, replay, and other service URLs.GetProfiles and GetVideoSources in parallel,
then getActiveSources() matches video sources to profiles. Sets activeSource, defaultProfile, and
defaultProfiles. Devices without video (e.g. Profile C door stations) skip this when Media is absent.
If Media is listed but a call fails (e.g. Axis A1601 “Optional action not implemented”), connect() still
succeeds with empty profiles / videoSources and emits warn.On success, connect() emits connect and returns the instance. See Quick start for a minimal example.
Methods take typed ONVIF request options and return the corresponding response data (often unwrapped when there is a
single property). Some helpers accept more convenient fields (for example dateTime?: Date on
SetSystemDateAndTimeExtended).
See the API documentation for per-service methods.
Pull-point and WS-BaseNotification subscriptions, topic filters, and EventEmitter integration.
onvif.on('event', (msg) => console.log(msg));Full guide (including Subscription and push notifications): innerDocs/events.md.
ONVIF schemas use xs:any / xs:anyAttribute extension points. This library exposes them via xsany and $
so vendor-specific XML can be read and written without losing data.
Details and camera examples: innerDocs/vendor-extensions.md.
The 1.x API covers all methods available in v0.8. A separate compatibility layer is provided for existing applications — functional coverage, not bit-identical behavior.
Full guide with callback / Promise examples and known differences: innerDocs/migration.md.
Additional samples are in the examples folder.
.cjs files alone): innerDocs/migration.mdLegacy numbered files (example.js … example9.js) still target mixed 0.x / partial 1.x APIs; prefer the samples above.
In 1.x you only pay for the ONVIF services you actually use.
| 0.x | 1.x | |
|---|---|---|
| Service loading | eager | lazy |
| TypeScript | — | ✓ |
| Typed WSDL interfaces | — | ✓ |
| Promise API | compatibility / wrappers | native |
| Large services loaded at startup | ✓ | — |
onvif.device, onvif.media, onvif.ptz, onvif.thermal, …) are lazy proxies.
The corresponding module is loaded the first time you call a method on it (for example await onvif.ptz.getNodes()).connect() uses dedicated helpers in connection.ts for the handshake SOAP (GetServices /
GetCapabilities, Media GetProfiles / GetVideoSources). It does not load the full device / media /
media2 class modules. Profiles and video sources are stored on the Onvif instance; when Media is later
loaded, it reuses that cache.Events is constructed eagerly (needed for onvif.on('event', …)). Everything else stays deferred.import { Onvif } from 'onvif';
const onvif = new Onvif({ hostname: '192.168.1.13', username: 'admin', password: 'admin' });
await onvif.connect(); // handshake only — no full Media/Device class modules yet
const info = await onvif.device.getDeviceInformation(); // loads device.js on first use
const uri = await onvif.media.getStreamUri({ protocol: 'RTSP' }); // loads media.js on first use
// onvif.thermal is never loaded unless you call itMeasured against happytime-onvif-server on Node.js 24
(heapUsed after
No open issues yet, or sync has not completed.