onvif
    Preparing search index...

    onvif

    ONVIF

    Coverage Status

    TypeScript-first ONVIF client for Node.js.

    • TypeScript + Promise API
    • Typed ONVIF/WSDL interfaces
    • Profiles S, T, G, M, C, A
    • WS-Discovery, WS-Security, Digest (MD5 / SHA-1 / SHA-256)
    • Lazy-loaded service modules
    • v0.x compatibility layer
    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.

    ONVIF

    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@rc
    

    This README describes 1.x. The package is still on the release-candidate channel until 1.0 is published.

    npm install onvif
    

    Default 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.

    Onvif
    ├── connect() / request()             # handshake + raw SOAP 
    │                                     # + some actions from device/media/media2 (these modules are not loaded)
    ├── device                            # Device management (lazy)
    ├── media / media2                    # Profiles S / T media (lazy)
    ├── ptz                               # Pan-tilt-zoom (lazy)
    ├── events                            # Pull-point / WS-BaseNotification (eager)
    ├── imaging                           # Imaging settings (lazy)
    ├── recording / replay / search       # Profile G NVR (lazy)
    ├── receiver                          # Stream receivers (lazy)
    ├── analytics / analyticsDevice       # Analytics (lazy)
    ├── deviceIO / display / actionEngine # (lazy)
    ├── thermal / provisioning            # (lazy)
    ├── doorControl / accessControl / credential / accessRules / schedule 
    │                                     # (lazy)
    └── advancedSecurity                  # TLS / keystore (experimental, lazy)
    
    Discovery                              # separate export (WS-Discovery on the LAN)
    ├── probe()                            # find NVT devices; returns Promise of Onvif / info objects
    └── on('device' | 'error', …)          # EventEmitter — device found / errors
    

    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:

    • TypeScript-first API with generated ONVIF interfaces
    • Native Promise-based methods
    • Lazy-loaded services
    • More ONVIF services than 0.x (access control, thermal, door control, etc.)
    • Improved error handling
    • Explicit support for vendor-specific XML extensions (xs:any / xs:anyAttribute) — innerDocs/vendor-extensions.md
    • Optional compatibility layer for existing 0.x applications

    New 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
    const server = require('http').createServer((req, res) =>
    res.end(`
    <!DOCTYPE html><body>
    <canvas width='640' height='480' />
    <script src="/socket.io/socket.io.js"></script><script>
    const socket = io(), ctx = document.getElementsByTagName('canvas')[0].getContext('2d');
    socket.on('data', (data) => {
    const img = new Image;
    const url = URL.createObjectURL(new Blob([new Uint8Array(data)], {type: 'application/octet-binary'}));
    img.onload = () => {
    URL.revokeObjectURL(url, {type: 'application/octet-binary'});
    ctx.drawImage(img, 100, 100);
    };
    img.src = url;
    });
    </script></body></html>`),
    );
    const { Onvif } = require('onvif');
    const io = require('socket.io')(server);
    const rtsp = require('rtsp-ffmpeg');
    server.listen(6147);

    const onvif = new Onvif({ username: 'username', password: 'password', hostname: '192.168.0.116', port: 2020 });
    (async () => {
    await onvif.connect();
    const input = (await onvif.media.getStreamUri({ protocol: 'RTSP' })).uri.replace(
    '://',
    `://${onvif.username}:${onvif.password}@`,
    );
    const stream = new rtsp.FFMpeg({ input, resolution: '320x240', quality: 3 });
    io.on('connection', (socket) => {
    const pipeStream = socket.emit.bind(socket, 'data');
    stream.on('disconnect', () => stream.removeListener('data', pipeStream)).on('data', pipeStream);
    });
    setInterval(
    () =>
    onvif.ptz.absoluteMove({
    position: {
    x: Math.random() * 2 - 1,
    y: Math.random() * 2 - 1,
    zoom: Math.random(),
    },
    }),
    3000,
    );
    })().catch(console.error);
    • Typed request/response interfaces from the latest ONVIF WSDL (onvif-generate-interfaces)
    • API documentation
    • Integration tests against HappyTimeSoft ONVIF server
    • Events: pull-point, WS-BaseNotification, filters, EventEmitter — see innerDocs/events.md
    • Lazy-loaded services — see Performance / lazy loading
    • Auth: WS-Security, Digest; Advanced Security (experimental)
    • WS-Discovery on the LAN
    • Implemented services — Device, Events, Media, Media2, PTZ, Imaging, Analytics, AnalyticsDevice, Recording, Replay, Search, Receiver, DeviceIO, Display, Action Engine, Thermal, DoorControl, AccessControl, Credential, AccessRules, Schedule, Provisioning, AdvancedSecurity
    • The following services currently have interfaces but no high-level implementation: AuthenticationBehavior, Application Management (appmgmt), Uplink, FederatedSearch (from the ONVIF Network Interface Specifications)
    • Optional v0.x compatibility entry points — see Two APIs

    Connection

    Before 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:

    1. Time synchronizationgetSystemDateAndTime() 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).
    2. Service discoveryGetServices (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.
    3. Media configuration (only if the device advertises Media) — 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.


    Services

    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.


    Events

    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.


    Vendor XML

    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.


    Migration from v0.x

    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.


    Examples

    Additional samples are in the examples folder.

    Legacy numbered files (example.jsexample9.js) still target mixed 0.x / partial 1.x APIs; prefer the samples above.


    Performance / lazy loading

    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
    • Service namespaces (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 it

    Measured against happytime-onvif-server on Node.js 24 (heapUsed after GC; illustrative):

    Library Core Partial All
    onvif 1.x ~6.2 MiB ~7.0 MiB ~7.5 MiB
    onvif 0.8 ~6.3 MiB ~6.8 MiB ~6.9 MiB
    node-onvif ~6.5 MiB ~6.5 MiB ~6.5 MiB
    @2bad/onvif (a fork of an earlier version of onvif 1.0) ~8.9 MiB ~9.1 MiB ~9.1 MiB
    • Coreconnect() + device information
    • Partial — + media / PTZ / discovery (media2 where available)
    • All — every service module that library exposes (1.x covers 20+ services)

    Lazy loading mainly helps the Core path: 1.x stays close to the memory footprint of 0.8 when only Device is used, and grows as additional services are accessed. Compiled JS sizes and methodology notes: innerDocs/performance.md.


    Development

    git clone https://github.com/agsh/onvif.git
    cd onvif
    npm install
    npm run build
    npm run lint
    npm test
    • npm test — lint, start the HappyTime mock ONVIF server, run Jest, stop the server
    • npm run test-local — Jest only (expects a server already on the configured host/port)
    • npm run build / npm run lint — TypeScript build and ESLint

    Default integration tests use happytime-onvif-server (__tests__/happytime.json, typically 127.0.0.1:8000).

    More detail (golden compatibility suite, pointing tests at another device): innerDocs/testing.md.

    Further reading:


    Device compatibility

    The library has been tested with cameras and devices from Axis, Bosch, Canon, Hanwha, Hikvision, Panasonic, Sony and other vendors.

    Please report your device and firmware using our compatibility form.

    Run console.log(await onvif.device.getDeviceInformation()); — you should get something like:

    {
      "manufacturer": "tp-link",
      "model": "Tapo C220",
      "firmwareVersion": "1.4.4 Build 260515 Rel.24570n",
      "serialNumber": "7461572b",
      "hardwareId": 1
    }
    

    Thanks

    Thanks to HappyTimeSoft for allowing us to use HappyTime ONVIF Server in our integration tests.

    Thanks to @RogerHardiman for the ongoing support, for keeping this project honest against the ONVIF specification, and for testing on real cameras.

    Thanks to everyone who filed issues over the years. We have not always replied quickly — day jobs come first — and we are sorry if open issues were left unanswered. Vendor device support, and much of what this library is today, exists because of you.

    If you have a lot of cameras and are willing to let us try this library against them, I would be happy to — open an issue or reach out.

    OBEY!