> ## Documentation Index
> Fetch the complete documentation index at: https://sequinstream.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Typesense sink

> Stream Postgres changes directly to Typesense with Sequin's Typesense sink.

The Typesense sink indexes documents into a Typesense collection, using the Typesense JSON API.

<Tip>
  This is the reference for the Typesense sink. See the [quickstart](/quickstart/typesense) for a step-by-step walkthrough or the [how-to guide](/how-to/stream-postgres-to-typesense) for an explanation of how to use the Typesense sink.
</Tip>

## Configuration

* **Endpoint URL**

  The URL of your Typesense server (e.g., `https://your-typesense-server:8108`).

* **Collection name**

  The name of the Typesense collection where documents will be indexed. The collection must exist before imports can work.

* **API key**

  The API key for authenticating with your Typesense server.

## Transform requirements

Your [transform](/reference/transforms) must return a document matching the schema of the [Typesense collection](https://typesense.org/docs/28.0/api/collections.html#create-a-collection).

This includes a string `id` field which is mandatory for Typesense.
It's also okay to use an integer/serial/bigserial/uuid `id` - Sequin will automatically stringify it for you.
However, from Typesense's perspective, all `id`s are strings and will be returned as such in search results.

<Note>The `id` should not include spaces or any other characters that require encoding in URLs.</Note>

For example, the following transform would convert a numeric `product_id` column into the string `id`:

```elixir theme={null}
def transform(action, record, changes, metadata) do
  Map.put(record, "id", Kernel.to_string(record["product_id"]))
end
```

## Import action

Sequin always uses Typesense's `emplace` [import action](https://typesense.org/docs/28.0/api/documents.html#action-modes-create-upsert-update-emplace), which means:

* Typesense will create a new document or update an existing one based on the `id`
* Your transform can supply either a complete document or a partial document for update.

<Note> The full document should be provided for creation -  partials are only allowed when updating an existing document. </Note>

## API endpoints used by Sequin

Sequin uses the following specific endpoints of the [Typesense documents API](https://typesense.org/docs/28.0/api/documents.html):

* `/collections/#{collection_name}/documents/import`

  For indexing batches of documents.  This is the most common path.

* `/collections/#{collection_name}/documents`

  For indexing documents one at a time.
  Used when batch size was manually configured to be 1, or when the batch happens to have only 1 document.

* `/collections/#{collection_name}/documents/#{document_id}`

  For deleting documents.  Deletions are always processed one at a time.

Sequin also uses one method of the [collections API](https://typesense.org/docs/28.0/api/collections.html):

* `/collections/#{collection_name}`

  Only called when you click "Test Connection" in the Sequin console.
  Successful responses indicate the connection is working and the collection exists, but the result is otherwise ignored.

Sequin does not perform any searches.

## Error handling

Common errors that can occur when working with the Typesense sink include:

* Connection issues to the Typesense server (HTTP vs HTTPS, TCP port number, URL typos, etc)
* Collection doesn't exist (Typesense reports "Not Found")
* Schema mismatches
* Missing `id` fields

When errors occur, they will be visible in the "Messages" tab of the Sequin web console. You can click on a message to see details about the error.
Error messages from the Typesense API are passed through unchanged to the Sequin console, except in case of batch imports, where they are aggregated into one error report for the whole batch.

## Routing

The Typesense sink supports dynamic routing of the `action` and `collection_name` with [routing functions](/reference/routing).

Example routing function:

```elixir theme={null}
def route(action, record, changes, metadata) do
  if record["deleted_at"] != nil do
    %{collection_name: metadata.table_name, action: "delete"}
  else
    %{collection_name: metadata.table_name, action: "index"}
  end
end
```

When not using a routing function, documents will be indexed into the collection specified in the sink configuration.
