sb3gen
v3.1.3
Published
Programmatically generate and edit [Scratch](https://scratch.mit.edu/) `.sb3` files with a typed JavaScript/TypeScript API.
Downloads
1,140
Readme
sb3gen
Programmatically generate and edit Scratch .sb3 files with a typed JavaScript/TypeScript API.
import { Project, Costume, WhenFlagClicked, SayForSecs } from 'sb3gen';
const project = new Project();
const stage = project.addStage();
stage.costumes.push(Costume.blank('backdrop1'));
const sprite = project.addSprite('Cat');
sprite.costumes.push(Costume.circle('costume1', '#4C97FF', 100));
sprite.addScript(s => {
s.push(WhenFlagClicked());
s.push(SayForSecs('Hello, World!', 2));
});
await project.save('hello-world.sb3');Installation
npm install sb3gen
# or
bun add sb3genCore concepts
Project
const project = new Project();
const stage = project.addStage(); // returns Stage
const sprite = project.addSprite('Name'); // returns Sprite
await project.save('out.sb3');Costumes
Four SVG factory methods — no external files needed:
Costume.blank('name') // white rectangle
Costume.colored('name', '#hex') // solid color fill
Costume.circle('name', '#hex', size) // filled circle
Costume.rect('name', '#hex', w, h, radius) // rounded rectanglePass any new Costume(name, uint8Array) for custom PNG/SVG data.
Scripts
addScript yields a Script builder. Push blocks, embed reporters, and use loop helpers:
sprite.addScript(s => {
s.push(WhenFlagClicked());
s.push(MoveSteps(10));
s.push(Forever(inner => {
inner.push(MoveSteps(10));
inner.push(IfOnEdgeBounce());
}));
s.push(Repeat(5, inner => {
inner.push(TurnRight(72));
}));
// embed a reporter block as an input value
s.push(GoToXY(
s.embed(Random(-200, 200)),
s.embed(Random(-150, 150))
));
s.setXY(300, 100); // set script position in the editor
});Variables, lists, and broadcasts
const score = stage.addVariable('score', 0); // VarInputVal
const items = stage.addList('items'); // ListInputVal
const greet = stage.addBroadcast('greet'); // BroadcastInputValPass the returned reference directly to block functions — no string lookups needed:
s.push(ChangeVariableBy(1, score));
s.push(AddToList('hello', items));
s.push(BroadcastAndWait(greet));
s.push(WhenIReceive(greet));Extensions
Extensions are auto-detected from opcodes when you call project.save(). Import blocks from the extension namespace:
import { pen, music } from 'sb3gen';
s.push(pen.PenClear());
s.push(pen.SetPenColor(InputVal.color('#FFD700')));
s.push(pen.SetPenSize(4));
s.push(pen.PenDown());Available namespaces: pen, music, wedo2, boost, ev3, gdxfor, makeymakey, microbit, translate, text2speech, videoSensing, faceSensing.
Custom blocks
Define reusable custom blocks with the procedures API:
import { procedure, DefineBlock, CallBlock, ArgumentReporterStringNumber, If } from 'sb3gen';
// Create a spec once — reuse for both define and call
const greet = procedure('greet %s loudly: %b', [
{ name: 'name', type: 'string_number' },
{ name: 'shout', type: 'boolean' },
]);
// Run without screen refresh (warp mode)
const fast = procedure('fast move %s steps', [
{ name: 'steps', type: 'string_number' },
], true);
// Definition script
sprite.addScript(s => {
s.push(DefineBlock(greet));
s.push(If(
s.embed(ArgumentReporterBoolean('shout')),
inner => { inner.push(Say(inner.embed(ArgumentReporterStringNumber('name')))); },
));
});
// Call site
sprite.addScript(s => {
s.push(WhenFlagClicked());
s.push(CallBlock(greet, [InputVal.str('World'), s.embed(Eq(1, 1))]));
});Inside a control block callback (inner =>), use inner.embed() — not s.embed().
Editing existing .sb3 files
import { readFileSync } from 'fs';
import { Zipper } from 'sb3gen';
const project = await Zipper.decode(readFileSync('input.sb3'));
const sprite = project.targets.find(t => !t.isStage);
sprite.name = 'Renamed';
sprite.addScript(s => {
s.push(WhenFlagClicked());
s.push(Say('Injected!'));
});
await project.save('output.sb3');Examples
Run any example with bun run examples/<file>:
| File | Demonstrates |
|-----------------------|--------------------------------------------------|
| 01-hello-world.js | Basic project, sprite, say block |
| 02-bouncing-ball.js | forever, IfOnEdgeBounce |
| 03-counter.js | Variables, ChangeVariableBy |
| 04-broadcast.js | addBroadcast, WhenIReceive, two sprites |
| 05-edit-existing.js | Zipper.decode, mutate and re-save |
| 06-clones.js | CreateCloneOf, WhenIStartAsAClone, s.embed |
| 07-operators.js | Math and logic reporter blocks |
| 08-if-else.js | If, IfElse, conditionals |
| 09-pen-star.js | Pen extension, repeat |
| 10-sensing-quiz.js | ask/answer, sensing blocks |
| 11-animation.js | Costume switching, timing |
| 12-lists.js | addList, ItemOfList, nested embeds |
| 13-monitors.js | Stage monitors |
| 14-custom-blocks.js | procedure, DefineBlock, CallBlock |
Building
bun install
bun run build