This is a complete guide for creating a new OpenBB Platform extension from scratch. Follow every phase in order. When the user says "build me an application that does X", use this guide to scaffold, implement, install, and verify the extension.
This is a complete guide for creating a new OpenBB Platform extension from scratch. Follow every phase in order. When the user says "build me an application that does X", use this guide to scaffold, implement, install, and verify the extension.
The openbb-cookiecutter package must be installed in the active Python environment
before scaffolding. Install it with:
pip install openbb-cookiecutter
Then run the CLI to generate the project skeleton. All variables have sensible defaults; override only what you need.
| Variable | Default | Description |
|---|---|---|
full_name | "Hello World" |
| Author name |
email | "[email protected]" | Author email |
project_name | "OpenBB Python Extension Template" | Human-readable project name |
project_tag | derived from project_name | Hyphenated slug (used as directory name and package identifier) |
package_name | derived from project_name | Python package name (lower_snake_case) |
provider_name | derived from project_name | Provider identifier (lower_snake_case) |
router_name | derived from project_name | Router identifier (lower_snake_case) |
obbject_name | derived from project_name | OBBject accessor name (lower_snake_case) |
All derived values are automatically generated from project_name — you only need
to supply project_name for most cases.
openbb-cookiecutter \
-o /path/to/output \
--no-input \
--extra-context project_name="My Extension Name"
Add more --extra-context KEY=VALUE pairs to override individual variables.
Use -f to overwrite an existing directory.
After scaffolding, you get this tree (with template variables resolved):
<project_tag>/
├── pyproject.toml # Dependencies, entry points (CRITICAL)
├── <package_name>/
│ ├── providers/
│ │ └── <provider_name>/
│ │ ├── __init__.py # Provider registration (fetcher_dict)
│ │ ├── models/
│ │ │ ├── example.py # Custom-schema fetcher example
│ │ │ └── ohlc_example.py # Standard-model fetcher example
│ │ └── utils/
│ │ └── helpers.py # Shared utility functions
│ ├── routers/
│ │ ├── __init__.py
│ │ ├── <router_name>.py # Router commands (API endpoints)
│ │ ├── <router_name>_views.py # Chart views (optional)
│ │ └── depends.py # Dependency injection
│ └── obbject/
│ └── <obbject_name>/
│ └── __init__.py # OBBject accessors (optional)
└── tests/
└── conftest.py
OpenBB discovers extensions via Python entry points in pyproject.toml.
Each plugin type has its own entry point group:
| Entry Point Group | What It Registers | Where It Lives |
|---|---|---|
openbb_provider_extension | Data provider (fetchers) | providers/<name>/__init__.py |
openbb_core_extension | Router (API endpoints/commands) | routers/<name>.py |
openbb_charting_extension | Chart views | routers/<name>_views.py |
openbb_obbject_extension | Result post-processing accessors | obbject/<name>/__init__.py |
This is the core of any extension. A provider fetches data from an external source and returns it as typed Pydantic models.
Every data fetcher follows this structure:
Class 1 — QueryParams (input schema):
openbb_core.provider.abstract.query_params.QueryParamssymbol, start_date)pydantic.Field for descriptions and defaultsClass 2 — Data (output schema):
openbb_core.provider.abstract.data.Dataopen, high, low, close, volume)pydantic.Field for descriptions__alias_dict__ to map source field names to your schema namesClass 3 — Fetcher (orchestrator):
Inherits from Fetcher[YourQueryParams, list[YourData]]
Has exactly three static methods:
transform_query(params: dict) -> YourQueryParams
Pre-process user input. Return a validated QueryParams instance.
extract_data(query, credentials, **kwargs) -> list[dict]
Make the actual HTTP request to the data source. Return raw data as dicts.
For async fetching, name it aextract_data instead.
transform_data(query, data, **kwargs) -> list[YourData]
Convert raw dicts into typed Data model instances.
from typing import Any
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.abstract.query_params import QueryParams
from pydantic import Field
IMPORTANT: All HTTP requests inside fetchers and utility helpers must use the built-in
utilities from openbb_core.provider.utils.helpers. Do not create raw
requests, aiohttp, or httpx clients from scratch. The built-in helpers
apply the user's configured HTTP settings (proxy, timeout, user-agent, etc.)
from system_settings.json automatically.
Convert a QueryParams model to a URL query string, optionally excluding parameters that should not appear in the URL:
from openbb_core.provider.utils.helpers import get_querystring
query_string = get_querystring(query.model_dump(), ["interval", "provider"])
url = f"https://api.example.com/data?{query_string}"
model_dump() strips None values automatically. Pass parameter names to
exclude in the second argument as a list (use [] if nothing to exclude).
For use inside extract_data (sync fetchers):
from openbb_core.provider.utils import make_request
# Returns a requests.Response object
response = make_request(url, headers={"Authorization": f"Bearer {api_key}"})
data = response.json()
All requests.get/requests.post keyword arguments are passed through.
If you need a session object for multiple requests:
from openbb_core.provider.utils.helpers import get_requests_session
session = get_requests_session()
response = session.get(url)
For use inside aextract_data (async fetchers). Always prefer async.
Single URL — returns parsed JSON by default:
from openbb_core.provider.utils.helpers import amake_request
data = await amake_request(url) # returns dict (parsed JSON)
Multiple URLs — downloads concurrently and returns a list:
from openbb_core.provider.utils.helpers import amake_requests
urls = [f"https://api.example.com/data/{s}" for s in symbols]
all_data = await amake_requests(urls) # returns list[dict]
Custom response handling (e.g., CSV): Both amake_request and
amake_requests default to parsing JSON. For non-JSON content (CSV, text,
binary), pass a response_callback:
from io import StringIO
from typing import Any
from pandas import read_csv
from openbb_core.provider.utils.helpers import amake_request