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, andmetadata. - 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"]
}
netlists hosts the connector may reach. Declare every host a request touches, including redirect targets and CDNs.readlists folders under the user's home folder that the connector may read.envlists environment variables it may read, andrunlists 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. Onlyidandtitleare required;idmust stay stable across polls.del(id)removes a document when the source proves it's gone.this.configcontains the values from your manifest configuration fields.this.lastStatecontains the last state yielded by the last successful poll, ornullon the first run.this.signalaborts when Gety stops the poll, for example when the user disables or restarts the connector. Pass it to fetches when possible.this.dataDiris 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.signalaborts. Pass it tofetch()and other long calls, and checkthis.signal.abortedbetween 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.dataDirinstead.
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 throughcontent. - Reading
this.configin the constructor. Configuration is available in lifecycle methods such asonLoad()andpoll(), 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
- Run
deno task buildto refreshdist/main.js. - Open Gety → Settings → Connectors and click Install from local folder.
- Select your connector folder.
- Review the permissions and click Allow.
- If the connector has configuration fields, fill them in and click Install.
- 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:
| Channel | Users install it with | Updates |
|---|---|---|
| npm | Install from remote → npm, Featured connectors, or gety connector install --npm <package> | Gety checks for new versions and lets users switch versions. |
| GitHub release | Install 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"] }
}
}
getyis the complete manifest, andgety.versionmust equal the packageversion. Gety doesn't read amanifest.jsonin the package.- Keep
manifest.jsonas the single source. In the sample,deno task syncwrites the packageversion,description, andgetyfrommanifest.json, anddeno task verifyruns it, so the two never disagree. filesmust include the entry and every other file the connector needs at run time, such as its icon and source map. ListCHANGELOG.mdtoo; npm leaves it out oncefilesis set.- Set
homepageto 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 themanifest.jsoninside the zip. Gety downloads only this file to show the permissions before the user confirms.<id>-<version>.zip, withidandversionfrom 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
- Raise
versioninmanifest.json, and raisemin_gety_app_versionif the new version needs a newer Gety. - 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. - 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_versionin 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.
Publish to the featured list
To list your connector in the Community tab of Featured connectors in Gety:
-
Publish the connector to npm as a public package, and install it in Gety from npm (
<package>@<version>) to check that it works. -
Open a pull request to gety-ai/gety-connectors that adds
community/<id>.json, where<id>is your manifestid:{"package": "@acme/gety-my-connector","version": "0.1.0","author": { "github": "acme" }} -
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.