Skip to main content

Build a connector

A connector indexes content from a source Gety doesn't cover by default. This guide takes you from the sample project to a connector installed in Gety, then shows how to publish it so others can install it. For the full field and API reference, see Connector reference.

Know the current scope​

A connector is a Deno and TypeScript program that polls a source and yields documents for Gety to index.

It can:

  • Pull data from a stable source it can access, such as a web API, local enumeration, or internal service
  • Index text content with stable IDs, titles, and metadata
  • Use persisted state to track incremental progress and add, update, or delete documents
  • Declare the hosts, folders, environment variables, and programs it needs, so Gety can run it in a sandbox

It can't:

  • Add its own UI to Gety beyond manifest-declared configuration fields
  • Fetch, extract, or cache remote files on demand when a user opens a result
  • Use a per-document dynamic link rule; document links are declared once in the manifest

If the source only exposes files that Gety would need to fetch and extract later, put the text your connector already has into content and store a stable URL or file path in metadata.

For example, Notion is a good connector fit: use the Notion API as the stable source, convert Notion blocks to Markdown with a library such as souvikinator/notion-to-md, then send that Markdown as the indexed content in Gety.

A media player is usually not a good connector fit. It is mainly an interactive UI, not a stable text source. Connectors also work poorly for sources that don't expose a reliable API, stable local files, or durable IDs for incremental sync.

Prerequisites​

Before you start, install:

  • Git to clone the sample connector.
  • Deno to build, test, and run the connector.
  • Gety 0.7.0 or later to install and test the connector.

To publish on npm, you also need Node.js and an npm account.

Create a connector with an AI agent​

If your AI coding tool supports Skills, use Gety's connector skill first. It gives the agent the connector contract, sample project workflow, and verification steps.

npx skills add gety-ai/gety-skills --skills create-gety-connector

Then tell the agent what you want to index:

Use the create-gety-connector skill to create a Gety connector for [source].

Ask me for any missing details before you start coding.

If your AI coding tool doesn't support Skills, paste this into the chat instead:

Create a Gety connector for [source]. Use https://docs.gety.ai/build-a-connector.md as the implementation workflow and https://docs.gety.ai/connector-reference.md as the full connector contract and field reference. Before writing code, ask me to clarify any missing details.

Don't paste API keys or other secrets into a chat. Put secrets in connector configuration fields or local .env files.

If you're building by hand, follow the steps below.

Start from the sample​

Clone the sample connector. Don't build the skeleton from scratch; the sample includes the build script, generated config types, local runner, packaging, and verification tasks.

git clone https://github.com/gety-ai/gety-sample-connector my-connector
cd my-connector

Review the project structure​

manifest.json # Connector metadata, configuration schema, and permissions
package.json # npm package metadata; its gety field is generated from manifest.json
CHANGELOG.md # Release notes Gety shows when users switch versions
src/index.ts # Connector implementation
src/index.test.ts # Unit tests
src/gen/manifest.d.ts # Generated config type; do not edit
dist/main.js # Built entry loaded by Gety
deno.json # Tasks: build, check, test, verify, runner, package, sync
dev/runner.ts # Local runner that runs the connector the way Gety does
scripts/build.ts # esbuild bundler
scripts/package.ts # GitHub Release packaging
vendor/gety-deno/ # Copy of Gety's SDK and connector runner; do not edit

dist/main.js is the entry Gety loads. After editing src/index.ts, run deno task build; changing the source file alone doesn't update the built connector.

Plan the document contract first​

Before writing code, decide how the source maps to Gety documents. Settle these up front:

  • The source and its authentication method.
  • A stable document ID that survives across polls.
  • How each record maps to title, content, and metadata.
  • How deleted or archived records are detected and removed.
  • What link opens when a user selects a search result.
  • Which hosts, folders, environment variables, or programs the connector needs.

Write a minimal manifest​

manifest.json tells Gety how to install and run the connector:

