Skip to content

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.

The easiest way to install extensions is with shape ext:

Terminal window
shape ext install python
shape ext install typescript

This 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:

Terminal window
shape ext install python --version 0.1.0

Manage extensions:

Terminal window
shape ext list # show installed and available extensions
shape ext remove python

Installed extensions are automatically detected by both the CLI and the LSP — no additional configuration is needed.

ExtensionCrateDescription
pythonshape-ext-pythonPython interop via PyO3
typescriptshape-ext-typescriptTypeScript/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>.

When running a script, you can also load an installed extension by name:

Terminal window
shape run script.shape --extension python

This 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.

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.

A language runtime extension must expose capability metadata and vtables through the ABI entrypoints:

  • shape_plugin_info
  • shape_abi_version
  • shape_capability_manifest
  • shape_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.

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.

Extensions can claim custom TOML sections in shape.toml, allowing domain-specific configuration without coupling it into Shape’s core.

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
}
FieldTypeDescription
name*const c_charSection name (e.g., "native-dependencies")
requiredboolWhether absence of the section is an error

When a project loads extensions, Shape validates that all custom TOML sections are claimed:

  1. shape.toml is parsed; unknown sections are captured in extension_sections
  2. Extensions are loaded; each declares its claimed sections via the ABI
  3. validate_with_claimed_sections() checks every extension section against the union of claims
  4. Unclaimed sections produce warnings (e.g., typos in section names)

Extensions receive their claimed sections as JSON values at initialization time:

shape.toml
[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.

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_sections export (backwards compatible).
  • See the Polyglot Functions chapter for the full guide to fn python ..., fn julia ..., etc.