[V2.0] Blueprint Roblox Editor : Visual programming tools for Luau

BlueprintRobloxEditor

BRE (Blueprint Roblox Editor) is a blueprint editor for creating Roblox games using a plugin within Roblox Studio.

The project uses Rojo, a VS Code extension for managing Roblox projects.

DevForum post: [V2.0] Blueprint Roblox Editor : Visual programming tools for Luau

Latest release: https://github.com/Program132/BlueprintRobloxEditor/releases/tag/V2.0



Installation

You can clone the repository and build the plugin yourself:

git clone https://github.com/Program132/BlueprintRobloxEditor.git
cd BlueprintRobloxEditor
rojo build -o BlueprintRobloxEditor.rbxmx

You can also download the latest release!

Once you have the plugin, copy it to your Roblox plugins folder:

Windows (PowerShell):

Copy-Item "BlueprintRobloxEditor.rbxmx" "$env:LOCALAPPDATA\Roblox\Plugins\BlueprintRobloxEditor.rbxmx"
  • Windows path: %LOCALAPPDATA%\Roblox\Plugins\
  • Mac path: /Users/$USER/Library/Application Support/Roblox/Versions/<version>/Plugins

Managing Projects

Roblox plugins have limited access to your local file system, so project management works slightly differently:

Saving a Project

  1. Click the Save Project button in the top bar.
  2. The plugin will automatically save your state inside Roblox Studio’s local plugin settings so you don’t lose progress if you close Studio.
  3. For backing up to an actual file: The plugin creates a StringValue named __BlueprintExport under the Workspace.
  4. Select __BlueprintExport in the Explorer, go to the Properties panel, and copy the text in the Value property.
  5. Paste this text into a new text file and save it as my_project.json on your computer.

Loading a Project

  1. To load an external project file, click Open Project in the top bar.
  2. A native file dialog will appear. Select your .json project file.
  3. The Blueprint Editor will now load and render the project!

Controls & Shortcuts

Action Control
Add Node Right-click on the canvas to open the node menu.
Move Node Drag the header of any node.
Connect Pins Drag from an output pin to an input pin.
Delete Node Hover over a node and press Delete or Backspace.
Pan Canvas Right-click or Middle-click and drag the background.
Zoom Use the Mouse Wheel to zoom in/out.

Creating Custom Nodes

Adding a new node to the editor is simple. Each node is a Luau module located in src/shared/nodes/.

1. Node Structure

Create a new file (e.g., MyNode.luau) and follow this template:

local Node = require(script.Parent.Parent.Node) -- Adjust path
local NodeType = require(script.Parent.Parent.NodeType)
local IO = require(script.Parent.Parent.IO)

local MyNode = {}
MyNode.__index = MyNode
setmetatable(MyNode, Node) 

function MyNode.new()
    -- NodeType.METHOD for nodes with Exec pins, NodeType.FUNCTION for pure data nodes
    local self = Node.new("My Node Name", NodeType.METHOD)
    setmetatable(self, MyNode)
    
    -- Customize appearance
    self:editColor(Color3.fromRGB(100, 150, 200))
    
    -- Add pins
    self:addInput(IO.Input.new("Exec", "")) -- Exec pin
    self:addInput(IO.Input.new("Text", '"Hello"'))
    self:addOutput(IO.Output.new("Exec", ""))
    
    return self
end

2. Examples

Example A: Print (Method Node)

A node that performs an action and has execution flow.

function MyNode.new()
    local self = Node.new("Print", NodeType.METHOD)
    setmetatable(self, MyNode)
    self:addInput(IO.Input.new("Exec", ""))
    self:addInput(IO.Input.new("Text", '"Hello"'))
    self:addOutput(IO.Output.new("Exec", ""))
    return self
end

function MyNode:toLuau()
    local text = self:getInput("Text").Value
    return `print({text})\n`
end

Example B: Add (Pure Data Node)

A node that returns a value to be used by other nodes, without execution pins.

function Add.new()
    local self = Node.new("Add", NodeType.FUNCTION)
    setmetatable(self, Add)
    self:addInput(IO.Input.new("A", "0"))
    self:addInput(IO.Input.new("B", "0"))
    self:addOutput(IO.Output.new("Result", "0"))
    return self