{
"id": "acme_my_connector",
"name": "My Connector",
"version": "0.1.0",
"min_gety_app_version": "0.7.0",
"capabilities": ["data-source"],
"entry": "dist/main.js",
"description": "Indexes documents from My Connector.",
"schedule": { "strategy": "interval", "interval_seconds": 3600 },
"doc_link": { "kind": "url", "field": "url" },
"permissions": { "net": ["api.example.com"] },
"config": {
"fields": [
{
"id": "api_key",
"title": "API Key",
"type": "password",
"required": true,
"description": "Create this token in the service settings."
}
]
}
}

Gety identifies an installed connector by its id. Prefix it with your own name, so it doesn't collide with another author's connector, and settle on it before you publish. See Keep the connector ID stable.

See Connector reference for every manifest field.

Declare permissions​

permissions decides what the connector may do, and Gety shows it to the user for review before every install. With permissions set, Gety runs the connector in a sandbox: it gets the access you declare plus a small base access, such as its own data folder, and everything else is denied.

"permissions": {
"net": ["api.example.com"],
"read": ["~/Documents/notes"]
}
  • net lists hosts the connector may reach. Declare every host a request touches, including redirect targets and CDNs.
  • read lists folders under the user's home folder that the connector may read.
  • env lists environment variables it may read, and run lists programs it may run.
  • To follow a setting the user enters, such as a server URL or a folder, use { "config_field": "<field id>" } instead of a fixed value.
  • A connector that needs nothing beyond base access declares "permissions": {}.

Declare only what the connector needs. Broad grants, such as any host ("*"), the whole home folder ("~"), or any program in run, make the user confirm a second time. A manifest without permissions runs with full access to everything the user can access, and Gety warns the user in red.

The sandbox needs Gety 0.7.0 or later, so set min_gety_app_version to 0.7.0 or later. For every entry format, see Permissions.

Implement src/index.ts​

Your connector is a class that extends Connector<Config, State>. Implement poll() as an async generator that yields batches of document updates and a state checkpoint.

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 *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({
id: item.id,
title: item.title,
content: item.body,
content_format: 'markdown',
doc_type: 'my:item',
doc_updated_at: item.updatedAt,
metadata: { url: item.url },
}),
),
...page.removed.map((id) => del(id)),
],
state: { cursor: page.next_cursor },
};
cursor = page.next_cursor;
}
}
}

Key points:

  • upsert(doc) adds or updates a document. Only id and title are required; id must stay stable across polls.
  • del(id) removes a document when the source proves it's gone.
  • this.config contains the values from your manifest configuration fields.
  • this.lastState contains the last state yielded by the last successful poll, or null on the first run.
  • this.signal aborts when Gety stops the poll, for example when the user disables or restarts the connector. Pass it to fetches when possible.
  • this.dataDir is the connector's own data folder, which persists across polls.

Manage state and batches​

Yield a state checkpoint with each batch, after the updates it covers. Gety saves the last state you yielded only when the whole poll finishes successfully, and passes it to the next poll as this.lastState. If a poll fails or is cancelled partway, Gety keeps the state of the last successful poll, so the next poll repeats the work from there.

Gety may already have indexed part of a failed poll, and it applies a large batch in several parts rather than all at once. Make your updates safe to replay: running a poll again from the same state must end with the same documents. Stable document IDs and upserts give you this for free.

Don't advance state before yielding the documents it covers. Yield at a boundary the source can resume from, such as an API page or a durable cursor.

Follow the runtime rules​

Each poll runs in a fresh process, and Gety watches it:

  • Show progress. If a poll sends nothing to Gety for 10 minutes, Gety treats it as hung and stops it. Yielding a batch or writing a log line counts as progress. During a long step that yields nothing, such as a large download, log a short line with console.info() now and then.
  • Stop when asked. When Gety stops a poll, this.signal aborts. Pass it to fetch() and other long calls, and check this.signal.aborted between units of work. If the connector hasn't exited 5 seconds later, Gety ends the process.
  • Keep state small. Gety passes the state back, together with the configuration, when the next poll starts. If they don't fit within 16 MiB, the poll fails. Keep large data in this.dataDir instead.

