Virtual Machine API
The VM (Virtual Machine) is the core engine that runs Scratch projects. For extension developers, the VM provides access to project execution, sprite management, block interpretation, and runtime events. This API is primarily available to unsandboxed extensions through Scratch.vm.
Overview
The VM manages:
- Project execution and control
- Sprite and target management
- Block execution and compilation
- Runtime events and monitoring
- Extension integration
- Asset and storage management
Accessing the VM
// Unsandboxed extensions only
if (!Scratch.extensions.unsandboxed) {
throw new Error('VM API requires unsandboxed extension');
}
const vm = Scratch.vm;
const runtime = vm.runtime;
Project Control
Basic Execution
// Start the project (green flag)
vm.greenFlag();
// Stop all scripts
vm.stopAll();
// Step execution manually (for debugging)
vm.runtime._step();
// Check if project is running
const isRunning = vm.runtime.threads.length > 0;
Turbo Mode
// Enable/disable turbo mode
vm.setTurboMode(true); // Enable turbo mode
vm.setTurboMode(false); // Disable turbo mode
// Listen for turbo mode changes
vm.on('TURBO_MODE_ON', () => {
console.log('Turbo mode enabled');
});
vm.on('TURBO_MODE_OFF', () => {
console.log('Turbo mode disabled');
});
Performance Options
// Set framerate (30 or 60 FPS)
vm.setFramerate(60);
// Enable/disable interpolation
vm.setInterpolation(true);
// Configure compiler options
vm.setCompilerOptions({
enabled: true,
warpTimer: false
});
// Set runtime options
vm.setRuntimeOptions({
maxClones: 300,
miscLimits: true,
fencing: true
});
Project Management
Loading Projects
// Load project from JSON string
const projectData = '{"targets": [...], "meta": {...}}';
await vm.loadProject(projectData);
// Load project from file buffer
const projectBuffer = new ArrayBuffer(/* project data */);
await vm.loadProject(projectBuffer);
// Clear current project
vm.clear();
Project Information
// Get project as JSON
const projectJson = vm.toJSON();
// Get project metadata
const runtime = vm.runtime;
const projectName = runtime.getTargetForStage().sprite.name;
// Check if project has changes
vm.on('PROJECT_CHANGED', () => {
console.log('Project has unsaved changes');
});
Asset Management
// Get all project assets
const assets = vm.assets;
// Add costume to sprite
const targetId = 'sprite1';
const costumeData = {
name: 'costume1',
dataFormat: 'png',
asset: assetBuffer,
md5ext: 'hash.png'
};
await vm.addCostume(costumeData, targetId);
// Add sound to sprite
const soundData = {
name: 'sound1',
dataFormat: 'wav',
asset: soundBuffer,
md5ext: 'hash.wav'
};
await vm.addSound(soundData, targetId);
// Add backdrop to stage
await vm.addBackdrop('backdrop.png', backdropObject);
Target Management
Accessing Targets
const runtime = vm.runtime;
// Get all targets (sprites + stage)
const allTargets = runtime.targets;
// Get stage
const stage = runtime.getTargetForStage();
// Get all sprites (excluding stage)
const sprites = runtime.getSpriteTargets();
// Get target by ID
const target = runtime.getTargetById('targetId');
// Get target by name
const namedTarget = runtime.getSpriteTargetByName('Sprite1');
// Get currently editing target
const editingTarget = vm.editingTarget;
Target Properties
// Sprite properties
const target = runtime.getSpriteTargetByName('Sprite1');
if (target) {
// Position and appearance
console.log('Position:', target.x, target.y);
console.log('Direction:', target.direction);
console.log('Size:', target.size);
console.log('Visible:', target.visible);
// Identity
console.log('Name:', target.getName());
console.log('Is original:', target.isOriginal);
console.log('Is stage:', target.isStage);
// Current costume/backdrop
console.log('Current costume:', target.currentCostume);
console.log('Costume name:', target.sprite.costumes[target.currentCostume].name);
}
Creating and Managing Clones
// Create a clone
const originalSprite = runtime.getSpriteTargetByName('Sprite1');
if (originalSprite) {
const clone = originalSprite.makeClone();
if (clone) {
runtime.addTarget(clone);
// Position the clone
clone.setXY(100, 50);
clone.setDirection(90);
}
}
// Find all clones of a sprite
const clones = runtime.targets.filter(target =>
!target.isStage &&
!target.isOriginal &&
target.sprite === originalSprite.sprite
);
// Delete a clone
const cloneToDelete = clones[0];
if (cloneToDelete && !cloneToDelete.isOriginal) {
runtime.disposeTarget(cloneToDelete);
}
Variables and Data
Global Variables
const stage = runtime.getTargetForStage();
// Access variables
const variables = stage.variables;
for (const [id, variable] of Object.entries(variables)) {
console.log(`${variable.name}: ${variable.value} (${variable.type})`);
}
// Get specific variable
const scoreVar = stage.lookupVariableByNameAndType('score', '');
if (scoreVar) {
console.log('Score:', scoreVar.value);
scoreVar.value = 100;
}
// Create new variable
stage.createVariable('newVar', 'myVariable', '');
Sprite Variables
const sprite = runtime.getSpriteTargetByName('Sprite1');
if (sprite) {
// Local variables
const localVars = sprite.variables;
// Get/set local variable
const localVar = sprite.lookupVariableByNameAndType('localScore', '');
if (localVar) {
localVar.value = 50;
}
}
Lists
const stage = runtime.getTargetForStage();
// Access lists
const lists = Object.values(stage.variables).filter(v => v.type === 'list');
// Get specific list
const itemsList = stage.lookupVariableByNameAndType('items', 'list');
if (itemsList) {
console.log('List contents:', itemsList.value);
// Modify list
itemsList.value.push('new item');
itemsList.value[0] = 'first item';
}
Block Execution
Starting Scripts
// Start hat blocks
const startedThreads = runtime.startHats('event_whenflagclicked');
console.log(`Started ${startedThreads.length} threads`);
// Start hat blocks with conditions
const broadcastThreads = runtime.startHats('event_whenbroadcastreceived', {
BROADCAST_OPTION: 'message1'
});
// Start specific block stack
const blockId = 'some-block-id';
const target = runtime.getSpriteTargetByName('Sprite1');
const thread = runtime._pushThread(blockId, target);
Manual Thread Management
Advanced / Internal
For more granular control, you can manually push threads and inspect their status.
// Manually start a thread
// _pushThread(blockId, target, opts)
const thread = runtime._pushThread(startBlockId, target, {
stackClick: true // Treat as a stack click (restarts if running)
});
// Thread Status Constants
// 0: RUNNING
// 1: PROMISE_WAIT
// 2: YIELD
// 3: YIELD_TICK
// 4: DONE
if (thread.status === 4) {
console.log('Thread finished');
}