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

# Weaviate as a vector store

> Connect a Weaviate cluster to ModuleX as a bring-your-own-key knowledge provider, configure the knowledge node, and run vector retrieval against your own classes.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

Weaviate is an open-source vector database with a GraphQL query API. In ModuleX, Weaviate is a **bring-your-own-key (BYOK) knowledge provider**: you store the connection details for your own cluster as a credential, and a [knowledge node](/workflow-builder/nodes/knowledge) in a workflow runs vector similarity search against one of your Weaviate classes. ModuleX never hosts your vectors and does not charge credits for retrieval against a Weaviate cluster you own — see [Billing](#billing-byok-retrieval-is-not-metered).

If you want ModuleX to host the vectors, ingest, embed, and search for you, use [modulexdb (managed)](/integrations/knowledge-providers/modulexdb) instead. For the full list of supported stores, see [Knowledge providers](/integrations/knowledge-providers/overview).

<Note>
  Weaviate retrieval in ModuleX runs **inside a workflow knowledge node**, not through the managed `/knowledge-bases` search API. The managed search, ingest, and document endpoints under `/knowledge-bases` operate only on `modulexdb`-backed knowledge bases. With Weaviate you bring vectors that already exist in your own cluster.
</Note>

## How Weaviate fits into ModuleX

ModuleX treats Weaviate as an external provider behind a single adapter. At run time, a knowledge node:

<Steps>
  <Step title="Resolves the credential">
    The node looks up your stored Weaviate credential by `credential_id`, scoped to the organization in `X-Organization-ID`, and decrypts the connection details (`url` and optional `api_key`).
  </Step>

  <Step title="Embeds the query">
    Weaviate retrieval in ModuleX requires a query vector. ModuleX generates the embedding from the node's `embedding_config` (for example an OpenAI `text-embedding-3-small` model) before calling Weaviate. See [embedding configuration](#embedding-configuration).
  </Step>

  <Step title="Runs a nearVector GraphQL search">
    The adapter issues a `Get` GraphQL query with a `nearVector` argument against your class, applies a `certainty` floor derived from `min_score`, and limits results to `top_k`.
  </Step>

  <Step title="Returns chunks or context">
    Matches are normalized into ModuleX's standard chunk shape and returned as `chunks`, a single `context` string, or `both`, depending on `output_format`.
  </Step>
</Steps>

<MediaEmbed id="MX-MEDIA-4160" type="image" caption={"Data-flow diagram of BYOK Weaviate retrieval inside a knowledge node."} />

## Connect Weaviate

A Weaviate connection is stored as a credential with the `custom` auth type and the integration name `weaviate`. Creating a credential requires the **owner** or **admin** role on the organization (`organization_admin_required`); the `member` role is retired and cannot create credentials. See [Roles & permissions](/security/roles-permissions).

Authenticate every request to the ModuleX API with `Authorization: Bearer mx_live_…` and the `X-Organization-ID` header, as described in [Authentication](/api-reference/authentication).

### Connection fields

These are the two fields the Weaviate auth schema exposes. They are stored, encrypted at rest, inside the credential's `auth_data`.

<ParamField path="url" type="string" required>
  The base URL of your Weaviate instance, with no trailing path. Example: `https://your-cluster.weaviate.cloud`. If omitted at query time the adapter falls back to `http://localhost:8080`, but you should always set it explicitly. Stored unencrypted-flagged (`sensitive: false`).
</ParamField>

<ParamField path="api_key" type="string">
  The Weaviate API key. **Required for Weaviate Cloud**; optional for an unauthenticated self-hosted cluster. When present, ModuleX sends it to Weaviate as `Authorization: Bearer <api_key>`. Stored as a secret (`sensitive: true`).
</ParamField>

### Create the credential

Send the connection fields under `auth_data` with `auth_type` set to `custom`. The response returns the new `credential_id`, which you then reference from the knowledge node.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.modulex.dev/credentials \
    -H "Authorization: Bearer mx_live_xxx" \
    -H "X-Organization-ID: org_123" \
    -H "Content-Type: application/json" \
    -d '{
      "integration_name": "weaviate",
      "auth_type": "custom",
      "display_name": "Production Weaviate",
      "auth_data": {
        "url": "https://your-cluster.weaviate.cloud",
        "api_key": "weaviate-api-key-..."
      }
    }'
  ```

  ```python Python theme={null}
  from modulex import AsyncModulex

  client = AsyncModulex(
      api_key="mx_live_xxx",
      organization_id="org_123",
  )

  credential = await client.credentials.create(
      integration_name="weaviate",
      auth_type="custom",
      display_name="Production Weaviate",
      auth_data={
          "url": "https://your-cluster.weaviate.cloud",
          "api_key": "weaviate-api-key-...",
      },
  )
  print(credential["credential_id"])
  ```

  ```javascript JavaScript theme={null}
  import { Modulex } from "modulex-js";

  const client = new Modulex({
    apiKey: "mx_live_xxx",
    organizationId: "org_123",
  });

  const credential = await client.credentials.create({
    integration_name: "weaviate",
    auth_type: "custom",
    display_name: "Production Weaviate",
    auth_data: {
      url: "https://your-cluster.weaviate.cloud",
      api_key: "weaviate-api-key-...",
    },
  });
  console.log(credential.credential_id);
  ```
</CodeGroup>

<Warning>
  Send `auth_type: "custom"` explicitly. Without it, the backend infers the credential type from `auth_data`: a payload containing `api_key` is treated as a standard API-key credential, which is the wrong shape for Weaviate (it would drop the `url`). The explicit `custom` type keeps both fields together in one credential.
</Warning>

For the broader credential lifecycle — listing, testing, setting a default, and rotation — see [Managing credentials](/integrations/managing-credentials).

### Test the connection

Testing a Weaviate credential validates connectivity rather than running a search. ModuleX calls your cluster's readiness probe at `GET {url}/v1/.well-known/ready` and, on success, reads `GET {url}/v1/schema` to count your classes.

| Outcome                                    | Meaning                                                                         |
| ------------------------------------------ | ------------------------------------------------------------------------------- |
| `status: "connected"` plus `classes_count` | The cluster is reachable and the API key (if any) is valid.                     |
| `401` from Weaviate                        | The `api_key` is missing or invalid for a cluster that requires authentication. |
| Connection refused / not ready             | The `url` is wrong, the cluster is down, or it is not reachable from ModuleX.   |

<MediaEmbed id="MX-MEDIA-4161" type="screenshot" caption={"The Weaviate connection form in the ModuleX credentials UI."} />

## Configure the knowledge node

Once the credential exists, add a [knowledge node](/workflow-builder/nodes/knowledge) to a workflow and point it at Weaviate. The node config (`KnowledgeNodeConfig`) is shared across all providers; the fields below are the ones that matter for Weaviate. Every node value supports `{{nodeId.path}}` references so a query can come from a previous step — see [Variables & references](/workflow-builder/variables-and-references).

### Connection and provider

<ParamField path="provider_type" type="string" default="modulexdb">
  Set to `weaviate` to route the node through the Weaviate adapter. The other accepted values are `modulexdb`, `qdrant`, `pinecone`, and `mongodb_atlas`.
</ParamField>

<ParamField path="credential_id" type="string" required>
  The `credential_id` of the Weaviate credential you created above.
</ParamField>

<ParamField path="collection_name" type="string" required>
  The Weaviate **class** to search (Weaviate's term for a collection). Required for every external provider; the node raises a validation error if it is missing for a non-`modulexdb` provider.
</ParamField>

<ParamField path="namespace" type="string">
  Accepted on the node config for Pinecone-style providers. **The Weaviate adapter ignores it** — Weaviate has no namespace concept, so leave it unset.
</ParamField>

### Query

<ParamField path="query" type="string" required>
  The search text. Supports `{{nodeId.path}}` references for dynamic queries. ModuleX embeds this text into a vector before calling Weaviate (see [embedding configuration](#embedding-configuration)).
</ParamField>

<ParamField path="query_from_input" type="boolean" default="false">
  When `true`, the node uses the workflow input as the query instead of the `query` field, reading the first of `query`, `question`, `input`, `user_input`, or `message` found in run state.
</ParamField>

### Retrieval settings

<ParamField path="top_k" type="integer" default="5">
  Number of results to return. Range `1`–`50`. Maps to the GraphQL `limit` argument.
</ParamField>

<ParamField path="min_score" type="number" default="0.3">
  Minimum similarity threshold, `0.0`–`1.0`. For Weaviate the adapter converts this to a `certainty` floor as `certainty = 1.0 - min_score` (only when `min_score > 0`; otherwise `certainty` is `0.0` and no floor is applied). The returned `score` for each match is Weaviate's `certainty` value.
</ParamField>

<ParamField path="max_tokens" type="integer" default="2000">
  Token budget for the assembled `context` string. Range `100`–`10000`. Applies when `output_format` is `context` or `both`.
</ParamField>

<ParamField path="filters" type="object">
  Provider-specific filter conditions passed through to the adapter. For Weaviate these correspond to GraphQL `where` conditions.
</ParamField>

<ParamField path="document_ids" type="array">
  Restrict results to specific document IDs. **Native (`modulexdb`) knowledge bases only** — this filter is not applied to Weaviate.
</ParamField>

### Output

<ParamField path="output_format" type="string" default="context">
  How retrieved knowledge is returned: `chunks` (individual matches with metadata), `context` (a single formatted RAG string), or `both`.
</ParamField>

<ParamField path="include_metadata" type="boolean" default="true">
  Include each match's properties (returned by Weaviate as object properties) in the chunk metadata.
</ParamField>

<ParamField path="include_source" type="boolean" default="true">
  Include source document headers in the formatted `context` string.
</ParamField>

### Embedding configuration

Weaviate retrieval in ModuleX **always requires a query vector** — the adapter reports `requires_query_embedding() = true` and raises `Weaviate requires query_vector for search` if none is supplied. ModuleX builds that vector from the node's `embedding_config` before calling your cluster, so this object is required for Weaviate.

<Warning>
  Use the **same embedding model** that produced the vectors stored in your Weaviate class. Mismatched models yield dimension errors or meaningless similarity scores. ModuleX does not read your class's vectorizer config to infer the model; you declare it in `embedding_config`.
</Warning>

<ParamField path="embedding_config" type="object" required>
  Embedding settings for generating the query vector.

  <Expandable title="embedding_config fields">
    <ParamField path="integration_name" type="string" required>
      Integration name of the embedding provider, for example `openai` or `cohere`.
    </ParamField>

    <ParamField path="provider_id" type="string" required>
      Provider identifier, for example `openai`.
    </ParamField>

    <ParamField path="model_id" type="string" required>
      Embedding model id, for example `text-embedding-3-small`. Must match the model used to index your Weaviate class.
    </ParamField>

    <ParamField path="credential_id" type="string">
      Credential for the embedding provider. If omitted, ModuleX uses the organization's default credential for `integration_name`.
    </ParamField>
  </Expandable>
</ParamField>

<Note>
  ModuleX's `text2vec`-style server-side text search is **not** wired through this adapter. Although the catalog metadata for Weaviate lists a `query_text` parameter and mentions `nearText`, the live retrieval path uses `nearVector` only and rejects a search with no `query_vector`. Plan to provide vectors via `embedding_config`.
</Note>

## BYOK retrieval

A complete Weaviate knowledge node configuration, ready to drop into a workflow's node definition `config`:

```json Knowledge node config (Weaviate) theme={null}
{
  "provider_type": "weaviate",
  "credential_id": "a1b2c3d4-0000-0000-0000-000000000000",
  "collection_name": "ProductDocs",
  "query": "{{trigger.question}}",
  "top_k": 5,
  "min_score": 0.3,
  "output_format": "context",
  "include_metadata": true,
  "include_source": true,
  "embedding_config": {
    "integration_name": "openai",
    "provider_id": "openai",
    "model_id": "text-embedding-3-small",
    "credential_id": null
  }
}
```

When this node runs, ModuleX embeds `{{trigger.question}}`, issues the `nearVector` GraphQL query against the `ProductDocs` class, and writes the result to run state under the node's own `id`. A downstream [LLM node](/workflow-builder/nodes/llm) or [Agent node](/workflow-builder/nodes/agent) can then reference the retrieved context with `{{<knowledge_node_id>.context}}`.

To run the workflow itself over the API or an SDK, see [Run via API](/workflow-builder/execution/api-endpoint) and the [Run a workflow](/guides/run-a-workflow) guide. The run streams over SSE — see [SSE run streaming](/realtime/sse-streaming).

### What the adapter returns

Each match is normalized into a chunk. The `score` is Weaviate's `certainty`; `metadata` carries the object's returned properties; `content` is the adapter's best-effort text extraction from those properties.

<ResponseField name="id" type="string">
  The Weaviate object id (`_additional.id`).
</ResponseField>

<ResponseField name="score" type="number">
  The match's `certainty` from Weaviate (`0`–`1`, higher is better).
</ResponseField>

<ResponseField name="content" type="string">
  Text extracted from the object's properties. The adapter checks common field names in order: `content`, `text`, `chunk_text`, `page_content`, `data`, `body`, `summary`, `document`. If none is present, `content` may be empty — name a text property accordingly in your class.
</ResponseField>

<ResponseField name="metadata" type="object">
  The object's returned properties (everything except `_additional`). Present only when `include_metadata` is `true`.
</ResponseField>

### Returning specific properties

The Weaviate adapter requests result properties for you. If your class stores its text under a non-default property name, set `properties` in the node `filters`/config path appropriate to your build, or ensure one of the recognized field names above exists so `content` is populated. The catalog `query` action documents these tunables:

<ResponseField name="class_name" type="string" required>
  The Weaviate class to search.
</ResponseField>

<ResponseField name="query_vector" type="array">
  The query embedding (`nearVector`). Supplied by ModuleX from `embedding_config`.
</ResponseField>

<ResponseField name="limit" type="integer" default="5">
  Maximum number of results.
</ResponseField>

<ResponseField name="certainty" type="number" default="0.7">
  Minimum certainty threshold (`0`–`1`). At run time this is derived from the node's `min_score`.
</ResponseField>

<ResponseField name="properties" type="array">
  Properties to return in results. Defaults to an empty list, in which case the adapter returns the object id, certainty, and distance.
</ResponseField>

<ResponseField name="where" type="object">
  GraphQL `where` filter conditions.
</ResponseField>

## Billing: BYOK retrieval is not metered

Searching a Weaviate cluster you own does **not** consume ModuleX credits. ModuleX meters retrieval only for managed (`modulexdb`) knowledge — a managed search reserves one retrieval credit before the embed and records it on success. BYOK providers, including Weaviate, are uncosted for retrieval. See [Credits & metering](/billing/credits) and [Knowledge & RAG](/concepts/knowledge-rag).

<Note>
  The Weaviate **search** is uncosted, but the **workflow run** that contains the knowledge node is still subject to the run-admission billing gate. A run on the run / composer / assistant / knowledge-managed surfaces can be denied with a flat `DenialEnvelope` (`{code, layer, key, current, limit, reason}`) returned as `402`, `403`, or `429` when your plan allowance or wallet is exhausted. See [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). Embedding the query (via a managed embedding model) can itself consume credits even when the vector store is BYOK.
</Note>

## Errors and edge cases

The adapter and node surface the following conditions. Adapter exceptions become the node's `error` field (the node returns empty `chunks`/`context` with an `error` rather than failing the whole run), while credential and config problems raise before the search.

<Expandable title="Connection and authentication">
  * **`401` from Weaviate** — surfaces as an authentication error (`Invalid Weaviate API key`). Check the `api_key`; Weaviate Cloud requires one.
  * **Connection refused / request error** — the `url` is unreachable from ModuleX or the cluster is down. Re-test the credential.
</Expandable>

<Expandable title="Configuration">
  * **`Credential not found: <id>`** — the `credential_id` is wrong or belongs to another organization. Credentials are strictly scoped to `X-Organization-ID`.
  * **`collection_name is required for external provider`** — set `collection_name` to your Weaviate class.
  * **`embedding_config required …`** — Weaviate needs a query vector; provide a valid `embedding_config`.
  * **`Weaviate requires query_vector for search`** — raised if embedding produced no vector. Confirm the embedding model and its credential resolve.
</Expandable>

<Expandable title="Query execution">
  * **GraphQL error** — surfaces as `Weaviate query failed: <message>`. The most common cause is a class or property name that does not exist; verify the class with the cluster's schema and confirm property names.
  * **Empty `content`** — the matched objects have no property among the recognized text field names; rename a property or store text under one of `content`/`text`/`chunk_text`/`page_content`.
  * **No matches** — `min_score` may be too high (it tightens the `certainty` floor). Lower `min_score` toward `0` to widen results.
</Expandable>

## Related pages

<CardGroup cols={2}>
  <Card title="Knowledge providers" icon="database" href="/integrations/knowledge-providers/overview">
    Compare every vector store ModuleX can use for retrieval.
  </Card>

  <Card title="Knowledge node" icon="diagram-project" href="/workflow-builder/nodes/knowledge">
    The node that runs Weaviate retrieval inside a workflow.
  </Card>

  <Card title="External knowledge providers" icon="plug" href="/platform/knowledge/external-providers">
    How BYOK vector stores plug into ModuleX.
  </Card>

  <Card title="Knowledge & RAG" icon="book-open" href="/concepts/knowledge-rag">
    The retrieval model behind chats and workflows.
  </Card>
</CardGroup>