The SDK splits large batches into smaller messages for you. Each single document update must stay under 16 MiB, and Gety keeps only the first 10 MB of a document's content.

Avoid these patterns​

These mistakes can cause memory pressure, broken links, or failed polls:

  • Loading all remote data into memory at once. Prefer paging or streaming the source instead.
  • Using temporary URLs in doc_link. Links must stay stable after the poll ends.
  • Linking documents to files the connector writes. Gety doesn't open files inside connector folders; link to the source service or to the user's own files.
  • Putting large blobs in metadata. Keep metadata small and index text through content.
  • Reading this.config in the constructor. Configuration is available in lifecycle methods such as onLoad() and poll(), after Gety injects it.
  • Importing modules from URLs or npm at run time. The sandbox blocks it; bundle every dependency into dist/main.js.

Verify locally​

Run the sample verification task before installing the connector:

deno task verify

verify runs formatting, lint fixes, config type generation, type-checking, tests, and the build. Then exercise the connector lifecycle with the local runner:

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

The runner runs the connector the way Gety does: each poll is a fresh process in Gety's sandbox, with the permissions from your manifest. Access the manifest doesn't grant fails the poll with the same message Gety shows, for example permission denied: net access to "example.com:443" is not granted by the connector's manifest.

The runner writes each run's output under dev/runs/<timestamp>/: documents, updates, deletes, and state snapshots. Check for stable IDs, expected content, correct metadata, and RFC 3339 timestamps. Like Gety, the runner saves state only when a poll succeeds. --reset-state starts from an empty state so you can test a clean first poll. To try other permissions without editing the manifest, pass --permissions '<json>'. Run deno task runner -- --help for every option.

If your .env needs secrets such as an API key, edit the file yourself. Don't paste secrets into a chat with an agent.

Install in Gety​

  1. Run deno task build to refresh dist/main.js.
  2. Open Gety → Settings → Connectors and click Install from local folder.
  3. Select your connector folder.
  4. Review the permissions and click Allow.
  5. If the connector has configuration fields, fill them in and click Install.
  6. Let Gety run the connector according to its sync schedule. For a manual connector, click More actions (⋯) → Sync now.

You can also install from a terminal with gety connector install ./my-connector. See CLI.

After later code edits, run deno task build again and click More actions (⋯) → Restart in Gety to load the new build. Gety reads manifest.json only when you install, so after you change the manifest, such as its permissions, click More actions (⋯) → Delete and install the connector again. To test a fresh sync without reinstalling, click More actions (⋯) → Rebuild index.

Publish your connector​

Users can install a published connector from Install from remote in Gety or with the CLI. Publish on npm, on GitHub, or both:

ChannelUsers install it withUpdates
npmInstall from remote → npm, Featured connectors, or gety connector install --npm <package>Gety checks for new versions and lets users switch versions.
GitHub releaseInstall from remote → GitHub, or gety connector install --github <owner>/<repo>Users delete the connector and install the new release.

Only npm connectors get version management and can appear in the featured list, so prefer npm.

Gety never builds from your source. It installs only the published files, records their checksum, and keeps its own copy of the code. A release or package is limited to 100 MiB, and to 20,000 files and 500 MiB unpacked, with no symbolic links.

Keep the connector ID stable​

Gety identifies an installed connector by its manifest id. Never change the id of a published connector: Gety treats a new id as a different connector, so users would install it as a second connector and index everything again. Users who installed an older version from npm can't switch to the new version: Gety refuses a version whose id changed.

Publish on npm​

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

{
"name": "@acme/gety-my-connector",
"version": "0.1.0",
"description": "Indexes documents from My Connector.",
"homepage": "https://github.com/acme/gety-my-connector#readme",
"files": ["dist/main.js", "dist/main.js.map", "CHANGELOG.md"],
"gety": {
"id": "acme_my_connector",
"name": "My Connector",
"version": "0.1.0",
"min_gety_app_version": "0.7.0",
"capabilities": ["data-source"],
"entry": "dist/main.js",
"permissions": { "net": ["api.example.com"] }
}
}
  • gety is the complete manifest, and gety.version must equal the package version. Gety doesn't read a manifest.json in the package.
  • Keep manifest.json as the single source. In the sample, deno task sync writes the package version, description, and gety from manifest.json, and deno task verify runs it, so the two never disagree.
  • files must include the entry and every other file the connector needs at run time, such as its icon and source map. List CHANGELOG.md too; npm leaves it out once files is set.
  • Set homepage to a page that explains the connector, such as its README.
  • The package must be public.

