Skip to main content
Every script folder needs a manifest.json next to its build output. It is the first thing the loader reads: no manifest, no script.
entry has to stay inside the script folder - "entry": "../../elsewhere.js" is rejected. The same rule applies to every relative import your bundle makes at runtime.

Permissions

Native modules that can reach outside the script - raw memory, the filesystem, arbitrary native code - are gated. A script that imports one it did not declare gets an error at import time:
Everything else - @native/entities, @native/render, @native/esp, @native/trace, @native/convars, @native/game, localStorage, timers - needs no permission at all. Those only read game state or draw.
A manifest with no permissions key at all keeps working and is granted everything, with a warning in the log. That is a compatibility fallback for scripts written before permissions existed - declare the list explicitly in anything new. An empty array ("permissions": []) means nothing is granted and is enforced as written.

Layout

The data/ folder is excluded from the file watcher, so writing to it from your script does not trigger a reload loop.

Bundling is required

The module loader resolves exactly three kinds of specifier:
  • ./thing.js, ../thing.js - relative paths inside your script folder
  • @native/* - the built-in modules
  • nothing else
There is no node_modules resolution at runtime. Any npm dependency has to be bundled into your entry file - which is what spd build does for you. If you see Cannot resolve 'lodash-es', the import survived into the output instead of being bundled.
Keep an eye on bundle size. Each script is compiled in its own context, so a 500 KB bundle costs real time on every load and reload.