Discuss Scratch

8to16
Scratcher
1000+ posts

Guide to Creating TurboWarp Unsandboxed Extensions

The Guide to Creating TurboWarp Unsandboxed Extensions

* This is the third guide to creating Unsandboxed Extensions on this forum. The second one can be found here. The first one can be found here. I have been given permission by the creator of the second guide to creating unsandboxed extensions.
** Most of this guide is an elaboration of the topics covered in the official sandbox documentation. Some things may be left out for the sake of being more user-friendly. If you have any question, please consult the original documentation before replying.


Section 0 - What Is an Unsandboxed Extension?

This guide was created on the basis that the reader has no former knowledge of sandboxed or unsandboxed extensions. If you already do, feel free to skip to section one.
With that being said, there are two types of Scratch extensions; the sandboxed and unsandboxed varieties. There's several difference between the two of them, such as the former not being allowed to access all APIs and taking a frame to run each time it's called on, but the general consensus is that unsandboxed extensions are far more powerful. Along with that, unsandboxed extensions are simply easier to share, due to the Official TurboWarp Unsandboxed Extension Gallery (which is an amazing site if you haven't checked it out beforehand). Other than that, you can also find all examples of TurboWarp Unsandboxed Extensions here.
That being said, great power comes with great responsibility. There's certain formatting and syntax that TurboWarp extensions require in order to run, just to keep the site safe and secure. Although it may seem daunting at first, it's pretty simple as long as you program carefully and follow a guide.

Section 1.1 - Loading Extensions via File

Originally, this step would consist of creating a local server and hosting our scripts via the command prompt. Luckily, we don't have to deal with that anymore. We can now load extensions via a file, or even through copied text.
Navigate to the TurboWarp Editor. At the bottom of the toolbox, click on the “Add Extension” button. This should look familiar– it's the same button for adding extensions such as Pen to Scratch projects.
There's a lot of options here, however we specifically need to select “Custom Extension,” which should be the last option before the first line.
This opens a menu with 3 tabs. For extension development, the last 2 tabs will prove useful to you. “File” can be used to open JS extension files, and “Text” can load any extension copied to your clipboard. As practice, navigate to “Text” and paste in this extension:

class HelloWorld {
  getInfo() {
    return {
      id: 'helloworld',
      name: 'It works!',
      blocks: [
        {
          opcode: 'hello',
          blockType: Scratch.BlockType.REPORTER,
          text: 'Hello!'
        }
      ]
    };
  }
  hello() {
    return 'World!';
  }
}
Scratch.extensions.register(new HelloWorld());
If you've done everything correctly, the extension should be loaded, and a new extension called “It works!” should appear in the palette.

Section 1.2 - Setting Up Our Script.

You'll likely want to create a new JS (JavaScript) file. If your operating system doesn't have a simple way to do this, create a text file and replace the letters “txt” with “js” and press ok through any pop-ups that may occur. Then open the file. If you have Visual Studio Code or other code editing software installed, use that. Otherwise, you'll have to use a notepad, which is okay, however it won't autofill scripts or catch errors for you.
The other option would be to repeatedly copy-paste our code into the “Text” tab, however this can be quite tedious and doesn't allow use of code editing softwares. Plus, to share the extension, you'll want it in a JS file anyways.
When we get into our script, we have to paste our template:
(function(Scratch) {
  'use strict';
  if (!Scratch.extensions.unsandboxed) {
    throw new Error('[NAME HERE] must run unsandboxed');
  }
  class [ID HERE] {
    getInfo() {
      return {
        id: '[ID HERE]',
        name: '[NAME HERE]',
        blocks: [
          // Palette Here
        ]
      };
    }
  // Block Code Here
  }
  Scratch.extensions.register(new [ID HERE]());
})(Scratch);
Now we need to decide on the name and id of our extension. There's not many limitations on name, however for id, I'd stick to one word, and only English characters. Then, everywhere it says “ID HERE”, replace it with your id, and everywhere it says “NAME HERE”, replace it with the name you chose. Congrats! You already have the basics of your extension down.

Section 2.1 - Adding Blocks To Our Palette.

Now that we have our template down, we can start adding our blocks. I've put a comment (the double /) called “Palette Here” where you can define each of your blocks. Here's the syntax for each block of code:
        {
          opcode: 'hello',
          blockType: Scratch.BlockType.REPORTER,
          text: 'Hello!'
        },
This is the same example given in the official documentation, so we'll have to do a bit of editing. First, let's focus on the variable “opcode”. Rename this to any all lowercase string of characters that describes the block you're trying to make. Remember this, we'll need it later.
As for the blocktype, there's 3 different values we can set this to. These correspond to the 3 types of Scratch blocks.

Block (Scratch.BlockType.COMMAND)
The basic Scratch block, doesn't return an output.
say []

Boolean (Scratch.BlockType.BOOLEAN)
The hexagonal block. Reports true/false values.
<[] = []>

Reporter (Scratch.BlockType.REPORTER)
Any block that returns a number/string output.
((0) + (0))

Unlike the other two, the blocktype does not need to be surrounded in quotes, as it is not a string (don't forget the comma after it, however).
The last variable we need to set it the blockname. Set it to something that represents what the block does. Any inputs, whether it be a dropdown, boolean input, or a menu, must be surrounded by [square brackets].
There's more that can be added to the block syntax to add extra functions, however none are particularly helpful for beginners and will be skipped over in this tutorial.

Section 2.2 - Defining Any Arguments We Made.

If your blocks don't have any arguments (which is rather unlikely), you may skip this section. Otherwise, we have to do a little bit more work so our arguments work. First, we have to add a comma after our “text” variable:
          text: 'Hello!',
Then, we have to add another syntax. The length will vary based on how many arguments your block has, however for simplicity sake I'll offer the syntax for the one argument and two argument versions, and you can fill in the blanks.
          arguments: {
            WHAT YOU PUT IN BRACKET ONE: {
              type: Scratch.ArgumentType.STRING,
              defaultValue: 'Default'
            }
          arguments: {
            WHAT YOU PUT IN BRACKET ONE: {
              type: Scratch.ArgumentType.STRING,
              defaultValue: 'Default'
            },
            WHAT YOU PUT IN BRACKET TWO: {
              type: Scratch.ArgumentType.STRING,
              defaultValue: 'Default'
            }
Now we have to fill in our blanks again. Of course (if you haven't already), replace ‘WHAT YOU PUT IN BRACKET _“ whatever text you put in the brackets in section 2.1 in the corresponding order. Inside each section, you can change the ”defaultValue“ to reflect what you want the block to show in the pallet, and you can change the ”type" to the type of input you want the block to have. There’s quite a few of these, but here's the basic 3:

String (Scratch.ArgumentType.STRING)
Leaves a spot for string to be typed.
say []

Boolean (Scratch.ArgumentType.BOOLEAN)
Leaves a spot for a boolean input.
wait until <>

String (Scratch.ArgumentType.NUMBER)
Leaves a spot for a number to be typed.
move () steps

Great! We have arguments! Some people may be wondering how you can program in dropdown menus, such as those used in the “move to the front/back layer” block. Although it may seem like it's own ArgumentType, it's actually quite a bit more complicated than that. To keep this guide as short as possible, I will not be including dropdown menus in this tutorial, however, further guidance can be found here.

Section 2.3 - Troubleshooting.

It's always a good idea to test your extension as you go to make sure no issues occur. Although code editors can point out JavaScript and JSON errors, TurboWarp is ultimately the program that's best for checking your code. Follow the same instructions as in Section 1.1. If your custom extension is not appearing in the toolbox, or looks incorrect, check your code for the following errors:
  • Forgetting a comma or semicolon. Seriously, those things can mess up your entire script if you're not careful.
  • Forgetting to define an argument.
  • Accidentally using the same opcode for several different blocks.
Another useful trick is pressing Ctrl+Shift+I and looking at the console tab. Most of the time, any errors that occur in your code will show up here. If you're not sure what an error means, you can sometimes Google it for a more descriptive explanation.

Section 3.1 - Programming Our Blocks.

So, you've got your blocks in the pallet now, however they don't do anything. Luckily, Scratch makes it relatively simple to program these blocks using JavaScript. Let's have a look back at our code, specifically where a comment says “Block Code Here”. If it wasn't self-explanatory, that's where we're going to start coding. Let's erase that comment, and replace it with the following script:
  REPLACE(args) {
    return 'Placeholder';
  }
The only thing we have to do to prep this script for free editing is to replace the word “REPLACE” with the opcode for the block you're programming (See? I told you they'd come back eventually). From here, you're free to start programming your block inside the curly brackets. To call upon any argument you may have defined earlier, you just have to add “args.” to the beginning of whatever you named your variable.
With booleans and reporters, of course you'll need to return a value when the block is processed. Luckily, Scratch is programmed so that returning any string, number, or bool will automatically relay that information to the user. If you're making a standard command block, there's no need for a reporter and you can delete the “return” line of code in the script.
Of course, most extensions will have more than one block. In that case, you can simply repeat the example script below the one you already wrote, remembering to replace the opcode. Refreshing the tab you have open with your extension at any point will reload whatever code you wrote and saved, so make sure to save and refresh often.

Section 3.2 - Events and Hat Blocks.
This section is work in progress.

Event Blocks and Hat Blocks are 2 different types of blocks that allow you to control when scripts can run. Note that even though they look the exact same, they are actually different types of blocks.

Event Blocks essentially do nothing on their own - they're just markers for what to run. They are intended to be triggered with other functions or blocks. For example, you may be familiar with this block:

when [space v] key pressed

This block does not do anything on its own as it is an event block. We can create our own block, with similar functionality, by using Scratch.BlockType.EVENT.

class WhenSpaceKeyPressed {
    getInfo() {
      return {
        id: 'eventexampleunsandboxed',
        name: 'Event Block Example',
        blocks: [
          {
            blockType: Scratch.BlockType.EVENT,
            opcode: 'whenSpacePressed',
            text: 'when space key pressed',
            isEdgeActivated: false // required boilerplate
          }
        ]
      };
    }
    // Notice: whenSpacePressed does not have a function defined!
}
document.addEventListener('keydown', (e) => {
    if (e.key === ' ') {
      Scratch.vm.runtime.startHats('eventexampleunsandboxed_whenSpacePressed');
    }
});
Scratch.extensions.register(new WhenSpaceKeyPressed());
When you press Space, any scripts attached to the
when space key pressed :: #0FBD8C hat
block will be ran.

Section 3.3 - API Requests And Asynchronous Blocks.

Lots of extensions require access to different APIs across the web. Requesting information from a website using TurboWarp is actually very easy, however somewhat limited. The biggest 2 limiting factors are that:
  1. TurboWarp will ask from permission from the user you try to access any domain.
  2. Certain domains, such as the Scratch API, are off-limits to the likes of TurboWarp.
With that being said, API requests are still a vital tool in the Scratch Extension making toolbox. To request information from a website, use the script:
const response = await Scratch.fetch('url');
and then can read it as text with the script:
const textData = await response.text();
or split it up into a JSON with the script:
const jsonData = await response.json();
It's also important that sending out an API request will make your block asynchronous. This means that your block cannot complete its action in under a frame. To mark a block as asynchronous, ad the string “async” before your opcode, like so:
  async REPLACE(args) {
    return 'Placeholder';
  }
Finally, as you'll read later, TurboWarp syntax really doesn't like empty returns on reporters or booleans. As such, it's always a good idea to add a script such as this around an API request:
try {
// CODE HERE
} catch (error){
return '';
}

Section 3.4 - Keeping to TurboWarp Formatting.

If you're programming your extension all for yourself, TurboWarp formatting won't have too much of an affect on your code. However, if you want to submit your extension to the Official TurboWarp Unsandboxed Extension Gallery, there's a few rules you have to follow. For one, most if not all copyrighted material is usually difficult to add to an extension, there's some stuff you can fill out to gain access to it, however that's legal stuff and legal stuff is boring. Basically, stay away from copyrighted material or code snippets if at all possible.
As for the code itself, GarboMuffin prefers if you use semicolons and double spaces (which is absolutely preposterous but it what it is I guess).
GarboMuffin will also usually turn down extensions that can be deemed dangerous, such as two extensions that are currently in limbo, Local Storage and JavaScript Execution. As long as your extension isn't dangerous to the user, you shouldn't have to worry about that.
Any libraries that the extension uses must be embedded into the code of the extension, and the extension should be able to work on all browsers.
Extensions must not return empty in a boolean or reporter. An empty string (such as ‘ ’) is acceptable, but it needs to return something.

Section 4.1 - Customizing Your Extension.

You may have noticed that up until now your extension has been looking rather bland. Not to worry, this can be fixed using some built-in APIs! For one, we can change the color of our extension. Using a color to hex code converter online, you can get the hex codes for the outlines and the block colors of your extensions. We can insert some variables inside the getInfo part of our original template:
... getInfo() {
    return {
      id: 'yourextension',
      name: 'Your Extension',
      color1: '#000000', // Block Color
      color2: '#000000', // Outline Color
      color3: '#000000', // Dropdown color
      blocks: ...
Simply insert the hex codes next to the comments to color your blocks. You may notice that the category icon on the toolbar changes to whatever you set your block color to; this is completely intentional. However, there is a process to add an icon to your extension. First, make a square icon (it doesn't need to literally be a square, however the width must be the same as the height). Then, use a program such as ezgif to convert the png of this icon into base64. After you do that, you simply plug in 2 lines of code:
... const icon = "insert very long base64 string here";
class YourExtension {
  getInfo() {
    return {
      id: 'yourextension',
      name: 'Your Extension',
      color1: '#000000',
      color2: '#000000',
      color3: '#000000',
      menuIconURI: icon, // Don't forget this!
      blocks: ...
While we're at it, you may want to include a link to the documentation of your extension. This is by no means required nor outright encouraged, but can be a neat professional touch to your extension. Or, you could just make it a link to a rickroll. Your choice:
... getInfo() {
    return {
      id: 'yourextension',
      name: 'Your Extension',
      color1: '#000000',
      color2: '#000000',
      color3: '#000000',
      menuIconURI: icon,
      docsURI: 'url', // Replace the text with the link to your documentation, perhaps.
      blocks: ...

Section 5 - Finishing Statements.

Congratulations on making an unsandboxed extension! What's outlined in this tutorial is by no means the full power of the unsandboxed extension maker, however, as you experiment more, you'll find more interesting features and tricks to level up your extensions. While I'd love to share how to share your extension, it's unfortunately against Scratch TOS to share links with outside sources. With that being said, you can freely post the JS of your extension in the forums, or other places.

Topics to be added to this guide:
  • Scratch.Cast
  • Menus and dropdowns

    If any other topics are misrepresented, missed, or need to be elaborated on, please feel free to let me know as a reply to this post.

Last edited by 8to16 (Oct. 2, 2024 09:55:42)


^^^^^ Below this line is a signature. It doesn't have anything to do with the post above.
8to16
Scratcher
1000+ posts

Guide to Creating TurboWarp Unsandboxed Extensions

BigNate469
Scratcher
1000+ posts

Guide to Creating TurboWarp Unsandboxed Extensions

class HelloWorld {
  getInfo() {
    return {
      id: 'helloworld',
      name: 'It works!',
      blocks: [
        {
          opcode: 'hello',
          blockType: Scratch.BlockType.REPORTER,
          text: 'Hello!'
        }
      ]
    };
  }
  hello() {
    return 'World!';
  }
}
Scratch.extensions.register(new HelloWorld());
It couldn't hurt to highlight it.
Use [code=javascript] or [code=js]

This signature is designed to be as useful as possible.
How to make a signature & other useful info about them
The Official List of Rejected Suggestions (TOLORS)
The Announcements Directory
Lesser-known Scratch URLs: https://scratch-mit-edu.ezproxy.canberra.edu.au/discuss/topic/542480/
Why @Paddle2See's responses are so often identical: https://scratch-mit-edu.ezproxy.canberra.edu.au/discuss/topic/762351/

Ads Useful projects:
Raycaster & Maze 1.4.1 | Don't Break The Ice | Procedurally Generated Terrain | Basic Trigonometry | Comparing the fastest list sorters on Scratch

“if nobody can learn the programming language, it's just gibberish that does math.” -me, in a forum post

The original name of “loves” was “love-its”. Technically speaking, this hasn't changed.

© @BigNate469, some rights reserved
8to16
Scratcher
1000+ posts

Guide to Creating TurboWarp Unsandboxed Extensions

BigNate469 wrote:

(#3)
class HelloWorld {
  getInfo() {
    return {
      id: 'helloworld',
      name: 'It works!',
      blocks: [
        {
          opcode: 'hello',
          blockType: Scratch.BlockType.REPORTER,
          text: 'Hello!'
        }
      ]
    };
  }
  hello() {
    return 'World!';
  }
}
Scratch.extensions.register(new HelloWorld());
It couldn't hurt to highlight it.
Use [code=javascript] or [code=js]
I've added syntax highlighting to most (if not all) of the guide, if there's anything left let me know.

^^^^^ Below this line is a signature. It doesn't have anything to do with the post above.
8to16
Scratcher
1000+ posts

Guide to Creating TurboWarp Unsandboxed Extensions

thanks for the sticky

^^^^^ Below this line is a signature. It doesn't have anything to do with the post above.
endyourenite
Scratcher
100+ posts

Guide to Creating TurboWarp Unsandboxed Extensions

8to16 wrote:

thanks for the sticky
Congrats

found something silly
Suggestions I own:
Scrollable What I have been doing
that's all….

Posts I found Funny:

A-MARIO-PLAYER wrote:

WHEN TOPIC FALLS OFF FRONT PAGE
  1. (src)
    Bump it.
  2. Get people to talk about this weird suggestion.
  3. Watch as the number of posts on this topic expands.
  4. Leave any rickrolls or evil kumquats behind.

(ignore this)
my first ever post on scratch forums
Anyways, just stop reading this, I am only a silly forumer.
But what do you want from me?




;
BigNate469
Scratcher
1000+ posts

Guide to Creating TurboWarp Unsandboxed Extensions

Interesting that the old one isn't unstickied yet.
I soooo want to destroy the 60-second rule

This signature is designed to be as useful as possible.
How to make a signature & other useful info about them
The Official List of Rejected Suggestions (TOLORS)
The Announcements Directory
Lesser-known Scratch URLs: https://scratch-mit-edu.ezproxy.canberra.edu.au/discuss/topic/542480/
Why @Paddle2See's responses are so often identical: https://scratch-mit-edu.ezproxy.canberra.edu.au/discuss/topic/762351/

Ads Useful projects:
Raycaster & Maze 1.4.1 | Don't Break The Ice | Procedurally Generated Terrain | Basic Trigonometry | Comparing the fastest list sorters on Scratch

“if nobody can learn the programming language, it's just gibberish that does math.” -me, in a forum post

The original name of “loves” was “love-its”. Technically speaking, this hasn't changed.

© @BigNate469, some rights reserved
TheCreatorOfUnTV
Scratcher
1000+ posts

Guide to Creating TurboWarp Unsandboxed Extensions

How do you create C blocks?

This is the start of my signature. Ctrl+Shift+Down to see all of it.

Check out my about me project here! (I'm allowed to advertise here)
1st post
1000th post

;
8to16
Scratcher
1000+ posts

Guide to Creating TurboWarp Unsandboxed Extensions

TheCreatorOfUnTV wrote:

(#8)
How do you create C blocks?
Not in the guide yet

^^^^^ Below this line is a signature. It doesn't have anything to do with the post above.

Powered by DjangoBB