Skip to main content
Every script folder has a manifest.json. The cheat reads it first when it loads the script, and spd build, spd publish and spd doctor check it with the same rules the market’s server uses. Only name and version are required. Everything else has a default, so this is a complete manifest:
spd new writes a bit more:

Fields

name, version, author, description and permissions end up on the script global. license isn’t read: a library is published under the spurdo! library license, and a script’s license isn’t used for anything. An invalid field fails spd build and spd publish with the field and what’s wrong with it. An unknown permission also fails the load in the cheat.

name

name is the key of the script everywhere: the cheat’s settings and script list, the registry and the market. It’s a package id:
  • @scope/slug, lowercase letters, digits, ., _ and -, up to 214 characters;
  • the scope is your account’s, spd whoami prints it. spd build warns while it’s still the @your-nick placeholder, and spd publish refuses a scope that isn’t yours;
  • the slug is yours to pick, and it’s the displayName when there isn’t one.
Changing name makes it a different script: settings saved under the old one don’t follow it. Old scripts with a free-form name are moved to a package id by spd migrate, which keeps the old name as displayName. On the market, displayName and description are used once: the first spd publish takes them as the item’s name and summary. After that the item’s card is edited on the site. See Publishing a script.

Permissions

A script without permissions, or with [], gets neither. List what it needs:
spd build fails when the code imports @native/ffi and the manifest has no ffi; spd build --fix-permissions adds it and builds again.
Manifests used to grant both permissions when permissions was missing. They don’t anymore: spd migrate warns about it. A version on the market that asks for ffi or filesystem, its own or through a library, is reviewed by a moderator before buyers get it. See Permissions and moderation.

Source and entry

source is the module the bundle starts from. Without it, the first of src/index.ts, src/index.tsx, src/index.js and src/index.jsx that exists is used. Set it only if your entry is somewhere else; it has to be inside src/, with forward slashes. entry is always dist/index.js: spd build writes the bundle there and the cheat loads it from there. You can leave it out.

Workers

Workers aren’t listed in the manifest. spd build finds them in the code:
Each one becomes a bundle of its own next to dist/index.js: src/parser.ts goes to dist/parser.js, src/workers/zip.ts to dist/workers_zip.js, and the path in the code is rewritten to it. Workers started by workers are found the same way. The path has to be a string literal, relative to the file; a computed path fails the build. A worker only has @native/fs and @native/wasi; the build warns when it imports another native module. See Workers.

Folder layout

Put every file the script loads and ships with in assets/ and load it by a path relative to the script folder: new Font("assets/Inter.ttf"), new Texture("assets/logo.svg"). spd publish uploads assets/, and buyers get it next to dist/, so the same path works for them. data/ is the script’s own runtime folder. It’s never published: every buyer starts with an empty one. A file the code loads from data/, or from any other folder of the project (fonts/, images/), exists only on your disk, and the script fails for everyone who bought it. spd build and spd publish warn when the code names such a file. Imports resolve to @native/* modules and to files inside the folder; npm packages are bundled in by spd build. There’s no node_modules at runtime. localStorage is keyed by the folder name, not name. The script hot-reloads when any file in its folder changes, except under data/.