Nuvaka › Developer docs › API reference

API reference

The app injects a global nuvaka object into every UI frame and background script (API v1). Every call returns a Promise; errors are Error objects with a code field.

Contents: basics · storage · shared · net · clipboard · files · notes · cloud · pool · friends · mail · connections · push · ui · events · errors · SDK package

Basics

MemberReturnsDescription
nuvaka.apiVersion1
nuvaka.backgroundbooleantrue in the background (QuickJS), false in the UI
nuvaka.readyPromise<ctx | null>in the UI resolves with { page, locale, theme } once the shell connects; in the background resolves immediately with null
nuvaka.context()ctx | nulllatest context (updated when page, language or theme change)
nuvaka.info(){ id, version, trust, dev, grants, appVersion }trust: community, verified, or dev in developer mode (version is dev too); grants: granted scopes, e.g. { "files.read": true }
nuvaka.me(){ displayName, language, avatarUrl? }no email or user ID; server call
nuvaka.settings.get(){ [key]: value }values of the manifest settings (default when the user has not set one)
nuvaka.on(name, fn)unsubscribe functionsee events
nuvaka.off(name, fn)
nuvaka.call(method, arg)raw call; use the namespaces instead

nuvaka.storage

The extension's own key-value storage (server, cross-device). No permission needed. Keys are 1–200 characters, / allowed; values are JSON up to 1 MB. Every write carries the version it read (MVCC).

CallReturns
get(key){ value, version } or null if missing
set(key, value, { version }){ version } (the new version). Use version: 0 for a new key.
delete(key, { version })deleting also requires the version read
update(key, fn){ value, version }; fn(oldValue | undefined) returns the new value (or a Promise). Returning undefined writes nothing and returns what was read (or null). Up to 5 attempts on conflict.
list(prefix?, after?){ items: [{ key, version, size, updatedAt }], next } — no values; pass next as after to continue
onChanged(fn)unsubscribe function. fn(key, { value, version, deleted }); key === null means all of the extension's data was deleted. Only newer versions arrive; this context's own writes do not.

Errors: if_version_required (no version given), version_conflict (e.data = { current, value }), quota_exceeded.

// Paged listing
let after = ''
do {
  const page = await nuvaka.storage.list('summary/', after)
  for (const it of page.items) console.log(it.key, it.version, it.size)
  after = page.next
} while (after)

nuvaka.shared

Named values shared between extensions. Writable names must be in the manifest's shared.exports, readable ones in shared.imports.

CallReturns
set(name, value, { version }){ version }; version: 0 on first write, value ≤ 1 MB
getOwn(name)the value this extension shares: { value, version } or null
get(from, name)another extension's value (value only) or null. The user must grant shared and the source extension must be installed.

nuvaka.net

Needs the net permission. Requests go through the app's proxy: https only, and only to the hosts in the manifest. The browser's fetch is blocked in frames by the CSP.

const r = await nuvaka.net.fetch('https://api.github.com/repos/tauri-apps/tauri', {
  method: 'GET',                                   // default GET
  headers: { Accept: 'application/vnd.github+json' },
  // body: a string or Uint8Array/ArrayBuffer
})
r.status; r.ok; r.url; r.headers['content-type']   // header names are lower case
const data = await r.json()                         // or r.text(), r.arrayBuffer()
  • Request and response bodies up to 10 MB; connect timeout 10 s, total 30 s.
  • At most 5 redirects, only to allowed hosts; Authorization and Cookie headers are dropped on redirect.
  • These headers are not sent: host, content-length, connection, transfer-encoding, upgrade, proxy-authorization, te, trailer, keep-alive. set-cookie is not passed back. Default User-Agent: NuvakaApps/1.
  • Names resolving to private network, loopback or link-local addresses are rejected.
  • Errors: permission_required (no net permission), net_error (host not allowed, http, timeout, size…). HTTP 4xx/5xx is not an error; check r.ok.

nuvaka.clipboard

This device's system clipboard. For the cross-device clipboard pool see nuvaka.pool.

CallPermissionReturns
readText()clipboard: readtext or null
writeText(text)clipboard: writenull; text ≤ 1 MB

nuvaka.files

Folders the user opened to the extension on this device. root is the absolute path returned by pick() or folders(); path is relative to it ("notes/a.txt"). .., absolute paths and escaping the root through symbolic links are rejected.

