How to Develop a Custom Data Source Connector for WeKnora: A Complete Implementation Guide
Implement the datasource.Connector interface in a new package under internal/datasource/connector/, register it via an init() function, and define UI metadata to make your custom data source available in WeKnora.
WeKnora synchronizes external knowledge bases through a plugin-based connector architecture that abstracts REST API integrations behind a common Go interface. Developing a custom data source connector for WeKnora involves implementing the Connector interface defined in internal/datasource/connector.go, handling authentication via thin HTTP clients, and registering your component in the global ConnectorRegistry. This guide provides the exact file paths, method signatures, and code patterns used in the Tencent/WeKnora codebase to build production-ready connectors.
Understanding the Connector Architecture
WeKnora's connector system relies on a central interface contract and a runtime registry. In internal/datasource/connector.go (lines 96-136), the Connector interface defines six required methods that every implementation must satisfy, while the ConnectorMetadataRegistry (lines 38-62) handles UI discovery. Each connector lives in its own package under internal/datasource/connector/<name>/ and typically consists of three files: connector.go for interface implementation, client.go for HTTP wrappers, and types.go for configuration structs.
The architecture separates concerns between credential management—handled via DataSourceConfig in internal/types/datasource.go—and data fetching. For large datasets, connectors may optionally implement StreamingConnector (lines 77-94 in internal/datasource/connector.go) to support checkpoint-driven incremental sync.
Step-by-Step Implementation Guide
Follow these nine steps to build a production-ready connector:
-
Create the package skeleton
Create a new directoryinternal/datasource/connector/<myconnector>and add three empty files:connector.go,client.go, andtypes.go. -
Define configuration structs
Intypes.go, define a config struct that parses the JSONB configuration stored in WeKnora's database. Include aParseConfighelper similar to Yuque'sparseYuqueConfigimplementation. -
Build the HTTP client
Implementclient.gowith a constructor that accepts your config struct and returns an*http.Client. Ensure all outbound requests route through WeKnora's SSRF-protected HTTP client frominternal/datasource/httpclient.go. -
Implement the Connector interface
Inconnector.go, define a struct that implements all six required methods:Type() string– returns a unique identifier constantValidate(ctx, cfg)– pings a lightweight endpoint to verify credentialsListResources(ctx, cfg, parentID)– returns available resources for selectionResolveResourceAncestors(...)– returns parent IDs for hierarchical sources (empty slice for flat structures)FetchAll(...)– retrieves all items for given resource IDsFetchIncremental(...)– fetches only changed items using a cursor
-
Add streaming support (optional)
If your source handles large volumes, implementStreamingConnectorwithFetchStreamthat emits items via aStreamHandlerand persists cursors after each page. Reference the Feishu Wiki connector ininternal/datasource/connector/feishu/wiki/connector.gofor a working example. -
Register the connector
Add aninit()function in your connector package that registers the instance:func init() { _ = datasource.NewConnectorRegistry().Register(NewConnector()) } -
Define UI metadata
ExtendConnectorMetadataRegistryininternal/datasource/connector.gowith a new entry mapping your connector type to name, description, auth type, and capabilities likeincrementalorstreaming. -
Write unit tests
Createconnector_test.gofollowing existing patterns to test validation, resource listing, and cursor handling. Rungo test ./...to ensure no regressions in the datasource package. -
Document the integration
Add a README entry underdocs/connectors/describing authentication requirements and environment variables, then submit a pull request to themainbranch after runninggo vetandgolint.
Complete Implementation Skeleton
Below is a minimal implementation template for connector.go that satisfies the core interface:
package myconnector
import (
"context"
"fmt"
"github.com/Tencent/WeKnora/internal/datasource"
"github.com/Tencent/WeKnora/internal/types"
)
type Connector struct{}
func NewConnector() *Connector { return &Connector{} }
func (c *Connector) Type() string { return types.ConnectorTypeMy }
func (c *Connector) Validate(ctx context.Context, cfg *types.DataSourceConfig) error {
mc, err := parseMyConfig(cfg)
if err != nil {
return err
}
cli := newClient(mc)
if err := cli.Ping(ctx); err != nil {
return fmt.Errorf("myservice validation failed: %w", err)
}
return nil
}
func (c *Connector) ListResources(ctx context.Context, cfg *types.DataSourceConfig, parentID string) ([]types.Resource, error) {
if parentID != "" {
return []types.Resource{}, nil
}
mc, _ := parseMyConfig(cfg)
cli := newClient(mc)
repos, err := cli.ListRepos(ctx)
if err != nil {
return nil, err
}
out := make([]types.Resource, len(repos))
for i, r := range repos {
out[i] = types.Resource{
ExternalID: fmt.Sprintf("%d", r.ID),
Name: r.Name,
Type: "repo",
URL: r.WebURL,
}
}
return out, nil
}
func (c *Connector) ResolveResourceAncestors(ctx context.Context, cfg *types.DataSourceConfig, ids []string) ([]string, error) {
return []string{}, nil
}
func (c *Connector) FetchAll(ctx context.Context, cfg *types.DataSourceConfig, ids []string) ([]types.FetchedItem, error) {
items, _, err := c.walk(ctx, cfg, ids, nil, false)
return items, err
}
func (c *Connector) FetchIncremental(ctx context.Context, cfg *types.DataSourceConfig, cursor *types.SyncCursor) ([]types.FetchedItem, *types.SyncCursor, error) {
return c.walk(ctx, cfg, cursor.ResourceIDs, cursor.MyCursor, true)
}
func (c *Connector) walk(ctx context.Context, cfg *types.DataSourceConfig, ids []string, cursor interface{}, incremental bool) ([]types.FetchedItem, *types.SyncCursor, error) {
// Implement API pagination, transform results to types.FetchedItem,
// and return updated cursor. See Yuque connector for reference.
return nil, nil, nil
}
Replace placeholder functions like parseMyConfig, newClient, and cli.ListRepos with actual implementations mapping to your external API.
Essential Reference Implementations
Study these existing connectors in the Tencent/WeKnora repository to understand different implementation patterns:
internal/datasource/connector/yuque/connector.go– Full-featured reference with validation, resource listing, and incremental walk patternsinternal/datasource/connector/notion/connector.go– Demonstrates handling of tree-structured resources whereResolveResourceAncestorsreturns meaningful datainternal/datasource/connector/feishu/wiki/connector.go– Shows streaming connector implementation with checkpoint persistenceinternal/datasource/connector.go– Defines theConnectorandStreamingConnectorinterfaces (lines 38-136) and the registration mechanisminternal/types/datasource.go– ContainsDataSourceConfigandSyncCursorstructures used across all connectorsinternal/datasource/httpclient.go– SSRF-protected HTTP client wrapper mandatory for all outbound connections
Summary
- Implement the six-method Connector interface defined in
internal/datasource/connector.goto satisfy WeKnora's contract - Organize code into three standard files:
types.gofor config,client.gofor HTTP logic, andconnector.gofor interface methods - Register your connector via an
init()function usingdatasource.NewConnectorRegistry().Register() - Add UI metadata to
ConnectorMetadataRegistryto enable discovery in the frontend - Use
internal/datasource/httpclient.gofor all outbound requests to ensure SSRF protection - Study the Yuque and Feishu Wiki implementations as reference patterns for standard and streaming connectors
Frequently Asked Questions
What Go interface must I implement to create a WeKnora data source connector?
You must implement the datasource.Connector interface defined in internal/datasource/connector.go. This requires six methods: Type(), Validate(), ListResources(), ResolveResourceAncestors(), FetchAll(), and FetchIncremental(). For large datasets, you can optionally implement StreamingConnector to support checkpoint-driven incremental sync via the FetchStream method.
Where do I register my custom connector so WeKnora discovers it at runtime?
Add an init() function in your connector's package that calls datasource.NewConnectorRegistry().Register(NewConnector()), as shown in internal/datasource/connector.go (lines 96-136). This pattern ensures your connector loads automatically when WeKnora starts. You must also add metadata to ConnectorMetadataRegistry so the UI can display your connector in the available sources list.
How should I handle authentication credentials in my connector implementation?
Store credentials in a custom config struct defined in types.go that parses the DataSourceConfig JSONB map from the database. Never hardcode secrets. Implement a Validate() method that uses these credentials to ping a lightweight endpoint, confirming connectivity before the sync begins. Use WeKnora's internal/datasource/httpclient.go wrapper for all HTTP requests to ensure SSRF protection and standardized timeouts.
Does WeKnora support incremental sync for large external datasets?
Yes. Implement the FetchIncremental() method to return only changed items using a cursor (typically a timestamp or revision ID), and optionally implement the StreamingConnector interface for memory-efficient processing. Reference the Feishu Wiki connector in internal/datasource/connector/feishu/wiki/connector.go for a production example of checkpoint-driven streaming with cursor persistence between batches.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →