Skip to main content

Connector reference

This page defines the connector contract: manifest fields, permissions, configuration schema, SDK lifecycle, document fields, state, local validation, and publishing. For a guided introduction, start with Build a connector.

Manifest​

manifest.json declares the connector to Gety. Gety reads the manifest when the user installs the connector and keeps that copy. To change the manifest of an installed connector, reinstall it from its local folder, or publish a new version.

FieldRequiredDescription
idYesStable connector ID. Must match ^[a-z0-9][a-z0-9_-]*$: start with a lowercase letter or digit, then use lowercase letters, digits, _, or -. Do not use uppercase letters, dots, or a leading _ or -. Gety identifies an installed connector by its ID, so never change it after you publish.
nameYesDisplay name shown in Gety's connector list and install dialog.
versionYesSemVer version of the connector.
min_gety_app_versionYesMinimum Gety app version, as SemVer. Older Gety versions refuse to install the connector. Set 0.7.0 or later when the manifest uses permissions or sync_version.
entryYesRelative path to the built entry inside the connector root, usually dist/main.js.
capabilitiesYesNon-empty array of host integration points. A polling data source declares ["data-source"], currently the only supported value. Unknown values are rejected.
descriptionNoShort description shown in the install dialog.
authorNoAuthor object with optional name, email, and url, for example { "name": "Gety" }.
iconNoRelative path to a PNG or SVG in the connector root. Gety shows it in the connector list and install dialog.
scheduleNoSync schedule. If omitted, Gety syncs every 600 seconds. See Schedule.
configNoConfiguration form schema. See Config fields.
doc_linkNoDeclares how documents open. See Doc link.
schema.extra_gety_indexed_fieldsNoMakes extra metadata keys searchable. See Indexed fields.
permissionsNoWhat the connector may do. An object runs the connector in a sandbox; omitted or "unrestricted" runs it with full access. Can't be null. See Permissions.
sync_versionNoPositive integer, 1 if omitted. Raise it when a new version can't continue from what an older version synced. See Versions and updates.

entry and icon must stay inside the connector root. Unknown manifest fields are rejected.

Schedule​

schedule controls how Gety polls the source. Users can change it after installation.

ShapeBehavior
{ "strategy": "manual" }Gety doesn't poll automatically. Users can still sync with More actions (⋯) → Sync now.
{ "strategy": "interval", "interval_seconds": 3600 }Poll every interval_seconds. Values below 60 are clamped to 60.
{ "strategy": "daily", "time": "09:00" }Poll once per day at time, using local time. HH:mm and HH:mm:ss are supported.

Config fields​

config.fields is an ordered array. Each field renders an input in Gety's install and configuration UI, and becomes a typed property on this.config.

PropertyRequiredDescription
idYesField key. It becomes a property on the generated ManifestConfig type.
titleYesLabel shown in the UI.
typeYesInput type. One of text, password, number, checkbox, dropdown, or directory.
descriptionNoHelper text shown under the field.
placeholderNoPlaceholder text for text-like inputs.
requiredNoWhether the field must be filled in. Defaults to false.
default_valueNoPre-filled value when the field is empty. The value must match the field type; a number field's default must be an integer, so 0.5 is rejected.
optionsFor dropdownOrdered array of { "value": "...", "label": "..." }.

Field types:

TypeUse for
textSingle-line text, such as a URL or username.
passwordAPI keys and tokens. Values are masked in the UI.
numberNumeric values.
checkboxBoolean on/off options.
dropdownA fixed choice from ordered options.
directoryA local folder path chosen with a picker.
Handling API keys

Use type: "password" for API keys and tokens. For local validation, the sample runner maps field IDs to environment variables. It uppercases the field path and tries the GETY_CONFIG_ prefix first, then the bare name:

  • api_key reads GETY_CONFIG_API_KEY first, then API_KEY.
  • auth.api_key reads GETY_CONFIG_AUTH_API_KEY first, then AUTH_API_KEY.

doc_link declares once, for the whole connector, how documents open.

{ "kind": "url", "field": "url" }
PropertyDescription
kindurl opens a browser link; file opens a local filesystem path.
fieldThe doc field or metadata key to read the link value from.

Resolution rules:

  • field is either a core doc field (id, parent_id, title, content, or doc_type) or a direct metadata key.
  • "field": "url" reads metadata.url. Use the direct key, not a dot path like "metadata.url".
  • Missing, empty, or non-string values mean the document has no link.
  • url links open in the browser. Gety doesn't fetch their content.
  • file links must be stable local paths, not temporary cache files.

Open, display, and copy actions come from doc_link, not from content_format. content_format only controls how Gety renders the text preview.

