To learn how to write custom extensions, see our documentation:
- https://docs.turbowarp.org/development/extensions/introduction
- https://docs.turbowarp.org/development/extensions/unsandboxed
- https://docs.turbowarp.org/development/extensions/compatibility
- https://docs.turbowarp.org/development/extensions/better-development-server
- and more in the sidebar of those pages
The rest of this page is about this specific repository.
These categories of extensions are highly discouraged:
- Broad "Utilities" extensions. Break them up into multiple extensions instead. See TurboWarp#674 for discussion.
- Extensions that are very similar to existing ones. Consider modifying the existing extension instead.
- Very niche extensions. You can write the extension for yourself, then import it as a file instead without needing us to review.
- Extensions whose primary purpose is monetization. It isn't in the spirit of a free and open source project.
- Joke extensions. Things that are funny to you are not funny to everyone, especially when we get bug reports about it.
Some extensions were added before these guidelines existed. We're trying to enforce them moving forward.
TurboWarp's threat model is that loading a project in the editor should be no more dangerous than opening a PowerPoint. Projects can't affect anything outside of the editor without consent from the user. We offer a bug bounty to people who find and report security bugs in merged extensions. Most guardrails can disappear once the project is packaged.
Evaluating project-supplied JavaScript using eval(), new Function(), or other methods is not allowed.
Every merged extension is more code that we are expected to maintain indefinitely, even if you disappear. Broken extensions mean that real projects by real people no longer work. If the renderer is rewritten one day, we will have to ensure that extensions like Clipping & Blending, RGB Channels, and Augmented Reality still work. That's not a small commitment.
We're all volunteers who all have lives outside of TurboWarp extensions. Many have full time jobs or are full time students. We'll get to you as soon as we can, so please be patient.
This repository contains a custom development server. Extension source code goes in the extensions folder. For example, an extension placed at extensions/hello-world.js would be accessible at http://localhost:8000/hello-world.js when you start the development server.
New extensions should be added in a user folder. You can name your folder anything you want; common choices are your GitHub username or your Scratch username. You can largely choose whatever you want. These folders are just for organization; other people are still allowed to edit your extension. Folder name changes are only granted in rare circumstances, so please get it right the first time.
We are not lawyers. This is not legal advice.
Everything in this repository must be available under an open source license. You can use any license you want, but we recommend using the Mozilla Public License verison 2.0 for all new extensions.
All extension are included in TurboWarp Desktop which is licensed under the GPLv3. Thus, you need to use a license that is compatible with GPLv3. This excludes:
- Creative Commons Attribution-ShareAlike licenses prior to version 4.0
- This includes user-generated content on the Scratch website which uses version 2.0.
- This includes StackOverflow posts contributed before 2018-05-02 which use several different versions.
- "No derivatives" licenses such as Creative Commons Attribution-NoDerivs
- "Non commercial" or "personal use only" licenses such as Creative Commons Attribution-NonCommercial
Once you choose a license for your extension, find its SPDX identifier from this table. The "FSF Free/Libre?" and "OSI Approved?" columns should both contain "Y". You'll need to include the identifier in the extension's metadata.
All extensions need a metadata comment at the start of the file, before any code. This section gets read by a script, so make sure to follow the format closely.
// Name: My Cool Extension
// ID: extensionid
// Description: Does a very cool thing. This must have punctuation at the end!
// By: GarboMuffin <https://scratch.mit.edu/users/GarboMuffin/>
// Original: TestMuffin
// License: MPL-2.0You must use line comments; block comments /* */ will not work. These fields are REQUIRED:
Nameappears on the website and in the library. It should be similar to thenamereturned by getInfo().IDmust be identical to theidreturned by getInfo().Descriptionappears on the webstie and in the library.Licensedescribes the license that the extension's code is under. It must be a valid SPDX license expression. For the Mozilla Public License verison 2.0 that we recommend, the identifier isMPL-2.0.
By allows you to credit yourself. Original is used if the extension is based on another person's work. They both use the same format of Name or Name <https://scratch.mit.edu/users/username>. Links to places other than Scratch are not allowed at this time. You can repeat both of these as many times as needed, just add another // By: ... comment.
Extensions should support being translated into any language. The development server and volunteer translators will handle the hard part. The developer's job is to use Scratch.translate() for any string that should be translated, such as block text or labels. Here's some examples to explain the idea:
// For simple strings that can be understood without additional context,
// call Scratch.translate with your string directly:
Scratch.translate("stage width")
// The translation system handles block inputs properly, so use the same process:
Scratch.translate("move [STEPS] steps")
// If your string needs some context to understand properly, call
// Scratch.translate with an object:
Scratch.translate({
default: "map",
description: "A map in the computer science sense. Maps keys to values. Sometimes called a dictionary."
})
// If your string needs to fill in a value that isn't known until runtime,
// use a placeholder. Don't try to concatenate strings yourself as that is
// confusing and not all languages have the same grammar structure.
Scratch.translate({
default: "Hello, {name}!",
description: "{name} is replaced with the user's name"
}, {
name: "world"
})All translators will see is the extension's name and description, the English text, and any description you add.
First, try to avoid using third-party libraries if you can. If you must, we have a custom dependency system for extensions called Scratch.external. It's somewhat in flux but the type definitions should make it clear enough how to use.
Add your extension's path (without extensions/ and without .js) to extensions/extensions.json. The order of that list determines the order of the library. Try to put it next to related related extensions if possible.
New extensions do not need an image for the library, but they are encouraged. Put any image in the images folder with the same folder name and file name but different file extension as the extension's JavaScript. For example, if your extension's code is in extensions/TestMuffin/fetch.js, the image would be images/TestMuffin/fetch.svg or images/TestMuffin/fetch.png. The homepage generator will find this file automatically. Images are displayed in a 2:1 aspect ratio. SVG (preferred), PNG, or JPG are accepted. PNG or JPG should be 600x300 in resolution. Add attribution to images/README.md for yourself and anything not made by you. Images submitted to this repository must be licensed under the GNU General Public License version 3. Avoid text if possible since these images can't be translated.
Most extensions shouldn't need external documentation -- it should be obvious what to do just by looking at the blocks. That said, some do need more explanation. Documentation is written in markdown and placed in the docs folder with a similar layout to images. For example, documentation for extensions/TestMuffin/fetch.js would be docs/TestMuffin/fetch.md. Our version of markdown is extended to allow rendering scratchblocks. Just look at the existing documentation for syntax examples. It's not a perfect experience: block colors have to be manually copied, and icons aren't supported, but it's better than what we had before. Once you put your markdown there, you can set a docsURI like https://extensions.turbowarp.org/TestMuffin/fetch.
Static resources such as example resources used by extensions go in the website folder. They are copied to the final website without any additional processing.
If you use our development server, TypeScript aware editors such as Visual Studio Code will give you smart autocomplete suggestions for most Scratch and extension APIs based on @turbowarp/types and @turbowarp/types-tw. Note that these types are not perfect; some methods are missing or incorrect. Please report any issues you find.
If you encounter a TypeScript error, as long as you understand the error, feel free to add // @ts-ignore, // @ts-expect-error, or just ignore the error entirely. We currently do not require extensions to pass type checking.
All pull requests are automatically checked by a combination of custom validation scripts, ESLint, and Prettier. Don't worry about passing these checks on the first attempt -- most don't. That's why we have these checks. Click on the failing check to view more details about what failed. All checks will tell you which file failed and usually some additional explanation.
Our custom validation scripts do things like making sure you have the correct headers at the start of your extension and that the images are the right size. Your extension must pass validation. The errors will tell you what file failed and why to help you fix it. You can run these checks locally with:
npm run validateESLint detects common JavaScript errors such as referencing non-existant variables as well as extension-specific issues such as fetching a resource without using Scratch.fetch(). Your extension must pass linting. You are allowed to disable ESLint warnings and errors, with proper justification. You can run ESLint locally with:
npm run lintWe use Prettier to ensure consistent code formatting. You can format your pull request by commenting "!format" on it then waiting a couple minutes for the bot to process it, or you can format your code locally with:
npm run formatExtension review: REVIEW_PROCESS.MD.