end

function Add:toLuau()
    local a = self:getInput("A").Value
    local b = self:getInput("B").Value
    
    -- We store the expression in the output so other nodes can use it
    self:getOutput("Result").Value = `({a} + {b})`
    
    -- Pure data nodes usually return an empty string because 
    -- they don't generate a standalone line of code.
    return ""
end

3. Registering the Node

Once created, you must register it in src/plugin/init.server.luau and src/plugin/ui/NodeMenu.luau.

In init.server.luau:

Add your node to the Nodes table:

local Nodes = {
    ...
    ["My Node Name"] = require(Shared.nodes.MyNode),
}

In NodeMenu.luau:

Add it to the NODES_DATA table so it appears in the right-click menu:

local NODES_DATA = {
    {
        category = "My Category",
        color = Color3.fromRGB(100, 150, 200),
        items = {
            { id = "My Node Name", name = "My Custom Node", type = "Method" }
        }
    },
    ...
}
18 Likes

This will be very useful for novices, but personally, I wouldn’t use it because I don’t understand blocks. I learned Luau the hard way.

2 Likes

As you wish, but in essence, nodes/blocks are like lines, well, not completely, but there you go, it’s not very different, but you do as you like, as long as you enjoy it :)!

1 Like

:eyes:

Example 1


Example 2


Update V0.15 is now available, featuring bug fixes, variables, and new nodes (those from the math library)! See below for update details.

Fixs

New

New nodes:

  • Add renamed Addition
  • Subtraction
  • Multiplication
  • Division
  • Squareroot
  • Logarithm
  • Exponential
  • Cos
  • Sin
  • Tan
  • Cosh
  • Sinh
  • Tanh
  • Arcos
  • Arcsin
  • Arctan
  • Absolute (abs)

New format to follow to create your own nodes (color is not mandatory, you can remove it):

{
    "color": [255,255,255],
    "type": "METHOD",
    "inputs": {
        "node_name": {
            "defaultValue": "<default value>"
        }
    },
    "outputs": [
    "output_name"
    ]
}

Variables:

  • Can be saved in you project file (.json) when you’re clicking on the button “Save”.
  • Can be loaded from a .json file.
  • SET node to update the value
  • GET node to get the value

Example


2 Likes

I tried it and found it very useful. However, as I mentioned before, I prefer writing code, so I won’t be using it further. It’s a good plugin for beginners, and I’m not criticizing it; I just don’t think it’s the right fit for me.

3 Likes

No worries that’s fine :slight_smile: ! I’m happy some people find it useful :>

2 Likes

Fixs

News

New Math nodes (from math lib):

  • arctangent2
  • ceiling
  • degree
  • extract mantissa & exponent
  • floating point remainder
  • floor
  • load exponent
  • log10
  • min
  • max
  • modulo fractional
  • pi
  • positive infinity (highest value)
  • power
  • radians
  • random seed

New String nodes (from string lib):

  • concat
  • upper
  • lower
  • length
  • byte
  • char
  • reverse
  • find
  • repeat
  • replace

New format, you can now set the “title” of the node:

{
    "title": "sqrt",
    "type": "FUNCTION",
    "inputs": {
        "a": {
            "defaultValue": "0"
        }
    },
    "outputs": ["result"]
}

Example



1 Like

News

New node “IF”:

  • Take one condition (as boolean) in inputs
  • Return 3 EXEC output: when the conditon is true, when the condition is false (else) and when the “if node is over” (next to end)

New node “WHILE”:

  • Take one condition as input
  • Return 2 EXEC: one about the loop body, last one once the condition is true (next to end)

New node “FOR” (Range):

  • Take one variable (only the name, not a variable you created) as input
  • Take number for start
  • Take number for end
  • Take number for step

New nodes to convert:

  • tonumber
  • tostring

New nodes about booleans:

  • and
  • not
  • <=
  • >=
  • ==
  • ~=
  • <
  • >

New way to create custom statement (it’s if definition):