To publish:

deno task verify
npm publish

In the sample, npm publish first runs deno task sync:check, which fails when package.json is out of date. To publish from CI instead, push a v* tag: the sample's .github/workflows/npm-publish.yml runs deno task verify and npm publish with npm trusted publishing, so the repository needs no npm token. npm accepts a trusted publisher only for a package that already exists, so publish the first version by hand, then add the trusted publisher in the package's settings on npmjs.com.

Gety installs the version the latest dist-tag points to, or the exact version a user names, such as @acme/gety-my-connector@0.2.0-beta.1. Other dist-tags and version ranges aren't supported. Publish a pre-release under another dist-tag, such as npm publish --tag next, so latest keeps pointing to a stable version. Users in mainland China download from a mirror first, which usually picks up a new version within a few minutes.

Publish a GitHub release​

Publish one GitHub release per version, tagged with the manifest version, optionally prefixed with v (v0.1.0 for 0.1.0). Each release carries two assets:

  • manifest.json, byte-for-byte identical to the manifest.json inside the zip. Gety downloads only this file to show the permissions before the user confirms.
  • <id>-<version>.zip, with id and version from the manifest. Its root is the connector folder: manifest.json, the entry, and every other file the connector needs, without a wrapping folder.

The sample builds and checks both assets:

deno task build
deno task package

deno task package writes the assets to release/. It packages manifest.json, the entry and its source map, and the icon; add other files with --include <path>, and pass --tag <tag> to check the tag against the manifest version. The sample's .github/workflows/release.yml publishes a release when you push a v* tag; the sample README shows how other repositories can reuse it.

Gety installs the release GitHub marks as Latest. Users install a pre-release only through its release link or --version. Turn on immutable releases in your repository settings so a published tag and its assets can't change.

Release a new version​

  1. Raise version in manifest.json, and raise min_gety_app_version if the new version needs a newer Gety.
  2. Add a section for the new version to CHANGELOG.md, such as ## 0.2.0. When users switch versions of an npm connector, Gety shows that section as the version's release notes.
  3. Publish as above.
  • If the new version can't continue from what an older version synced, such as when the state changes shape or document IDs change, raise sync_version in the manifest. Gety then deletes the connector's documents and syncs from scratch when users update.
  • If the new version needs more permissions, users review the added permissions before switching. A published npm version can't change, so changing permissions always means a new version.
  • To withdraw a broken npm version, deprecate it with npm deprecate <package>@<version> "<message>". Gety never offers a deprecated version as an update.

See Versions and updates for the full rules.

To list your connector in the Community tab of Featured connectors in Gety:

  1. Publish the connector to npm as a public package, and install it in Gety from npm (<package>@<version>) to check that it works.

  2. Open a pull request to gety-ai/gety-connectors that adds community/<id>.json, where <id> is your manifest id:

    {
    "package": "@acme/gety-my-connector",
    "version": "0.1.0",
    "author": { "github": "acme" }
    }
  3. Wait for the checks and the review. After the pull request is merged, the featured list is regenerated and published, which can wait for the next release of the official connectors. Gety then lists your connector the next time it loads the featured list.

The list shows the name, description, and icon from the manifest of the registered version, and your GitHub avatar and name. Gety installs exactly the registered version. To list a newer version, open a pull request that changes version; users who already installed the connector can update it in Gety at any time.

For the icon requirements, the checks, and how to remove a listing, see the contributing guide.

Next steps​

For the complete contract, including manifest fields, permissions, configuration field types, the SDK lifecycle, and WireDoc fields, see Connector reference.