For the user's safety, Gety refuses to open a link that points to:

  • A file inside any connector's folders, including the connector's own data folder and code. Link to the source service or to the user's own files instead.
  • A file that doesn't exist.
  • A file: URL that names a host instead of a local path, such as file://server/share/doc.txt.
  • On Windows, a file that isn't on a local volume.

This applies to every link that resolves to a local file, whether kind is file or url. Open file directory on a file link isn't affected.

Indexed fields​

Gety always indexes title and content. schema.extra_gety_indexed_fields makes additional metadata keys searchable. Each entry needs both field and strategy:

{
"schema": {
"extra_gety_indexed_fields": [
{ "field": "status", "strategy": "fast_text" }
]
}
}
StrategyBest for
fast_textShort values that need exact, substring, fuzzy, or pinyin matching, such as names, tags, folders, or statuses.
full_textLonger text that benefits from SQLite FTS5 keyword search, BM25 ranking, and tokenization.
semanticMeaningful prose that should match by vector similarity. Requires the embedding model.

Permissions​

permissions decides what the connector's process may do. Gety shows it to the user before every install, in the app and in gety connector install, and the user can view it later. Permissions require Gety 0.7.0 or later, so a manifest that declares them must set min_gety_app_version to 0.7.0 or later.

{
"min_gety_app_version": "0.7.0",
"permissions": {
"net": [
"api.notion.com",
"*.notion.so",
"files.example.com:8443",
{ "config_field": "base_url" }
],
"read": [
"~/.codex/sessions",
{
"path": "~/Library/Application Support/Google/Chrome",
"os": ["macos"]
},
{ "config_field": "export_dir" }
],
"env": ["ACME_LOG_LEVEL"],
"run": ["git"]
}
}

Modes​

permissionsMode
An object, including {}Sandbox. The connector gets base access plus exactly what it lists. Everything else is denied.
Omitted, or "unrestricted"Full access. The connector runs without a sandbox and can do anything the user can. Gety shows a red warning and asks the user to confirm a second time.

Entries​

KeyEntry
net"*" (any host); host or host:port; *.domain (the domain and its subdomains, no port); an IPv4 address or [IPv6] address, optionally with a port. Hosts are lowercase ASCII; write internationalized names in punycode. Write a host, not a URL.
read"~" or "~/dir/…", granting that folder and everything below it. { "path": "~/…", "os": ["macos", "windows", "linux"] } applies only on the listed systems. Absolute paths aren't accepted.
envEnvironment variable names the connector may read.
runProgram names, without paths, that the connector may start. See Running programs.
{ "config_field": "<id>" }Takes the grant from a config field: a text field in net (a URL or host[:port]), a directory field in read. An empty value grants nothing. Password fields can't be referenced.

Gety checks values taken from config fields when the user installs the connector or saves its configuration: a net value must be a URL or host[:port] without wildcards, and only its host and an explicit port are granted; a read value must be an absolute path. When the user changes the field, the grant follows the new value without another review.

~ means the home folder Gety passes in. Use this.homeDir and this.expandHome(path) rather than reading HOME from the environment, which needs an env grant and may not match the folder Gety grants.

env grants reading a variable, not access to what it points to. If a location can move, such as a tool that honors its own *_HOME variable, let the user pick it in a directory config field and grant it with { "config_field": "<id>" } in read.

Any of these makes a connector high-risk, and the user confirms the install a second time:

  • "*" or a single-label wildcard such as *.com in net
  • Any run entry
  • "~" in read

Running programs​

A program the connector runs isn't limited by the sandbox, so a run entry is equivalent to full access.

Gety resolves each run name through PATH when the connector starts, and only that exact path may be started; the same program at another path, or through its resolved symlink target, is denied. A name not found on PATH grants nothing. On macOS, Gety appends /opt/homebrew/bin and /usr/local/bin to PATH when missing, because an app started from Finder gets only a minimal PATH. Folders that shells add at startup, such as nvm's or Volta's, aren't on that PATH, so programs installed with them aren't found. Ask users to link the program into one of the folders above, or run the connector with full access.

Base access​

Every sandboxed connector can, without declaring it:

  • Read and write its data folder, this.dataDir, which is also the working directory and keeps its contents between syncs and across versions.
  • Read and write a temporary folder (TMPDIR, TMP, TEMP) that is deleted after each sync.
  • Read basic system information: the home folder, CPU count, memory, OS release, uptime, load average, and disk usage.
  • Read its own code.

Each connector also gets its own localStorage, Cache API, and module cache, not shared with other connectors. The permission review doesn't list base access.