{
    "color": [200,200,200],
    "type": "METHOD",
    "inputs": {
        "condition": {
            "defaultValue": "true"
        }
    },
    "outputs": [],
    "exec": ["True", "False", "Continue"]
}

Example

if



while



for (range)


Next updates

  • Arrays & Dict
  • Custom function
  • Multiple scripts
  • Custom events (from Roblox)
2 Likes

I reworked the backend system to address new issues I was facing, such as handling multiple events and enabling the creation of custom functions.
By the way, you can now have more than one “script”/graph in your project!

News

Fixs / Issues solved

Rework backend + new elements about frontend @Program132

Format

Make sure to follow the format, now title is mandatory !

{
    "type": "METHOD",
    "title": "My Custom Node",
    "inputs": {
        "input_name": {
            "defaultValue": "default_input_value"
        }
    },
    "outputs": [
        "output_name"
    ]
}

You can set the color of you node by the way:

{
    "type": "METHOD",
    "title": "My Custom Node",
    "color": [255,23,56],
    "inputs": {
        "input_name": {
            "defaultValue": "default_input_value"
        }
    },
    "outputs": [
        "output_name"
    ]
}

Table (Arrays + Dict)

You can create arrays using table nodes:

  • Construct Table return an empty array
  • Table Insert insert the element given in the array
  • Table Length return the length of the array

You can create dict using table nodes:

  • Construct Table return an empty dict
  • Table SET insert the element given in the dict, with the key given
  • Table Length return the length of the dict
  • Table GET return the value of the key given

Events

Two events got added:

  • PlayerAdded
  • CharacterAdded (from PlayerAdded)

Variables

GET node got an update, now it’s a FUNCTION node, not METHOD.

General

Added two more nodes:

  • warn: like print but for warnings
  • error: like print but for errors

Values nodes (can use them to specifiy the real value if you want to simulate):

  • string
  • int
  • number
  • boolean

Example





Issues / Fixs

Due to a lot of problem with String, I decided to remove the automatic detection of values type, it means now you have to put " yourself or you can use the String node.

News

New node “Instance New” : you can create instance from Roblox Studio like :

  • Part
  • Model
  • etc.
    Make sure to specify the classname (part, model etc.) and the parent.

New node “Get Service” : you can call services from Roblox like Workspace, ReplicatedStorage, ServerScriptService and more !

New nodes to get & set a property of an instance, like get / set the name of a part !

New nodes to find a child from an instance (FindFirstChild, FindFirstChildOfClass, FindFirstChildWhichIsA, WaitForChild), I added as well IsA which return a boolean.

New async nodes:

  • task.wait
  • task.delay
  • task.defer
  • task.spawn

New events :

  • InputBegan
  • InputEnded

New enums:

  • KeyCode
  • UserInputType

Example














This is very cool, good work here. I can see this being useful for beginners.

1 Like

Issues Solved / Fixs

Enums - New type for nodes @Program132

News

New node type to create custom enums:

{
    "type": "ENUM",
    "title": "Enum title",
    "color": [200,200,200],
    "inputs": {
        "input_name": {
            "defaultValue": "default_input_value"
            "values": [
                "value1",
                "value2",
                "value3"
            ]
        }
    },
    "outputs": [
        "output_name"
    ]
}

Enums added:

  • Material
  • HumanoidStateType

Moreover, you can add a description to your nodes :

{
    "type": "FUNCTION",
    "title": "Node",
    "description": "My description",
    "inputs": {
        "from": {
            "defaultValue": ""
        },
        "to": {
            "defaultValue": ""
        }
    },
    "outputs": [
        "Output 1"
    ]
}

New nodes about Vector3 :

  • Builder: create an Vector3 from x,y,z.
  • Add: addition of two vectors
  • Sub: substraction of two vectors
  • Mul: multiplication of two vectors
  • Div: division of two vectors
  • Get Vector3 Components : Get x,y,z from a vec3

Added CFrame nodes:

  • CFrame Builder (CFrame.new)
  • CFrame Angles (CFrame.Angles)
  • CFrame Look At (CFrame.lookAt)
  • Ge CFrame Position (cframe.Position)

Added async nodes:

  • task.wait
  • task.defer
  • task.delay
  • task.spawn

