Extensions
The Shape extension system is exclusively for language runtime integrations
(Python, TypeScript, etc.). Extensions are loadable dynamic libraries that
register a foreign language interpreter so you can write fn python ... or
fn typescript ... blocks inside Shape code.
For calling native C libraries (DuckDB, PostgreSQL, libcurl, etc.), use
extern C fn syntax instead. Database
connectors and other C-backed functionality are distributed as regular
Shape packages — see Packages & Building.
Installing Extensions
Section titled “Installing Extensions”The easiest way to install extensions is with shape ext:
shape ext install pythonshape ext install typescriptThis downloads the extension crate from crates.io, compiles
it from source for your platform, and installs the shared library to
~/.shape/extensions/. The Rust toolchain is required for building.
Pin a specific version:
shape ext install python --version 0.1.0Manage extensions:
shape ext list # show installed and available extensionsshape ext remove pythonInstalled extensions are automatically detected by both the CLI and the LSP — no additional configuration is needed.
Available first-party extensions
Section titled “Available first-party extensions”| Extension | Crate | Description |
|---|---|---|
python | shape-ext-python | Python interop via PyO3 |
typescript | shape-ext-typescript | TypeScript/JS interop via V8 (deno_core) |
Third-party extensions follow the same naming convention: any crate named
shape-ext-<name> on crates.io can be installed with shape ext install <name>.
CLI shorthand
Section titled “CLI shorthand”When running a script, you can also load an installed extension by name:
shape run script.shape --extension pythonThis resolves python to ~/.shape/extensions/libshape_ext_python.so
automatically.
- Extensions are dynamic libraries (
.so/.dylib/.dll). - They are loaded through the unified runtime loader.
- Capability dispatch is manifest-driven (single source of truth).
- The only supported contract is
shape.language_runtime.
Configure in shape.toml
Section titled “Configure in shape.toml”For project-level configuration, you can also specify extensions explicitly in
shape.toml (this overrides the global ~/.shape/extensions/ directory):
[[extensions]]name = "python"path = "./extensions/libshape_ext_python.so"autoload = true
[extensions.config]For standalone scripts, use frontmatter with the same [[extensions]] shape.
Capability Contracts
Section titled “Capability Contracts”A language runtime extension must expose capability metadata and vtables through the ABI entrypoints:
shape_plugin_infoshape_abi_versionshape_capability_manifestshape_capability_vtable
The runtime binds the language runtime from shape.language_runtime metadata.
Database Packages (formerly Database Extensions)
Section titled “Database Packages (formerly Database Extensions)”Database connectors (PostgreSQL, DuckDB) are now distributed as Shape
packages that use extern C fn to call the
underlying C libraries. This replaces the former extension-based approach.
The query pattern remains the same — packages expose Queryable implementations
with lazy query building:
from duckdb use { connect }
let db = connect("pricing_data.duckdb")let results = db.trades .filter(|t| t.volume > 1000) .orderBy(|t| t.timestamp, Order.Desc) .limit(50) .execute()Under the hood, the package uses extern C fn declarations to call the native
DuckDB C API, with native dependencies declared in the package’s shape.toml:
[native-dependencies]duckdb = { provider = "system", linux = "libduckdb.so", macos = "libduckdb.dylib", windows = "duckdb.dll" }That means the current DuckDB package expects the host to provide DuckDB. To
reference package-local native libraries instead, use provider = "vendored"
with target-qualified targets entries in the package’s
[native-dependencies].
Current limitation: shape build / shape publish do not embed those native
files into the .shapec bundle or registry upload yet. The bundle preserves
the metadata only, so vendored/path native assets still need separate
filesystem distribution today.
See the Native C Interop chapter for the exact provider rules, target matching order, and lock behavior.
For HTTP/REST APIs (replacing the former OpenAPI extension), use the built-in
http stdlib module directly — no extension or package needed.
Data Source and Lockflow
Section titled “Data Source and Lockflow”External schema/type artifacts are persisted in the unified lock pipeline:
- project:
shape.lock - standalone script:
<script>.lock
Native artifacts are target-aware and fingerprint-aware inside those lockfiles, and there is no connector-specific side lock system.
Section Claims
Section titled “Section Claims”Extensions can claim custom TOML sections in shape.toml, allowing domain-specific configuration without coupling it into Shape’s core.
Declaring Claims
Section titled “Declaring Claims”Extensions export shape_claimed_sections to declare which sections they handle:
#[no_mangle]pub extern "C" fn shape_claimed_sections() -> *const SectionsManifest { static CLAIMS: [SectionClaim; 1] = [ SectionClaim { name: c"native-dependencies".as_ptr(), required: false, }, ]; static MANIFEST: SectionsManifest = SectionsManifest { sections: CLAIMS.as_ptr(), sections_len: 1, }; &MANIFEST}SectionClaim Fields
Section titled “SectionClaim Fields”| Field | Type | Description |
|---|---|---|
name | *const c_char | Section name (e.g., "native-dependencies") |
required | bool | Whether absence of the section is an error |
Validation Workflow
Section titled “Validation Workflow”When a project loads extensions, Shape validates that all custom TOML sections are claimed:
shape.tomlis parsed; unknown sections are captured inextension_sections- Extensions are loaded; each declares its claimed sections via the ABI
validate_with_claimed_sections()checks every extension section against the union of claims- Unclaimed sections produce warnings (e.g., typos in section names)
Accessing Section Data
Section titled “Accessing Section Data”Extensions receive their claimed sections as JSON values at initialization time:
[native-dependencies]libcurl = { linux = "libcurl.so.4", macos = "libcurl.dylib" }The runtime converts the TOML table to JSON and passes it to the extension’s init function.
Writing a Language Runtime Extension
Section titled “Writing a Language Runtime Extension”Language runtime extensions implement the shape.language_runtime capability
contract. For the full capability vtable and lifecycle, see
Polyglot Functions.
For a working reference implementation, see the Python extension source at
shape/extensions/python/.
- Extensions are exclusively for language runtimes. For C library access, use
extern C fn. - Keep extension boundaries wire-native (
shape-wire) and avoid VMValue-based interfaces in new code. - Contract tests should validate capability behavior (load, schema, invocation, failure modes).
- Extensions that don’t need custom sections simply omit the
shape_claimed_sectionsexport (backwards compatible). - See the Polyglot Functions chapter for the full guide to
fn python ...,fn julia ..., etc.