Writing outside these folders, ffi, the hostname, network interfaces, and user or group IDs are never available in the sandbox. A connector that needs them has to run with full access.

Permission errors​

When the sandbox denies an access, the sync fails with a stable message, for example:

permission denied: net access to "api.example.com:443" is not granted by the connector's manifest

write, sys, ffi, and import accesses report is not available to sandboxed connectors instead, because no manifest entry grants them. Add the missing entry to permissions, or remove the access from the code.

  • Every redirect hop of a fetch is checked, so declare redirect targets and CDN hosts too.
  • Node-compatible APIs such as node:http report a denied host as getaddrinfo EPERM <host> without naming the permission. Prefer fetch.
  • Reading an environment variable that isn't granted doesn't fail: Deno.env.get returns undefined, as if it were unset.

Limitations​

  • Sandboxed connectors can't import remote (https:) or npm: modules at run time. Bundle dependencies into the connector folder.
  • Files the connector writes can't be opened through document links. See Doc link.

SDK lifecycle​

A connector is a default-exported class extending Connector<Config, State>.

import { Connector, type PollResult, del, upsert } from '@gety-ai/connector-sdk';
import type { ManifestConfig } from './gen/manifest.d.ts';

type State = {
cursor?: string;
};

export default class MyConnector extends Connector<ManifestConfig, State> {
async onLoad(): Promise<void> {
// Optional. Runs once in a fresh runtime process before poll().
}

async *poll(): AsyncGenerator<PollResult, void, unknown> {
let cursor = this.lastState?.cursor;

for await (const page of fetchPages(this.config.api_key, cursor, this.signal)) {
yield {
updates: page.items.map((item) => upsert(toDoc(item))),
state: { cursor: page.next_cursor },
};
cursor = page.next_cursor;
}
}
}
MemberDescription
this.configTyped configuration injected from Gety's install or configuration form, shaped by config.fields.
this.lastStateThe last state yielded by the last successful poll, or null on the first run.
this.signalAbortSignal that aborts when Gety stops the poll, for example when the user disables or restarts the connector. Pass it to fetches.
this.dataDirAbsolute path of the connector's data folder, which persists between polls.
this.homeDirAbsolute path of the user's home folder, as Gety resolves ~ in permissions.read.
this.expandHome(path)Resolves a leading ~ against this.homeDir. Other paths are returned unchanged.
onLoad()Optional, may be async. Runs once in a fresh runtime process before poll().
poll()Required async generator. Yields PollResult batches.

Lifecycle facts:

  • Each scheduled poll starts a fresh connector runtime process.
  • Class fields don't persist across polls. Use state, or files in this.dataDir, for durable data.

Runtime limits​

  • Idle timeout. A poll that sends nothing to Gety for 10 minutes is treated as hung and cancelled. Yielded batches and console output both count. During a long operation, yield a batch or log a low-frequency progress line such as console.info("still syncing..."). Avoid high-frequency logging.
  • Cancellation. When Gety stops a poll, this.signal aborts. Check it during long operations, pass it to fetch(), and release temporary files and other resources. Cancellation is cooperative first; if the connector hasn't exited 5 seconds later, Gety kills the process.
  • Message size. The SDK splits large batches into smaller messages. Each single document update must stay under 16 MiB.
  • State size. The next poll starts with the configuration and the state together, within 16 MiB. A yielded state that would exceed it fails the poll.

WireDoc fields​

Build documents with upsert(doc) and remove them with del(id).

upsert({
id: issue.id,
title: issue.title,
content: markdown,
content_format: 'markdown',
doc_type: 'linear:issue',
doc_updated_at: issue.updatedAt,
original_file_size: markdown.length,
metadata: {
url: issue.url,
created_at: issue.createdAt,
status: issue.status,
},
});
FieldRequiredDescription
idYesStable document ID, unchanged across polls.
titleYesDocument title. Always indexed.
contentNoText Gety indexes and previews.
content_formatNoplaintext (default) or markdown. markdown enables Markdown rendering in the text preview. It controls rendering, not source type or how the document opens. Use markdown only for real Markdown.
doc_typeNoUsually <namespace>:<type>, such as linear:issue, for external records. Use file:<ext> only when the document is genuinely file-like and should use file-extension presentation. Don't label an external record as a file just to get Markdown preview; set the real record type and use content_format for rendering.
doc_updated_atNoRFC 3339 source update time with Z or an offset. Use the source document's update time, not the indexing time.
parent_idNoID of a parent document for hierarchical relationships.
hide_from_searchNoHides the document from search while preserving it for parent or relationship use.
original_file_sizeNoSize in bytes, used for display.
metadataNoArbitrary key/value map. metadata.url is the conventional link target. If present, metadata.created_at must be an RFC 3339 string or the poll fails. Extra keys become searchable through schema.extra_gety_indexed_fields.