CallPermissionReturns
folders()readstring[] — folders opened on this device
pick()readopens a folder picker; the chosen absolute path, or null if cancelled
list(root, path?)read[{ name, dir, size, mtime }] (mtime in ms or null; symbolic links are skipped; at most 5000 entries)
readText(root, path)readtext (UTF-8; invalid bytes become U+FFFD)
readBytes(root, path)readUint8Array
writeText(root, path, text)writenull; missing parent folders are created
writeBytes(root, path, bytes)writenull
mkdir(root, path)writenull
remove(root, path)writenull; a folder is removed only if empty

The largest readable file is 50 MB. The picked folder itself cannot be modified or removed. Errors: forbidden (folder not opened, or path outside the root), bad_request (invalid path), io (file system error), permission_required.

nuvaka.notes

CallPermissionReturns
list({ q?, folder? })readarray [{ id, title, content, color, isPinned, createdAt, updatedAt, reminderCount }]; with folder only that folder's notes, and items include folderId. q searches title/content and is filtered on the client (in the SDK).
get(id)read{ note: { id, title, content, color, isPinned, folderId, createdAt, updatedAt }, images: [{ id, fileName, fileSize }], reminders: [{ id, reminderType, reminderMessage, remindAt, intervalMinutes, maxReminders, sentCount, isActive }] }
create({ title, content, color? })writethe created note
update(id, { title?, content?, color?, isPinned? })writethe updated note
delete(id)write{ success: true }

Dates are ISO 8601 strings. Note content may be HTML; use textContent when rendering it.

nuvaka.cloud

The user's Nuvaka cloud files; only the folders (or all) the user opened to the extension at install.