Added two events (from BasePart):

  • Touched
  • TouchEnded
  • RenderStepped from RunService
  • Heartbeat from RunService

Example

Enum Type

Vector3


CFrame


Touched event


News

New statement nodes:

  • For in pairs
  • For in ipairs

New nodes from Color3:

  • new
  • fromHSV
  • fromRGB

New nodes from BrickColor:

  • New
  • Random
  • To RGB

New enums nodes:

  • EasingDirection
  • EasingStyle

New nodes for TweenService:

  • TweenInfo.new
  • Create
  • Cancel
  • Play

New nodes for Local Player & Humanoid:

  • Get Local Player
  • Get Character
  • Get Humanoid
  • Get Humanoid State
  • Humanoid Take Damage
  • Get Humanoid State

UDim2 nodes:

  • UDim2.new
  • UDim2.fromScale
  • UDim2.fromOffset

New nodes for RemoteEvents:

  • FireClient
  • FireServer

New events:

  • Died (Humanoid)
  • HealthChanged (Humanoid)
  • StateChanged (Humanoid)
  • ChildAdded
  • ChildRemoved
  • Changed (Instance, when a property is updated)
  • OnServerEvent
  • OnServerInvoke

Example

For In


Color3


Tweens


Player & Humanoid


ChildAdded


Remote Events


Kill Brick Example


