Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/GraphiteEditor/Graphite/llms.txt

Use this file to discover all available pages before exploring further.

Custom nodes for the Graphene engine are written in Rust and defined using the #[node_macro::node] proc-macro attribute. A node is a regular Rust function with typed parameters — the macro generates all of the boilerplate needed to integrate it into the engine: type registration, input/output connector wiring, metadata for the UI, and the node executor implementation. Once registered, a node appears in the Graphite editor’s node search and can be wired into any document graph.

The node_macro Proc-Macro

The node-macro crate (at node-graph/node-macro/) provides the #[node_macro::node] attribute macro. When applied to a Rust function, it:
  • Generates a named struct (the node type) and implements the Node trait for it
  • Registers the node with a ProtoNodeIdentifier so the compiler can look it up by name
  • Extracts doc comments from parameters to produce UI tooltip text
  • Supports #[implementations(...)] to define generic type specializations
  • Supports #[default(...)] to set default values for parameters
  • Optionally generates a GPU shader variant via shader_node(...)
The lib.rs of node-macro also exposes #[derive(ChoiceType)] for defining enum-based dropdown/radio widgets and #[derive(BufferStruct)] for GPU-compatible shader structs.

Basic Node Structure

Here is an example of a real node from node-graph/nodes/math/src/lib.rs — the addition node:
use node_macro::node;

/// The addition operation (`+`) calculates the sum of two scalar numbers or vectors.
#[node_macro::node(category("Math: Arithmetic"))]
fn add<A: Add<B>, B>(
    _: impl Ctx,
    /// The left-hand side of the addition operation.
    #[implementations(f64, f32, u32, DVec2, f64, DVec2)]
    augend: Item<A>,
    /// The right-hand side of the addition operation.
    #[implementations(f64, f32, u32, DVec2, DVec2, f64)]
    addend: Item<B>,
) -> Item<<A as Add<B>>::Output> {
    let (augend, attributes) = augend.into_parts();
    Item::from_parts(augend + addend.into_element(), attributes)
}
Key conventions:
  • The first parameter _: impl Ctx is the execution context — always present, never named in the UI
  • All other parameters become input connectors in the node graph
  • Doc comments on parameters become tooltip text in the editor
  • #[implementations(...)] lists the concrete types this generic parameter should be monomorphized for
  • #[default(...)] sets the default value shown in the properties panel when no connection is made
  • The function return type becomes the node’s output connector type

Attribute Arguments

The #[node_macro::node(...)] attribute accepts several named arguments:
ArgumentExamplePurpose
category(...)category("Math: Arithmetic")Groups the node in the Add Node menu
name(...)name("As u32")Overrides the display name (default: function name in title case)
properties(...)properties("math_properties")Names a custom properties panel renderer
memoizememoizeInserts a Memoize node after this node in subnetwork expansion
inject_scopeinject_scopeMarks this node as providing a new scope for context injection

Node Crate Organization

Nodes are organized into domain-specific crates under node-graph/nodes/:
CratePathDomain
graphene-core (gcore)node-graph/nodes/gcore/Fundamental types, context, memo infrastructure
graphene-std (gstd)node-graph/nodes/gstd/Rendering pipeline, web requests, animation
vector-nodesnode-graph/nodes/vector/Vector geometry, paths, boolean ops
raster-nodesnode-graph/nodes/raster/Raster image processing, color adjustments
graphic-nodesnode-graph/nodes/graphic/Compositor nodes, graphic group handling
transform-nodesnode-graph/nodes/transform/Transform, scale, rotate, translate
math-nodesnode-graph/nodes/math/Arithmetic, trig, numeric operations
repeat-nodesnode-graph/nodes/repeat/Repetition and instancing nodes
blending-nodesnode-graph/nodes/blending/Layer blending modes
text-nodesnode-graph/nodes/text/Text rendering and layout
brush-nodesnode-graph/nodes/brush/Brush stroke nodes
The best way to write a new node is to find an existing node in the relevant crate that is similar in structure — especially one with the same input/output types — and use it as a template. The math nodes in node-graph/nodes/math/src/lib.rs are particularly clean examples of the common patterns.

Registering a Node

Nodes defined with #[node_macro::node] self-register their metadata at startup through the graphene_std::registry::NODE_METADATA static. However, for the DynamicExecutor to be able to construct node instances at runtime, each type monomorphization must also be registered in the node registry. The node registry lives at node-graph/interpreted-executor/src/node_registry.rs. It is a large node_registry() function that returns a HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>. New native node types must be added here using the async_node! macro with their concrete type parameters:
// Example entries from node_registry.rs
async_node!(graphene_core::memo::MonitorNode<_, _, _>,
    input: Context,
    fn_params: [Context => List<Artboard>]
),
async_node!(graphene_core::memo::MonitorNode<_, _, _>,
    input: Context,
    fn_params: [Context => List<Graphic>]
),
Each entry registers one specific input/output type combination. Generic nodes need one entry per concrete type they support.

Input/Output Types

Graphene uses a typed connector system. Type mismatches between connected nodes are detected at graph compile time. Common types used in node signatures:
TypeDescription
f64Scalar floating-point number
DVec22D double-precision vector
DAffine22D affine transform
ColorRGBA color value
Item<T>A typed data item carrying attributes alongside its value
List<T>A list of typed items
VectorVector path data
Raster<CPU>CPU-side raster image buffer
Raster<GPU>GPU-side raster texture
GraphicA composited graphic element
ArtboardAn artboard with bounds and content
ContextThe execution context (always the first parameter)
The #[implementations(...)] annotation on generic parameters tells the macro which concrete types to generate implementations for. Any combination of input types not listed will not be available as a valid connection in the editor.

Subgraph Nodes

Not all nodes need to be written in Rust. Complex nodes can be implemented as subgraph documents — a .graphite network file that is embedded as a DocumentNodeImplementation::Network inside a DocumentNode. These subgraph nodes appear in the editor just like native nodes. Double-clicking a subgraph node in the Graphite node graph reveals its interior network, where you can see and modify the underlying composition. Many of the built-in Graphite nodes — particularly higher-level artistic effects — are subgraphs built from simpler native nodes.

GPU Shader Nodes

Raster nodes that benefit from GPU acceleration can provide a shader implementation in addition to their CPU fallback. Shader nodes are written in Rust using spirv-std and compiled to SPIRV. The #[node_macro::node(..., shader_node(...))] attribute instructs the macro to generate both variants.
GPU shader nodes require the spirv-std crate and are compiled separately using cargo-gpu. They are only available on native targets with WGPU support. When running in WebAssembly, the CPU fallback path is used unless the browser supports WebGPU. See node-graph/nodes/raster/shaders/ for existing shader node examples.
For a broader understanding of the engine that executes these nodes, see Graphene Engine.

Build docs developers (and LLMs) love