# How to Create Custom Provider Plugins for LangExtract: A Complete Developer's Guide

> Learn how to create custom provider plugins for LangExtract. Follow this developer's guide to extend LangExtract functionality with your own language model providers.

- Repository: [Google/langextract](https://github.com/google/langextract)
- Tags: how-to-guide
- Published: 2026-02-19

---

**You can create custom provider plugins for LangExtract by implementing a class that inherits from `BaseLanguageModel`, registering it with the `@lx.providers.registry.register` decorator, and exposing it via the `langextract.providers` entry point in your package configuration.**

LangExtract's modular architecture allows developers to integrate any language model backend through a standardized plugin system. Whether you need to connect to a proprietary API or a local model server, you can create custom provider plugins for LangExtract using the registry pattern and Python entry points. This article explains the three-layer architecture—registration, discovery, and factory instantiation—and provides a complete implementation based on the `google/langextract` source code.

## Understanding the Plugin Architecture

LangExtract discovers and loads language-model providers through a registry system split into three distinct layers. Understanding these layers is essential before implementing your own provider.

### The Three Layers of Provider Integration

| Layer | Responsibility | Core Implementation |
|-------|----------------|----------------------|
| **Provider registration** | Maps regex patterns for model IDs to a provider class. Registration happens lazily via the `@router.register` decorator or the `router.register_lazy` function. | [`langextract/providers/router.py`](https://github.com/google/langextract/blob/main/langextract/providers/router.py) |
| **Provider discovery** | Loads built-in providers, optional providers (e.g., OpenAI), and third-party providers installed as separate packages. Discovery uses Python entry points under the group `langextract.providers`. | [`langextract/plugins.py`](https://github.com/google/langextract/blob/main/langextract/plugins.py) |
| **Factory creation** | The factory (`factory.create_model`) asks the router to resolve a model ID (or a provider name) and returns an instantiated provider. | [`langextract/factory.py`](https://github.com/google/langextract/blob/main/langextract/factory.py) |

When you add a new provider, you only need to implement a class that inherits from `BaseLanguageModel` and register it with the router. The registration makes the provider discoverable both for built-in use and external plugin consumption.

### Key Concepts

- **Patterns**: One or more regular-expression strings that decide which `model_id` values should be handled by the provider. They are stored as compiled `re.Pattern` objects in the router entry.
- **Priority**: An integer (default 0) that decides which provider wins when multiple patterns match. Higher numbers win.
- **Lazy loading**: Providers are loaded only when needed, avoiding heavy optional dependencies from being imported at package import time.
- **Schema support**: If a provider can emit structured JSON, it implements `get_schema_class()` and `apply_schema()` as shown in the built-in Gemini provider.

## Step-by-Step Guide to Building a Custom Provider

The repository ships a helper script that scaffolds the boilerplate for you. While all steps below can be done manually, the script saves time and creates a test suite.

### Step 1: Scaffold the Plugin Structure

Run the scaffolding script from the LangExtract repository:

```bash
python scripts/create_provider_plugin.py MyProvider \
    --patterns "^myprovider" "^custom" \
    --with-schema          # optional – include schema skeleton

```

This creates the following structure:

```

langextract-myprovider/
├── langextract_myprovider/
│   ├── __init__.py
│   ├── provider.py
│   └── schema.py      # only if --with-schema

├── README.md
├── pyproject.toml
├── test_plugin.py
└── .gitignore

```

The script writes the files using the templates in [`scripts/create_provider_plugin.py`](https://github.com/google/langextract/blob/main/scripts/create_provider_plugin.py).

### Step 2: Implement the Provider Class

Edit [`provider.py`](https://github.com/google/langextract/blob/main/provider.py) to implement your provider logic. Here is the minimal implementation pattern:

```python
import os
import langextract as lx
from typing import Sequence

@lx.providers.registry.register(r'^myprovider', r'^custom', priority=10)
class MyProviderLanguageModel(lx.inference.BaseLanguageModel):
    """Minimal LangExtract provider for MyProvider."""
    
    def __init__(self, model_id: str, api_key: str | None = None, **kwargs):
        super().__init__()
        self.model_id = model_id
        # Resolve API key from environment if not supplied.

        self.api_key = api_key or os.getenv("MYPROVIDER_API_KEY")
        # Initialise the concrete client (replace with your SDK).

        # self.client = MyClient(api_key=self.api_key)

    def infer(self, batch_prompts: Sequence[str], **kwargs):
        """Yield a list of ScoredOutput objects for each prompt."""
        for prompt in batch_prompts:
            # Replace the line below with a real API call.

            result = f"Mock response for: {prompt[:30]}..."
            yield [lx.inference.ScoredOutput(score=1.0, output=result)]

```

**Key implementation points:**

- The `@lx.providers.registry.register` decorator (see `router.register` at lines 9-35 in [`langextract/providers/router.py`](https://github.com/google/langextract/blob/main/langextract/providers/router.py)) registers the class with the given patterns and priority.
- The class inherits from `BaseLanguageModel`, which supplies helper methods like `merge_kwargs`.
- You must implement **`infer`** to return an iterator of `ScoredOutput` objects. See the Gemini implementation in [`langextract/providers/gemini.py`](https://github.com/google/langextract/blob/main/langextract/providers/gemini.py) for a more complex example handling batching and schema constraints.

### Step 3: Add Structured Output Support (Optional)

If your provider supports structured JSON output, add a [`schema.py`](https://github.com/google/langextract/blob/main/schema.py) that defines a subclass of `lx.schema.BaseSchema` and expose it via `get_schema_class`. The scaffold already contains a template; the Gemini provider shows a full example in [`providers/gemini.py`](https://github.com/google/langextract/blob/main/providers/gemini.py) (lines 75-83).

```python
import langextract as lx

class MyProviderSchema(lx.schema.BaseSchema):
    def __init__(self, schema_dict: dict):
        self._schema = schema_dict

    @property
    def schema_dict(self) -> dict:
        return self._schema

    @classmethod
    def from_examples(cls, examples_data, attribute_suffix="_attributes"):
        # Build a JSON schema from example extractions.

        return cls({...})
    
    def to_provider_config(self) -> dict:
        return {"response_schema": self._schema, "structured_output": True}

```

Then add the class method to your provider:

```python
@classmethod
def get_schema_class(cls) -> type[lx.schema.BaseSchema] | None:
    return MyProviderSchema

```

### Step 4: Configure the Entry Point

Ensure your [`pyproject.toml`](https://github.com/google/langextract/blob/main/pyproject.toml) exposes the provider correctly. The scaffold writes:

```toml
[project.entry-points."langextract.providers"]
myprovider = "langextract_myprovider.provider:MyProviderLanguageModel"

```

This makes the plugin discoverable by the **plugins** module (`available_providers` / `get_provider_class` in [`langextract/plugins.py`](https://github.com/google/langextract/blob/main/langextract/plugins.py)).

### Step 5: Test Locally

Install the plugin in editable mode:

```bash
cd langextract-myprovider
pip install -e .

```

Run the bundled test script:

```bash
python test_plugin.py

```

The test script validates registration, pattern matching, inference, and (if present) schema handling. See [`scripts/create_provider_plugin.py`](https://github.com/google/langextract/blob/main/scripts/create_provider_plugin.py) for the autogenerated test logic.

### Step 6: Publish (Optional)

Follow standard Python packaging steps:

```bash
python -m build
twine upload dist/*

```

Once published, users can install it with `pip install langextract-myprovider` and the provider will be automatically available to LangExtract without any additional imports.

## Complete Minimal Provider Example

Here is the full implementation for a minimal working provider:

```python
import os
import langextract as lx
from typing import Sequence

@lx.providers.registry.register(r'^myprov', r'^custom', priority=10)
class MyProviderLanguageModel(lx.inference.BaseLanguageModel):
    """Simple example provider."""

    def __init__(self, model_id: str, api_key: str | None = None, **kwargs):
        super().__init__()
        self.model_id = model_id
        self.api_key = api_key or os.getenv("MYPROVIDER_API_KEY")
        # self.client = MyClient(self.api_key)   # ← replace with real client

    def infer(self, batch_prompts: Sequence[str], **kwargs):
        """Yield results for each prompt."""
        for prompt in batch_prompts:
            # Replace with actual request to the LLM service.

            result = f"[MyProvider] echo: {prompt}"
            yield [lx.inference.ScoredOutput(score=1.0, output=result)]

```

**Registration details:** The `@lx.providers.registry.register` decorator (see `router.register` at lines 9-35 in [`langextract/providers/router.py`](https://github.com/google/langextract/blob/main/langextract/providers/router.py)) binds the class to the specified regex patterns.

**Factory usage:**

```python
import langextract as lx

config = lx.factory.ModelConfig(model_id="myprov-1")
model = lx.factory.create_model(config)      # resolves to MyProviderLanguageModel

out = next(model.infer(["Hello world"]))[0].output
print(out)   # → "[MyProvider] echo: Hello world"

```

## Important Source Files

| File | Role | Link |
|------|------|------|
| [`langextract/providers/README.md`](https://github.com/google/langextract/blob/main/langextract/providers/README.md) | High-level guide to provider architecture, pattern registration, and plugin discovery. | [README](https://github.com/google/langextract/blob/main/langextract/providers/README.md) |
| [`scripts/create_provider_plugin.py`](https://github.com/google/langextract/blob/main/scripts/create_provider_plugin.py) | CLI that scaffolds a complete provider package (directory layout, [`pyproject.toml`](https://github.com/google/langextract/blob/main/pyproject.toml), tests, README). | [script](https://github.com/google/langextract/blob/main/scripts/create_provider_plugin.py) |
| [`langextract/providers/router.py`](https://github.com/google/langextract/blob/main/langextract/providers/router.py) | Core registry implementation (`register`, `resolve`, `resolve_provider`). | [router](https://github.com/google/langextract/blob/main/langextract/providers/router.py) |
| [`langextract/plugins.py`](https://github.com/google/langextract/blob/main/langextract/plugins.py) | Discovery of built-in, optional, and third-party providers via entry points. | [plugins](https://github.com/google/langextract/blob/main/langextract/plugins.py) |
| [`langextract/providers/gemini.py`](https://github.com/google/langextract/blob/main/langextract/providers/gemini.py) | Reference implementation of a fully-featured provider (API key handling, batch mode, schema support). | [Gemini provider](https://github.com/google/langextract/blob/main/langextract/providers/gemini.py) |
| [`langextract/factory.py`](https://github.com/google/langextract/blob/main/langextract/factory.py) | Public API that creates providers from `ModelConfig`. | [factory](https://github.com/google/langextract/blob/main/langextract/factory.py) |

These files together define the full lifecycle—from **registration** to **discovery** to **instantiation**—that you need to understand when writing a custom provider.

## Summary

- **Three-layer architecture**: Registration happens in [`router.py`](https://github.com/google/langextract/blob/main/router.py), discovery in [`plugins.py`](https://github.com/google/langextract/blob/main/plugins.py), and instantiation in [`factory.py`](https://github.com/google/langextract/blob/main/factory.py).
- **Minimal implementation**: Inherit from `BaseLanguageModel`, implement the `infer` method, and use `@lx.providers.registry.register` with regex patterns.
- **Entry point requirement**: Expose your class under the `langextract.providers` group in [`pyproject.toml`](https://github.com/google/langextract/blob/main/pyproject.toml) to enable automatic discovery.
- **Scaffolding available**: Use [`scripts/create_provider_plugin.py`](https://github.com/google/langextract/blob/main/scripts/create_provider_plugin.py) to generate boilerplate, tests, and packaging configuration.
- **Schema support optional**: Implement `BaseSchema` and `get_schema_class()` to enable structured JSON output.

## Frequently Asked Questions

### What is the priority parameter in the register decorator?

The **priority** parameter is an integer (default 0) that resolves conflicts when multiple provider patterns match the same model ID. Higher numbers win. For example, if two providers both match the pattern `^gpt`, the one with `priority=10` will be selected over one with `priority=0`. This is implemented in [`langextract/providers/router.py`](https://github.com/google/langextract/blob/main/langextract/providers/router.py) at lines 9-35.

### Can I register multiple patterns for the same provider?

Yes, you can pass multiple regex patterns to the `@lx.providers.registry.register` decorator. The provider will handle any model ID that matches any of the provided patterns. For example: `@lx.providers.registry.register(r'^myprov', r'^custom', priority=10)` registers the class to handle both patterns simultaneously.

### How does LangExtract discover external provider packages?

LangExtract uses Python's **entry point** system defined in [`langextract/plugins.py`](https://github.com/google/langextract/blob/main/langextract/plugins.py). When you install a package that exposes an entry point under the group `langextract.providers`, LangExtract automatically discovers it via the `available_providers()` function. This happens at runtime without requiring users to manually import your module.

### Do I need to implement schema support for my custom provider?

No, schema support is **optional**. If your provider only returns plain text, you only need to implement the `infer` method. Schema support (via `BaseSchema` and `get_schema_class()`) is only required if you want to enable structured JSON output. You can refer to [`langextract/providers/gemini.py`](https://github.com/google/langextract/blob/main/langextract/providers/gemini.py) lines 75-83 for a complete implementation example.