Only id and title are required; other unset fields are dropped from the wire. Gety keeps the first 10 MB (UTF-8 bytes) of content and truncates the rest. Emit pages or chunks instead of huge individual documents or metadata blobs.

Poll and state​

poll() yields PollResult batches. Each batch carries updates and an optional state checkpoint.

yield {
updates: [
upsert(docA),
upsert(docB),
del(staleId),
],
state: { cursor: page.next_cursor },
};

Gety saves the last state a poll yields only after the whole poll succeeds; it becomes lastState in the next poll. If a poll fails or is cancelled, the next poll starts again from the state of the last successful poll. Gety may already have indexed part of the failed poll's updates, because it applies a batch in bounded units rather than as a single transaction.

Rules:

  • Make updates safe to replay. Running a poll again from the same state must end with the same documents. Derive updates from lastState and stable document IDs.
  • Checkpoint after updates. The state you yield with a batch should describe progress reached after that batch's updates.
  • Don't advance state early. Never yield state for documents you haven't yielded yet.
  • Emit deletes from real signals. Call del(id) only when the source proves the document disappeared or left scope.
  • Yield at resumable boundaries. For remote APIs, yield per page or durable cursor. For local enumerations, yield per file or chunk.
  • Handle timestamp cursors carefully. Keep enough state to avoid missing updates that share the same timestamp.
  • Keep state small. Store cursors, not data. Keep larger data in this.dataDir.
  • Page or stream the source. The SDK splits large batches into smaller messages, but your connector should still page or stream source data instead of holding everything in memory.

Validation​

Run the sample verification task before installing:

deno task verify

verify runs fix, check, test, and build:

  • fix runs formatting and lint fixes.
  • check generates the config type and type-checks the project.
  • test runs tests.
  • build writes the bundled entry loaded by Gety.

You can also run the steps individually:

deno task fix
deno task check
deno task test
deno task build

Exercise the real lifecycle with the local runner:

cp .env.example .env
deno task runner -- --reset-state
deno task runner -- --polls 2

The runner runs each poll the way Gety does: in a fresh Deno process, in Gety's sandbox with the manifest's permissions, or with full access when the manifest declares none. A denied access fails the poll with the same message Gety shows. --permissions '<json>' tries other permissions without editing the manifest, and --full-access runs without the sandbox. this.dataDir is dev/.runner/data/ and is kept between runs.

Like Gety, the runner saves state only when a poll succeeds, at dev/.runner/state.json. --reset-state ignores it for a clean first poll. Each run writes a snapshot under dev/runs/<timestamp>/:

dev/runs/<timestamp>/
summary.json
state.before.json
state.after.json
updates.json
deletes.json
docs/
0001-<doc_type>__<doc_id>.md
0001-<doc_type>__<doc_id>.json

The content file is .md for content_format: "markdown" and .txt otherwise. It is omitted when the document has no content. The .json sidecar is always written. Inspect the output for stable IDs, expected content, correct metadata.url, RFC 3339 timestamps, deletes, and before/after state behavior.

After editing src/index.ts, run deno task build, then click More actions (⋯) → Restart in Gety to load the new dist/main.js.

Publishing​

Users install published connectors from a GitHub release or an npm package. Gety never builds from source and never calls the GitHub API. It installs only the published files, records the SHA-256 of what it downloaded, and keeps its own copy of the code. For a walkthrough, see Publish your connector.

GitHub releases​

Publish one release per version. The tag is the manifest version, optionally prefixed with v (1.2.0 or v1.2.0). Each release carries two assets:

  • manifest.json, byte-for-byte identical to the manifest.json at the root of the code package. Before the user confirms, Gety downloads only this file and shows the permissions it declares.
  • <id>-<version>.zip, with id and version taken from the manifest. Its root is the connector folder: manifest.json, the entry, and every other file the connector needs at run time, such as its icon, source map, and data files, without a wrapping folder.

Code package limits: at most 100 MiB, at most 20,000 files and 500 MiB unpacked, no symbolic links, and every file stored or compressed with deflate.

When the user confirms, Gety downloads the code package and manifest.json again, and refuses to install unless the package's manifest.json is byte-for-byte identical to that asset and its id and version match the ones the user reviewed. Turn on immutable releases in the repository settings so a published tag and its assets can't change.