CallPermissionReturns
roots()read{ roots: "all" | [{ id, name }] }
list(folderId?)read{ items: [{ type: "folder"|"file", id, name, size, contentType, fileId, createdAt, isFrozen, frozenReason, frozenUntil }], breadcrumb: [folders], currentFolderId }. Without folderId, the root (only when roots: "all").
read(fileId)readUint8Array (the file item's fileId)
upload(folderId, name, bytes, contentType?)write critical{ success, file: { id, fileId, fileName, fileSize, contentType, createdAt } }; up to 100 MB
delete(fileId)write critical{ success: true }

Folders or files outside the scope are rejected with out_of_scope. Writing and deleting are confirmed by the user every time.

nuvaka.pool

The account's cross-device clipboard pool (nuvaka.clipboard permission).

CallPermissionReturns
list({ before?, limit? })read{ items: [{ id, deviceId, deviceName, kind, mime, fileName, size, hash, createdAt, text, textTruncated, blocks }] } — newest first; limit 1–200 (default 50); before returns items older than this ID
add(text)writethe added item (same shape; text up to 4096 characters, longer text is cut and textTruncated: true)

Adding to the pool is done on behalf of this device; it fails if the device is not registered and active in sync.

nuvaka.friends

list() → { items: [{ username, since }] }. Permission: nuvaka.friends: read.

nuvaka.mail

Account based: call accounts() first. nuvaka.mail.read and .send are critical and confirmed on every use.

CallPermissionReturns
accounts()read or send{ items: [{ id, email, type, isNuvaka }] } (no passwords)
folders(accountId)readarray [{ name, fullName, unreadCount, totalCount, isInbox, isSent, isDrafts, isTrash, isSpam, children: [same shape] }]
messages(accountId, { folder?, page?, pageSize? })read{ messages: [{ id, uniqueId, from: { name, email }, to: [{ name, email }], subject, date, isRead, hasAttachments, preview, messageId }], totalCount, page, pageSize, unreadCount }; folder defaults to INBOX, pageSize 1–100
get(accountId, folder, uniqueId)read{ id, uniqueId, from, to, cc, bcc, subject, date, isRead, hasAttachments, textBody, htmlBody, attachments: [{ id, fileName, size, contentType }] }
send(accountId, { to, cc?, bcc?, subject, body, isHtml?, replyToMessageId? })send{ success: true }. to/cc/bcc as a comma-separated string or an array. 30 per hour per extension; every mail sent lands in the user's notifications.

nuvaka.connections

CallPermissionReturns
list()list or secrets{ items: [{ id, type, name, details: { host, port, username, database … }, createdAt }] } — password/key fields stripped
secret(id)secrets critical{ id, type, name, data: { … raw fields including password / privateKey } }; confirmed on every call and added to the user's notification feed

nuvaka.push

nuvaka.push(title, body?) → { success: true }. A notification to all of the user's devices; nuvaka.push permission. title is required. Subject to the notification limit; notify_limit if the user does not raise it.

nuvaka.ui

CallReturns
notify(title, body?)null. A local notification on this device; notifications permission; notification limit (notify_limit). Works in the background too.
theme(){ dark, vars } — in the UI
page()the open page's id
locale()the app language, e.g. en
onTheme(fn)when the theme changes; unsubscribe function
onPage(fn)when the user switches to another page from the menu; unsubscribe function

Events

Listen with nuvaka.on(name, fn). The background receives the events listed in the manifest's triggers; server-originated events are not delivered without the matching read permission.

EventWherePayload
schedulebackground{ schedule, at } — at in unix seconds
app.started, app.focused, network.onlinebackgroundnull
notes.changed, cloud.fileAdded, mail.received, clipboard.addedbackgrounda change notice (scope, method, path …); no data, read again through the API
data.changedboth{ kind, name, version, deleted } — raw event; prefer storage.onChanged
theme, page, localeUInew theme / page ID / language
// background.js
nuvaka.on('schedule', async ({ schedule, at }) => {
  console.log('fired', schedule, new Date(at * 1000).toISOString())
})
nuvaka.on('notes.changed', () => refreshIndex())   // manifest: { "event": "notes.changed" } + nuvaka.notes: read

A Promise returned by a handler is awaited; an error thrown in a handler is written to the app log and does not stop the other handlers.

Errors

Every error is an Error: name: "NuvakaError", code, message (Turkish), and when relevant permission (the missing scope) and data.

CodeMeaning
permission_requiredno permission, or it was revoked; e.permission is the missing scope
permission_deniedcritical confirmation denied or timed out
version_conflictthe version read is stale; e.data = { current, value }
if_version_requiredset/delete called without a version
quota_exceededstorage quota full
notify_limitnotification limit reached and the user did not raise it
rate_limitedrate limit (300 calls per minute, 30 mails per hour)
confirmation_floodmore than 3 pending confirmations
out_of_scopecloud folder not opened to the extension
not_foundrecord not found
forbidden, bad_request, iolocal file errors
net_error, networknet.fetch proxy error / server unreachable
not_installed, disabled, blockedextension not installed, stopped or blocked
session_revokedthe user's session was revoked
unknown_methodunknown call

Codes not listed here can also appear; show unknown codes as a generic error.

SDK package (@nuvaka/extension-sdk)

Optional. It contains no transport code: it types the global nuvaka, turns errors into classes and gives you a mock nuvaka for tests.

npm i -D @nuvaka/extension-sdk
import { nuvaka, PermissionError, VersionConflictError, NotifyLimitError } from '@nuvaka/extension-sdk'
import type { Manifest } from '@nuvaka/extension-sdk'

try {
  await nuvaka.push('Hello')
} catch (e) {
  if (e instanceof NotifyLimitError) { /* try again later */ }
}
ClassCode
NuvakaErrorbase class (any code)
PermissionErrorpermission_required
ConfirmationDeniedErrorpermission_denied (subclass of PermissionError)
VersionConflictErrorversion_conflict (current, value)
QuotaErrorquota_exceeded
NotifyLimitErrornotify_limit
RateLimitErrorrate_limited, confirmation_flood

The package's nuvaka throws no_runtime when called outside the app (plain browser, Node). Code that uses the global without importing can get types only with import type {} from '@nuvaka/extension-sdk/global'.

Testing: mock nuvaka

import { installMockNuvaka, uninstallMockNuvaka } from '@nuvaka/extension-sdk/testing'

const m = installMockNuvaka({
  grants: ['notifications', 'net'],             // flat scope names
  storage: { count: 1 },
  settings: { greeting: 'Hello' },
  notifyPerMinute: 3,
  fetch: (req) => ({ status: 200, body: { ok: true } }),
})
await nuvaka.storage.update('count', (n) => n + 1)
await m.emit('schedule', { schedule: 'every 15m', at: 1790000000 })
m.advance(60_000)                               // fake clock (notification window)
uninstallMockNuvaka()

The mock emulates permission checks, critical confirmation (confirm), MVCC, storage.onChanged, the notification limit and the quota. nuvaka-ext test runs vitest in the extension folder.

Nuvaka Apps API v1 · last updated 2026-09-27