This guide covers the react-native-duckdb Expo config plugin -- how it works internally, how to configure it, and how to troubleshoot common issues.
For bare React Native projects (no Expo), see bare-workflow.md.
Add the plugin to your app.json (or app.config.js):
{
"expo": {
"plugins": [
["react-native-duckdb", { "extensions": ["core_functions", "parquet"] }]
]
}
}Then run:
npx expo prebuild --cleanThat's it. The plugin writes extension configuration into native property files, and the build pipeline picks them up automatically.
The plugin bridges Expo's JavaScript config with the native DuckDB build pipeline. Here's the full flow:
- User configures extensions in
app.json(orapp.config.js) - Plugin runs during
expo prebuild, writing extension names to platform-specific property files - Build scripts read the property files and pass extensions to
configure-extensions.js configure-extensions.jsgeneratesextension_config_local.cmakefor DuckDB's build system- DuckDB compiles with selected extensions statically linked into the native binary
The plugin uses only safe Expo mods (withGradleProperties and createBuildPodfilePropsConfigPlugin) -- no dangerous modifications to native files.
type DuckDBPluginProps = {
extensions?: string[];
};| Property | Type | Default | Description |
|---|---|---|---|
extensions |
string[] |
undefined |
Extensions to statically link into the DuckDB binary |
| Extension | Description |
|---|---|
core_functions |
Essential SQL functions (sum, avg, list_value, uuid, etc.) -- strongly recommended |
parquet |
Apache Parquet file format support |
json |
JSON file format support |
icu |
Unicode collation and locale-aware text functions |
sqlite_scanner |
Read and write SQLite databases via ATTACH |
httpfs |
Remote file access over HTTPS (details) |
fts |
BM25 full-text search (details) |
vss |
HNSW vector similarity search (details) |
autocomplete |
SQL autocomplete suggestions |
tpch |
TPC-H benchmark data generator |
tpcds |
TPC-DS benchmark data generator |
delta |
Delta Lake table format |
No extensions (valid, but you'll lose common SQL functions):
["react-native-duckdb"]Recommended minimum:
["react-native-duckdb", { "extensions": ["core_functions"] }]Common setup (analytics + file queries):
["react-native-duckdb", { "extensions": ["core_functions", "parquet", "json"] }]Full setup (analytics + files + SQLite interop + locale):
["react-native-duckdb", { "extensions": ["core_functions", "parquet", "json", "icu", "sqlite_scanner"] }]app.json
└─ plugins: [["react-native-duckdb", { extensions: ["core_functions", "parquet"] }]]
│
▼
Expo plugin (withAndroid.ts)
└─ withGradleProperties → writes RNDuckDB_extensions=core_functions,parquet
│
▼
android/gradle.properties
└─ RNDuckDB_extensions=core_functions,parquet
│
▼
android/build.gradle
└─ Reads RNDuckDB_extensions → passes -DDUCKDB_EXTENSIONS=core_functions,parquet to CMake
│
▼
android/CMakeLists.txt
└─ If DUCKDB_EXTENSIONS is set → passes --extensions "core_functions,parquet" to configure-extensions.js
│
▼
scripts/configure-extensions.js
└─ Generates duckdb/extension/extension_config_local.cmake with duckdb_extension_load() calls
│
▼
DuckDB CMake build
└─ Reads extension_config_local.cmake → statically links selected extensions
app.json
└─ plugins: [["react-native-duckdb", { extensions: ["core_functions", "parquet"] }]]
│
▼
Expo plugin (withIos.ts)
└─ createBuildPodfilePropsConfigPlugin → writes react-native-duckdb.extensions=core_functions,parquet
│
▼
ios/Podfile.properties.json
└─ { "react-native-duckdb.extensions": "core_functions,parquet" }
│
▼
scripts/build-duckdb-ios.sh (runs during pod install)
└─ Reads Podfile.properties.json → extracts react-native-duckdb.extensions value
│
▼
scripts/configure-extensions.js --extensions "core_functions,parquet"
└─ Generates duckdb/extension/extension_config_local.cmake
│
▼
DuckDB CMake + xcframework build
└─ Statically links selected extensions into the iOS binary
Moving from bare workflow package.json config to Expo app.json config.
{
"expo": {
"plugins": [
["react-native-duckdb", { "extensions": ["core_functions", "parquet"] }]
]
}
}Use the same extensions you had in package.json.
You can remove the react-native-duckdb key from your app's package.json:
- "react-native-duckdb": {
- "build": {
- "extensions": ["core_functions", "parquet"]
- }
- }This is optional -- when both exist, the app.json config takes priority via the --extensions flag, which overrides package.json discovery in configure-extensions.js.
npx expo prebuild --cleanVerify the extensions appear in the generated files:
- Android:
grep RNDuckDB_extensions android/gradle.properties - iOS:
cat ios/Podfile.properties.json | grep react-native-duckdb
The Expo config plugin only runs during expo prebuild. It has no effect on bare React Native projects.
- Expo managed workflow: Extensions configured in
app.json→ plugin writes property files → build scripts read them - Bare workflow: Extensions configured in
package.json→configure-extensions.jsreadspackage.jsondirectly. See bare-workflow.md. - Both configured:
app.jsontakes priority (the--extensionsflag overridespackage.jsondiscovery)
If you eject from Expo, the property files written by the plugin remain in your native directories and continue to work. You can also switch to package.json config after ejecting.
The extension name is not in the valid list. Check for typos:
Valid: core_functions, parquet, json, icu, sqlite_scanner, autocomplete, tpch, tpcds, delta, httpfs, fts, vss
- Verify
expo prebuildran successfully (no errors) - Check
android/gradle.propertiescontainsRNDuckDB_extensions=... - Check
ios/Podfile.properties.jsoncontains"react-native-duckdb.extensions": "..." - If missing, ensure the plugin is listed in
app.jsonplugins array
DuckDB caches build artifacts. If extensions seem stale after changing config:
# Clean iOS build cache
rm -rf duckdb/build-ios*
# Clean Android build cache
rm -rf android/.cxx
# Regenerate native projects
npx expo prebuild --cleanThe plugin resolves from the app's node_modules. Ensure react-native-duckdb is in your app's direct dependencies (not just hoisted to the workspace root):
# In your app directory
npm install react-native-duckdbThe package exports ./app.plugin.js explicitly. If you see resolution errors:
- Delete
node_modulesand reinstall - Verify
react-native-duckdbversion includes the plugin (checknode_modules/react-native-duckdb/app.plugin.jsexists)
package/plugin/
src/
index.ts — Main plugin entry, composes Android + iOS, wraps with createRunOncePlugin
withAndroid.ts — Writes RNDuckDB_extensions to gradle.properties
withIos.ts — Writes react-native-duckdb.extensions to Podfile.properties.json
types.ts — DuckDBPluginProps type definition
build/ — Compiled CommonJS output (committed for npm publish)
tsconfig.json — Separate CommonJS config targeting Node.js
The entry point is package/app.plugin.js which requires ./plugin/build.
bun run build:plugin
# or
npx tsc --project plugin/tsconfig.jsonThe compiled output in plugin/build/ is committed to git (despite the build/ gitignore pattern) because it's needed for npm publish -- consumers don't have TypeScript at prebuild time.
Scaffold a temporary Expo app and run prebuild:
npx create-expo-app /tmp/test-app --template blank-typescript
cd /tmp/test-app
npm install /path/to/react-native-duckdb/package
# Edit app.json to add the plugin with extensions
npx expo prebuild --clean --no-install
# Verify output
grep RNDuckDB_extensions android/gradle.properties
cat ios/Podfile.properties.json
# Clean up
rm -rf /tmp/test-app- Uses only safe Expo mods --
withGradleProperties(Android) andcreateBuildPodfilePropsConfigPlugin(iOS). No dangerous mods that modify Podfiles, AppDelegates, or build.gradle directly. createRunOncePluginwrapper ensures idempotent execution (safe if user accidentally lists plugin twice).- The
propValueGetterAPI oncreateBuildPodfilePropsConfigPluginreturnsundefinedto omit the property (not an empty string), so no extensions = no property file entry.
Part of react-native-duckdb -- see API Reference for the full API.