{
    "version": "2.0",
    "scripts": [
        {
            "id": "script-1763990649166",
            "name": "Main",
            "nodes": [
                {
                    "id": "node-1763990657293",
                    "name": "touched",
                    "type": "EVENT",
                    "x": 49,
                    "y": 350.609,
                    "inputs": {}
                },
                {
                    "id": "node-1763990664191",
                    "name": "getproperty",
                    "type": "FUNCTION",
                    "x": -290,
                    "y": 528.609,
                    "inputs": {
                        "PropertyName": "Parent"
                    }
                },
                {
                    "id": "node-1763990677640",
                    "name": "isa",
                    "type": "FUNCTION",
                    "x": 50,
                    "y": 543.609,
                    "inputs": {
                        "className": "Player"
                    }
                },
                {
                    "id": "node-1763990729292",
                    "name": "if",
                    "type": "METHOD",
                    "x": 398,
                    "y": 353.609,
                    "inputs": {}
                },
                {
                    "id": "node-1763990762539",
                    "name": "start",
                    "type": "EVENT",
                    "x": -34,
                    "y": 45.609,
                    "inputs": {}
                },
                {
                    "id": "node-1763990772704",
                    "name": "set",
                    "type": "METHOD",
                    "x": 643.565,
                    "y": 60.063,
                    "inputs": {
                        "name": "part"
                    }
                },
                {
                    "id": "node-1763990776970",
                    "name": "get",
                    "type": "FUNCTION",
                    "x": -279.842,
                    "y": 382.291,
                    "inputs": {
                        "name": "part"
                    }
                },
                {
                    "id": "node-1763990789907",
                    "name": "getservice",
                    "type": "FUNCTION",
                    "x": -139,
                    "y": 183.609,
                    "inputs": {
                        "ServiceName": "Workspace"
                    }
                },
                {
                    "id": "node-1763990796596",
                    "name": "findfirstchild",
                    "type": "FUNCTION",
                    "x": 242,
                    "y": 164.609,
                    "inputs": {
                        "childName": "Part"
                    }
                },
                {
                    "id": "node-1763990843329",
                    "name": "takedamage",
                    "type": "METHOD",
                    "x": 901,
                    "y": 388.609,
                    "inputs": {
                        "damage": "100"
                    }
                },
                {
                    "id": "node-1763990869957",
                    "name": "set",
                    "type": "METHOD",
                    "x": 630.223,
                    "y": 367.379,
                    "inputs": {
                        "name": "plr"
                    }
                },
                {
                    "id": "node-1763990883426",
                    "name": "gethumanoid",
                    "type": "FUNCTION",
                    "x": 626,
                    "y": 697.609,
                    "inputs": {}
                },
                {
                    "id": "node-1763990901047",
                    "name": "getcharacter",
                    "type": "FUNCTION",
                    "x": 291,
                    "y": 697.609,
                    "inputs": {}
                },
                {
                    "id": "node-1763990918423",
                    "name": "get",
                    "type": "FUNCTION",
                    "x": -47.79,
                    "y": 696.487,
                    "inputs": {
                        "name": "plr"
                    }
                },
                {
                    "id": "node-1763991061488",
                    "name": "getproperty",
                    "type": "FUNCTION",
                    "x": 415,
                    "y": 559.609,
                    "inputs": {
                        "PropertyName": "Parent"
                    }
                }
            ],
            "connections": [
                {
                    "fromNode": "node-1763990657293",
                    "fromPort": "otherPart",
                    "toNode": "node-1763990664191",
                    "toPort": "instance",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990664191",
                    "fromPort": "value",
                    "toNode": "node-1763990677640",
                    "toPort": "instance",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990677640",
                    "fromPort": "result",
                    "toNode": "node-1763990729292",
                    "toPort": "condition",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990657293",
                    "fromPort": "ExecOut",
                    "toNode": "node-1763990729292",
                    "toPort": "ExecIn",
                    "type": "exec"
                },
                {
                    "fromNode": "node-1763990762539",
                    "fromPort": "ExecOut",
                    "toNode": "node-1763990772704",
                    "toPort": "ExecIn",
                    "type": "exec"
                },
                {
                    "fromNode": "node-1763990776970",
                    "fromPort": "value",
                    "toNode": "node-1763990657293",
                    "toPort": "instance",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990789907",
                    "fromPort": "service",
                    "toNode": "node-1763990796596",
                    "toPort": "instance",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990796596",
                    "fromPort": "child",
                    "toNode": "node-1763990772704",
                    "toPort": "value",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990729292",
                    "fromPort": "Then",
                    "toNode": "node-1763990869957",
                    "toPort": "ExecIn",
                    "type": "exec"
                },
                {
                    "fromNode": "node-1763990883426",
                    "fromPort": "humanoid",
                    "toNode": "node-1763990843329",
                    "toPort": "humanoid",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990901047",
                    "fromPort": "character",
                    "toNode": "node-1763990883426",
                    "toPort": "character",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990918423",
                    "fromPort": "value",
                    "toNode": "node-1763990901047",
                    "toPort": "player",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990869957",
                    "fromPort": "ExecOut",
                    "toNode": "node-1763990843329",
                    "toPort": "ExecIn",
                    "type": "exec"
                },
                {
                    "fromNode": "node-1763991061488",
                    "fromPort": "value",
                    "toNode": "node-1763990869957",
                    "toPort": "value",
                    "type": "data"
                },
                {
                    "fromNode": "node-1763990657293",
                    "fromPort": "otherPart",
                    "toNode": "node-1763991061488",
                    "toPort": "instance",
                    "type": "data"
                }
            ]
        }
    ],
    "activeScriptId": "script-1763990649166",
    "variables": [
        {
            "name": "plr"
        },
        {
            "name": "part"
        }
    ],
    "customFunctions": []
}

Looks awesome! Out of curiousity – do you think this could integrate with ECS systems like Matter? I feel like that could make this a powerful tool even for experienced users.

1 Like

ECS is literally functional programming with a mutable state
If this tool has tables, it should be fully integratable.
People treat ECS like tech bros, and I don’t like it when, in reality, it has nothing similar to OOP or whatever famous corporate ceremony in programming is currently popular.

2 Likes

There is table node, but there is no way to create “class” or modules, I’ll start a rework on this project when I’ll can :>.

I think a lot of people want this type of tools in Roblox Studio, not from a website, I’m gonna try a rework of this project, the code will be available on GitHub in the same repository I think. I’m will try to same possibility to have your own nodes, but you will have to edit the source code in Luau.
I’m gonna use Rojo, I’ll give you more information about the V2.0 later.

Hey is there any progress on this? Would love to hear about an update or something!

I’ll work back on the project soon, currently I’m in hollidays and I’m studying, next months or July max I’ll bring back news about BLE :grinning_face_with_smiling_eyes: