TurboWarp Extension for Cloud Key-Value Storage
View the full REST API documentation for advanced usage
This extension allows you to save and load data from the cloud in your TurboWarp projects. Perfect for saving high scores, user preferences, or sharing data between projects!
Go to turbowarp.org/editor
Click the "Extensions" button (puzzle piece icon) in the bottom-left corner
Scroll down and click "Custom Extension", then either:
(function(Scratch) {
'use strict';
const apiBase = 'https://kvstore.quincycoders.click';
const THROTTLE_MS = 1000;
const lastCallTime = {};
const cache = {};
function throttle(key) {
const now = Date.now();
if (lastCallTime[key] && now - lastCallTime[key] < THROTTLE_MS) {
return false;
}
lastCallTime[key] = now;
return true;
}
function getCached(key, defaultValue) {
return cache[key] !== undefined ? cache[key] : defaultValue;
}
function setCache(key, value) {
cache[key] = value;
return value;
}
class QCCloudVariables {
getInfo() {
return {
id: 'qccloudvariables',
name: 'QC cloud variables',
blocks: [
{
opcode: 'getAllKeys',
blockType: Scratch.BlockType.REPORTER,
text: 'get all keys from [TOPIC]',
arguments: {
TOPIC: {
type: Scratch.ArgumentType.STRING,
defaultValue: 'myTopic'
}
}
},
{
opcode: 'getValue',
blockType: Scratch.BlockType.REPORTER,
text: 'get [KEY] from [TOPIC]',
arguments: {
KEY: {
type: Scratch.ArgumentType.STRING,
defaultValue: 'myKey'
},
TOPIC: {
type: Scratch.ArgumentType.STRING,
defaultValue: 'myTopic'
}
}
},
{
opcode: 'setValue',
blockType: Scratch.BlockType.COMMAND,
text: 'set [KEY] to [VALUE] in [TOPIC]',
arguments: {
KEY: {
type: Scratch.ArgumentType.STRING,
defaultValue: 'myKey'
},
VALUE: {
type: Scratch.ArgumentType.STRING,
defaultValue: 'myValue'
},
TOPIC: {
type: Scratch.ArgumentType.STRING,
defaultValue: 'myTopic'
}
}
},
{
opcode: 'deleteKey',
blockType: Scratch.BlockType.COMMAND,
text: 'delete [KEY] from [TOPIC]',
arguments: {
KEY: {
type: Scratch.ArgumentType.STRING,
defaultValue: 'myKey'
},
TOPIC: {
type: Scratch.ArgumentType.STRING,
defaultValue: 'myTopic'
}
}
}
]
};
}
getAllKeys({ TOPIC }) {
const cacheKey = `getAllKeys:${TOPIC}`;
if (!throttle(cacheKey)) return getCached(cacheKey, '[]');
const url = `${apiBase}/${encodeURIComponent(TOPIC)}`;
return Scratch.fetch(url)
.then(response => response.json())
.then(data => setCache(cacheKey, JSON.stringify(data.keys || [])))
.catch(() => getCached(cacheKey, '[]'));
}
getValue({ KEY, TOPIC }) {
const cacheKey = `getValue:${TOPIC}:${KEY}`;
if (!throttle(cacheKey)) return getCached(cacheKey, '');
const url = `${apiBase}/${encodeURIComponent(TOPIC)}/${encodeURIComponent(KEY)}`;
return Scratch.fetch(url)
.then(response => response.json())
.then(data => setCache(cacheKey, data.value || ''))
.catch(() => getCached(cacheKey, ''));
}
setValue({ KEY, VALUE, TOPIC }) {
if (!throttle(`setValue:${TOPIC}:${KEY}`)) return;
const url = `${apiBase}/${encodeURIComponent(TOPIC)}/${encodeURIComponent(KEY)}/${encodeURIComponent(VALUE)}`;
return Scratch.fetch(url, { method: 'PUT' }).catch(() => {});
}
deleteKey({ KEY, TOPIC }) {
if (!throttle(`deleteKey:${TOPIC}:${KEY}`)) return;
const url = `${apiBase}/${encodeURIComponent(TOPIC)}/${encodeURIComponent(KEY)}`;
return Scratch.fetch(url, { method: 'DELETE' }).catch(() => {});
}
}
Scratch.extensions.register(new QCCloudVariables());
})(Scratch);
Get a list of all keys stored in a topic. Returns a JSON array of key names.
| Input | Description |
|---|---|
TOPIC | The topic/namespace to list keys from |
Retrieve a value from the cloud. Returns an empty string if the key doesn't exist.
| Input | Description |
|---|---|
KEY | The name of the value to retrieve |
TOPIC | The topic/namespace (like a folder for your data) |
Save a value to the cloud. The value will automatically expire after 24 hours.
| Input | Description |
|---|---|
KEY | The name for your value |
VALUE | The value to store |
TOPIC | The topic/namespace (like a folder for your data) |
Delete a value from the cloud.
| Input | Description |
|---|---|
KEY | The name of the value to delete |
TOPIC | The topic/namespace containing the key |
Here's how to create a simple high score system:
when green flag clicked
set [current score v] to (get [highscore] from [mygame])
if <(current score) = []> then
set [current score v] to [0]
end
say (join [High Score: ] (current score))
when I receive [game over v]
if <(score) > (current score)> then
set [highscore] to (score) in [mygame]
say [New High Score!]
end
alice-spacegame.
highscore, level, or username""| Problem | Solution |
|---|---|
| Get block returns empty | The key might not exist or may have expired. Check spelling and topic name. |
| Data disappears | Values expire after 24 hours automatically. |
| Extension not loading | Make sure you're using TurboWarp. Custom extensions aren't supported on scratch.mit.edu. |
| Blocks are slow | Network requests take time. Use "wait until" blocks or check the value in a loop. |