Dependency Resolution
OpenCore builds each server resource as its own runtime boundary. When a server bundle uses build.server.external, those packages are not included in server.js by default, so the built resource must still be able to load them at runtime.
FiveM and RedM servers running FXServer Node.js 22 use a filesystem sandbox. If a dependency resolves outside the mounted resource folder, Node can fail with errors like:
ERR_ACCESS_DENIED
permission: FileSystemRead
no device found
build.dependencyResolution controls how OpenCore makes server.external packages available to the runtime.
Quick Recommendation
Use isolated for production FiveM and RedM resources.
import { defineConfig } from '@open-core/cli'
export default defineConfig({
build: {
dependencyResolution: {
mode: 'isolated',
packageManager: 'auto',
verifySandboxPaths: true,
allowInstallScripts: false,
cache: true,
},
},
})
Configure server.external on the resources that actually need runtime packages, for example a database resource that imports pg or typeorm.
auto currently resolves to isolated for FiveM and RedM.
Configuration
type DependencyResolutionMode =
| 'auto'
| 'isolated'
| 'shared-resource'
| 'bundle'
| 'symlink'
interface DependencyResolutionConfig {
mode?: DependencyResolutionMode
packageManager?: 'auto' | 'npm' | 'pnpm' | 'yarn'
sharedResourceName?: string
verifySandboxPaths?: boolean
allowInstallScripts?: boolean
cache?: boolean
}
Options:
| Option | Default | Meaning |
|---|---|---|
mode | 'auto' | Selects the dependency strategy. |
packageManager | 'auto' | Package manager used for physical installs in isolated and shared-resource. |
sharedResourceName | '__opencore_deps' | Resource name generated by shared-resource. |
verifySandboxPaths | true | Scans generated resources and rejects symlinks that resolve outside the resource folder. |
allowInstallScripts | false | Allows dependency lifecycle scripts during install. Keep disabled unless a dependency truly requires it. |
cache | true | Reuses OpenCore's generated physical dependency trees from node_modules/.cache/opencore/dependencies. |
Package Normalization
OpenCore normalizes external imports to package names before installing or checking dependencies.
| External import | Normalized package |
|---|---|
typeorm | typeorm |
typeorm/browser | typeorm |
pg/lib/client | pg |
@prisma/adapter-pg | @prisma/adapter-pg |
@scope/package/subpath | @scope/package |
Node built-ins are ignored:
fs
path
node:fs
node:path
Filesystem paths are rejected:
../path
/absolute/path
file:...
Version Resolution
OpenCore never installs latest silently. Versions are resolved in this order:
- Resource
package.json - Root
package.json - Installed package metadata
- Clear build error
Local filesystem specs such as file:, link:, and workspace: are not accepted for deployed runtime dependencies. Use registry versions for resources that must run inside FXServer.
Strategies
auto
auto chooses the stable default for the target runtime.
For FiveM and RedM, auto currently means isolated.
Pros:
- Minimal config for most projects.
- Tracks OpenCore's safest runtime default.
- Avoids legacy symlink behavior under the FXServer Node.js 22 sandbox.
Cons:
- Behavior can evolve as OpenCore gains more runtime knowledge.
- Use an explicit mode if you need a locked strategy for CI or release builds.
Use when:
- You want the recommended OpenCore default.
- You do not need experimental shared dependency behavior.
isolated
isolated installs runtime dependencies into each built resource that has server.external packages.
Output shape:
dist/
└── database/
├── fxmanifest.lua
├── server.js
├── package.json
└── node_modules/
What it does:
- Writes a minimal
package.jsoninto the built resource. - Installs only normalized runtime dependencies required by
server.external. - Creates a physical local
node_modulesin the resource. - Uses pnpm's hoisted node linker with copied packages when pnpm is selected, so transitive runtime imports resolve inside the resource.
- Rejects symlinks that resolve outside the resource when
verifySandboxPathsis enabled.
Pros:
- Stable default for FiveM and RedM.
- Best fit for FXServer Node.js 22 sandbox rules.
- Works with packages that expect runtime files in
node_modules. - Keeps resource boundaries explicit.
- Easier to inspect and deploy because each resource is self-contained.
Cons:
- Duplicates dependencies across resources.
- Slower builds than symlink mode because packages may be installed per resource.
- Larger output if several resources externalize the same packages.
Use when:
- You use database drivers, ORMs, Prisma adapters, or packages with runtime assets.
- You deploy to FiveM or RedM production servers.
- You want the least surprising runtime behavior.
For example, typeorm requires tslib at runtime. With isolated pnpm installs, OpenCore generates node_modules/tslib inside the resource so FXServer does not climb to the workspace root and fail the sandbox check.
Example:
export default defineConfig({
build: {
dependencyResolution: {
mode: 'isolated',
verifySandboxPaths: true,
allowInstallScripts: false,
},
server: {
external: ['typeorm', 'pg', '@prisma/adapter-pg'],
},
},
})
shared-resource
shared-resource generates one dependency resource and routes external imports through that resource.
Output shape:
dist/
├── database/
│ ├── fxmanifest.lua
│ └── server.js
└── __opencore_deps/
├── fxmanifest.lua
├── noop.js
├── package.json
└── node_modules/
What it does:
- Aggregates
server.externalpackages from all resources using shared-resource mode. - Installs those dependencies once into
__opencore_deps. - Fails on dependency version conflicts instead of choosing one silently.
- Uses an esbuild virtual module to load dependencies with
GetResourcePath('__opencore_deps'). - Does not use regex replacement on generated JavaScript.
Pros:
- Avoids duplicated dependency installs across resources.
- Smaller deployment when many resources share the same runtime packages.
- Keeps each game resource as a separate resource instead of making one monolithic bundle.
Cons:
- Experimental.
- FXServer Node.js 22 may still restrict cross-resource filesystem reads.
- All participating resources must agree on dependency versions.
- Startup order and resource availability must be considered in real server testing.
Use when:
- Multiple resources share the same large runtime dependencies.
- You can validate the result on a real FXServer Node.js 22 environment.
- You accept that sandbox behavior may still change or block cross-resource access.
Example:
export default defineConfig({
build: {
dependencyResolution: {
mode: 'shared-resource',
sharedResourceName: '__opencore_deps',
verifySandboxPaths: true,
},
server: {
external: ['typeorm', 'pg'],
},
},
})
Do not mark shared-resource as stable for your project until you test it on the exact FXServer version and Node.js runtime you deploy.
bundle
bundle treats server.external as a list of packages that should be compatibility-checked and then bundled into each resource.
Output shape:
dist/
└── utility/
├── fxmanifest.lua
└── server.js
No generated node_modules is required for the bundled packages.
What it does:
- Checks each configured package before bundling.
- Rejects native packages with
binding.gyp,.nodefiles, prebuilds, or native package metadata. - Warns about dynamic
require()because bundlers cannot always statically resolve it. - Bundles compatible packages into the resource's
server.js.
Pros:
- Smallest deployment shape for pure JavaScript packages.
- No runtime dependency install needed.
- No cross-resource reads.
- Good for small utilities and libraries with static imports.
Cons:
- Experimental.
- Not reliable for packages that load files at runtime.
- Not reliable for packages with dynamic
require()or dynamic imports. - Not suitable for native modules.
- Can increase bundle size per resource if the same package is used in many resources.
Use when:
- The dependency is a pure JavaScript library.
- The package uses static imports or static
require()calls. - The package does not load runtime assets from disk.
- You want a self-contained
server.jsfor small utility dependencies.
Avoid for:
- Prisma engines or Prisma clients.
- Native modules such as
better-sqlite3,sqlite3,bcrypt,sharp, orcanvas. - Packages with
.nodebinaries. - Packages that read templates, migrations, schemas, certificates, or other assets at runtime.
- Packages that construct import paths dynamically.
Example:
export default defineConfig({
build: {
dependencyResolution: {
mode: 'bundle',
},
server: {
external: ['nanoid'],
},
},
})
Pure Libraries For Bundle Mode
A pure library is a package that can be copied into the JavaScript bundle without needing files or binaries next to it at runtime.
Good signs:
- Written in JavaScript or TypeScript and published as JavaScript.
- No
binding.gyp. - No
.nodefiles. - No
prebuilds/folder. - Imports are static and visible to the bundler.
- Does not call
fs.readFileSync()to load package assets at runtime. - Does not depend on native binaries, external executables, generated engines, or platform-specific downloads.
Usually good candidates:
- Small ID, math, date, validation, formatting, parsing, and utility libraries.
- Libraries like
nanoid,zod,date-fns, or similar pure-JS utilities, depending on their exact version and transitive dependencies.
Usually bad candidates:
- Database drivers with native bindings.
- ORMs that load runtime metadata or assets dynamically.
- Prisma-related packages.
- Image processing packages.
- Password hashing packages backed by native modules.
- Packages that scan directories or load arbitrary files from their package folder at runtime.
If you are unsure, use isolated first. Move to bundle only after testing startup and the code paths that use the package.
symlink
symlink preserves the old behavior and links node_modules from the source resource into the built resource.
Pros:
- Fast.
- Useful for older runtimes or local experiments.
- Avoids repeated package installs.
Cons:
- Legacy opt-in only.
- May fail under the FXServer Node.js 22 filesystem sandbox.
- Can resolve packages outside the mounted resource folder.
- Less representative of a production deployment.
Use when:
- You are targeting an older runtime that is known to allow this behavior.
- You are debugging locally and explicitly accept sandbox risk.
Example:
export default defineConfig({
build: {
dependencyResolution: {
mode: 'symlink',
},
server: {
external: ['typeorm', 'pg'],
},
},
})
OpenCore prints a warning when symlink mode is used:
Warning: symlink mode may fail under the FXServer Node.js 22 filesystem sandbox.
Use mode "isolated" for production.
Strategy Comparison
| Mode | Stability | Runtime shape | Best for | Main risk |
|---|---|---|---|---|
auto | Stable | Uses runtime default | Most projects | Default may evolve over time |
isolated | Stable | Per-resource node_modules | Production FiveM/RedM externals | Larger output |
shared-resource | Experimental | One generated dependency resource | Shared large dependencies | Cross-resource sandbox restrictions |
bundle | Experimental | Dependencies inside server.js | Pure JavaScript libraries | Dynamic loading and runtime assets |
symlink | Legacy | Linked node_modules | Older runtimes and local testing | FXServer Node.js 22 sandbox failures |
Recommended Choices
Use isolated for:
typeormpg@prisma/adapter-pg- Prisma-related packages
- packages with runtime assets
- packages where correctness matters more than output size
Use shared-resource only when:
- many resources share the same dependencies
- duplicated installs are too expensive
- you can run an FXServer Node.js 22 integration test
Use bundle only when:
- the package is pure JavaScript
- the package has no native bindings
- the package has no runtime asset loading
- all important code paths work after bundling
Use symlink only when:
- you explicitly need legacy behavior
- you understand the sandbox risk
Troubleshooting
ERR_ACCESS_DENIED or no device found
Use isolated and keep verifySandboxPaths: true.
This usually means a runtime dependency resolved outside the mounted FiveM resource path.
Build fails because a version cannot be resolved
Add the dependency to the resource package.json or root package.json with an explicit version.
OpenCore will not install latest silently.
Bundle mode rejects a package
Use isolated unless you can remove the native dependency or runtime asset dependency.
Bundle mode is intentionally strict because a successful build does not always mean the package can run inside FXServer.
Shared-resource mode works in build but fails in server
Switch to isolated.
The build can generate valid files, but FXServer may still restrict cross-resource filesystem reads under Node.js 22.