@graphland/zkteco
v0.1.0
Published
TypeScript client for ZKTeco biometric attendance devices — TCP/UDP protocol, users, attendance logs, real-time events
Readme
@graphland/zkteco
TypeScript client for ZKTeco biometric attendance devices. Connects over TCP (with UDP fallback), supports CommKey authentication, and provides a high-level API for users, attendance, and device management.
Works on Node.js ≥ 18 and Bun — no runtime dependencies.
Installation
npm install @graphland/zkteco
# or
bun add @graphland/zktecoShips as ESM with bundled TypeScript declarations. Prefer a GUI? The same engine powers the Graphland ZKT Client desktop app for macOS and Windows.
Quick start
import { ZKTecoClient } from "@graphland/zkteco";
const zk = new ZKTecoClient({
ip: "192.168.0.153",
port: 6523, // device TCP port (default: 4370)
timeout: 10000,
commKey: 0, // device CommKey password (default: 0)
});
await zk.connect();
const info = await zk.getInfo();
console.log(info); // { userCounts, logCounts, logCapacity }
await zk.disconnect();Configuration
| Option | Default | Description |
|--------|---------|-------------|
| ip | — | Device IP address (required) |
| port | 4370 | TCP port |
| timeout | 10000 | Socket timeout in ms |
| udpPort | 4000 | Local UDP bind port (fallback) |
| commKey | 0 | Communication key set on the device |
| openDoorDelaySec | 3 | Door unlock duration |
Connection
- Tries TCP first, falls back to UDP if TCP is refused.
- Handles CommKey auth:
CMD_ACK_UNAUTH→CMD_AUTHhandshake (same as pyzk). - Validates the device after connect via
getInfo().
await zk.connect({
onError: (err) => console.error(err),
onClose: (type) => console.log(`Disconnected (${type})`),
});Users
// List all users
const users = await zk.getUsers();
// Find by id (throws ZkNotFoundError if missing)
const user = await zk.getUserById("5011");
// Search
const results = await zk.searchUsers("rayhan");
const exact = await zk.searchUsers({ userId: "5011", match: "exact" });
const first = await zk.searchUser({ name: "Ray" }); // null if not found
// Create / update / delete
await zk.createUser({ userId: "99", name: "Jane", password: "1234" });
await zk.updateUser("99", { name: "Jane Updated" });
await zk.deleteUser("99"); // or deleteUser(uid)Attendance
The device returns all attendance logs in one download. Per-user methods filter client-side.
// All records
const all = await zk.getAttendances();
// One user
const records = await zk.getUserAttendances("5011");
// With date range
const june = await zk.getUserAttendances("5011", {
from: new Date("2026-06-01"),
to: new Date("2026-06-30"),
});
// Real-time punches
await zk.getRealTimeLogs(({ userId, attTime }) => {
console.log(userId, attTime);
});Check-in / check-out
Each record includes punch and punchLabel:
{
userSn: 6850,
deviceUserId: "5011",
recordTime: Date,
punch: 1, // 0=out, 1=in, 2=break-out, 3=break-in
punchLabel: "check-in",
status: 1, // verify mode: fingerprint, card, face, …
statusLabel: "fingerprint",
}import { isCheckIn, isCheckOut, getPunchLabel } from "@graphland/zkteco";
records.filter((r) => isCheckIn(r.punch!));
records.filter((r) => isCheckOut(r.punch!));Note: Punch values depend on device function-key configuration. If the device doesn't use in/out keys,
punchmay always be0.
| punch | Default label |
|---------|---------------|
| 0 | check-out |
| 1 | check-in |
| 2 | break-out |
| 3 | break-in |
| 4 | overtime-in |
| 5 | overtime-out |
Device operations
await zk.getTime();
await zk.setTime(new Date());
await zk.openDoor(5); // unlock door for 5 seconds
await zk.disableDevice();
await zk.enableDevice();
await zk.refreshData();
await zk.clearAttendanceLog();
await zk.resetDevice(); // wipes users, fingerprints, logs, and settingsError handling
import { ZkError, ZkNotFoundError, ZkConnectionError } from "@graphland/zkteco";
try {
await zk.connect();
await zk.getUserById("missing");
} catch (err) {
if (err instanceof ZkConnectionError) {
// Wrong port, CommKey, or unreachable device
console.error(err.message, err.port);
} else if (err instanceof ZkNotFoundError) {
console.error(`${err.resource} ${err.id} not found`);
} else if (err instanceof ZkError) {
console.error(err.toast());
}
}| Error | When |
|-------|------|
| ZkConnectionError | Connect fails (wrong port, CommKey, timeout) |
| ZkNotFoundError | getUserById, updateUser, deleteUser — user missing |
| ZkError | Any other device/command failure |
Low-level API
import { COMMANDS } from "@graphland/zkteco";
const raw = await zk.executeCmd(COMMANDS.CMD_GET_VERSION, "");Contributing
Development happens in the zk-client-app monorepo (this package lives in packages/zkteco). Requires Bun:
git clone https://github.com/graphland-dev/zk-client-app.git
cd zk-client-app
bun install
bun run test # unit tests — protocol, auth, search; no hardware required
bun run typecheckBug reports and feature requests: GitHub issues.