By default, Gety installs the release GitHub marks as Latest. Pre-releases install only through their release link (https://github.com/<owner>/<repo>/releases/tag/<tag>) or --version.

npm packages​

The package's package.json carries the whole manifest in a gety object:

{
"name": "@acme/gety-linear",
"version": "1.2.0",
"homepage": "https://github.com/acme/gety-linear#readme",
"files": ["dist/main.js", "icon.svg", "CHANGELOG.md"],
"gety": {
"id": "acme-linear",
"name": "Acme Linear",
"version": "1.2.0",
"min_gety_app_version": "0.7.0",
"entry": "dist/main.js",
"icon": "icon.svg",
"capabilities": ["data-source"],
"permissions": { "net": ["api.linear.app"] }
}
}
  • gety is the complete manifest, and gety.version must equal the package version, compared as SemVer including pre-release and build metadata.
  • files must include the entry and every other file the connector needs at run time. List CHANGELOG.md too: npm doesn't pack it on its own once files is set.
  • Prefix id with your own name (acme-linear, not linear): a connector with the same ID can't be installed next to yours.
  • A manifest.json in the package isn't read; only gety counts. When you publish to both GitHub and npm, keep manifest.json as the single source and generate the gety object from it.
  • Set homepage to a URL that explains the connector.
  • The package must be public. Gety installs the version the latest dist-tag points to, or the exact version the user names (@acme/gety-linear@1.3.0-beta.1, https://www.npmjs.com/package/@acme/gety-linear/v/1.3.0-beta.1, or --version). Other dist-tags and version ranges aren't supported.

The package tarball follows the same limits as a GitHub code package: at most 100 MiB, at most 20,000 entries and 500 MiB unpacked, and only regular files and folders under package/, with no symbolic or hard links. The registry must list a sha512 integrity for it.

Before the user confirms, Gety shows the permissions declared in the gety object of the registry's metadata for that version. When the user confirms, Gety downloads the tarball, checks it against the registry's integrity, and refuses to install unless the gety object in the tarball's package.json equals the one the user reviewed. npm never lets a published version change, so changing permissions means publishing a new version.

When the system region is mainland China, Gety reads the package from npmmirror first and falls back to registry.npmjs.org. The mirror usually syncs a new version within a few minutes.

Versions and updates​

Gety checks the registry for newer versions of connectors installed from npm and lets users switch to any published version, newer or older, in the app or with gety connector update. A version is offered as an update when it is higher than the installed one, isn't a pre-release, isn't deprecated, and runs on the user's Gety. The latest dist-tag plays no part in this. Connectors installed from GitHub or a local folder have no version management.

Raising min_gety_app_version doesn't strand users on older Gety versions: they see that the version needs a newer Gety, and can install an older one.

Release notes. Gety shows the CHANGELOG.md at the root of the package, split into sections by version headings. ## 1.2.0, ## v1.2.0, and ## [1.2.0] - 2026-07-14 all work, and headings inside code blocks are ignored. Use the same heading level for every version. A section ends at the next heading of the same or a higher level; a heading there that isn't a version, such as ## Unreleased, isn't shown, nor is its content. A --- line after a blank line counts as such a heading, so text after it is cut. Keep the sections of older versions in the file. Changesets writes this format. Only the first 1 MiB is shown.

sync_version. A whole number, 1 when absent. It's the format version of what the connector has synced: its state, its document IDs, and the content of its documents. Raise it in a release that can't continue from what an older release synced, such as when the state changes shape or document IDs change. Gety then deletes the connector's documents, clears its sync state, and syncs from scratch on upgrade. Changing extra_gety_indexed_fields does the same without raising sync_version.

Downgrades always delete the connector's documents and sync from scratch: an older release can't be expected to read the state a newer one wrote.

Withdrawing a version. Published versions can't change. To withdraw a broken one, deprecate it:

npm deprecate @acme/gety-linear@1.2.0 "Loses comments; use 1.2.1"

Gety never offers a deprecated version as an update. Users still see it, marked as deprecated with your message, and can choose it explicitly. Pointing latest back at an older version doesn't withdraw a newer one.

What carries over. The connector's data folder (this.dataDir) and its localStorage are kept across versions; the sync state is cleared only when the switch resyncs. The user's settings carry over field by field: a field that keeps its id and its type keeps the user's value, secrets included. A field whose type changes is reset to the new default, a field that's gone is removed, and a new field starts at its default. When a required field ends up empty, or a value isn't among the new options, the user fills it in before switching. A switch also asks the user to review any permissions the new version adds.

Connectors published on npm can be listed in the Community tab of Featured connectors in Gety by registering them in gety-ai/gety-connectors. See Publish to the featured list and the contributing guide.