Quick answer: Set the effect’s resource_name to the BBCode tag, add the effect resource to the label’s custom_effects array, and enable bbcode_enabled. All three are required.

A custom shake RichTextEffect script overrides _process_custom_fx to wiggle each character. Using [shake]Hello[/shake] in the label’s text shows the text statically — no wiggle. The script logic works (you can call it manually) but the BBCode tag doesn’t dispatch.

The Three Wiring Points

For a custom effect to trigger from BBCode:

  1. Define the effect resource with a resource_name that matches the BBCode tag.
  2. Add an instance of that effect to the RichTextLabel’s custom_effects array.
  3. Enable the label’s bbcode_enabled.

Skipping any one silently breaks dispatch.

Step 1: Effect Definition

# shake_effect.gd
@tool
class_name ShakeEffect extends RichTextEffect

var bbcode = "shake"   # the BBCode tag name

func _process_custom_fx(char_fx: CharFXTransform) -> bool:
    var mag = float(char_fx.env.get("mag", 2.0))
    char_fx.offset += Vector2(randf_range(-mag, mag), randf_range(-mag, mag))
    return true

Note var bbcode = "shake" rather than resource_name. In Godot 4, the RichTextEffect base class uses this convention.

Step 2: Add to custom_effects

In the editor, select the RichTextLabel:

  1. Inspector → Custom Effects.
  2. Click Add Element → New ShakeEffect.
  3. Save.

Or programmatically:

$RichTextLabel.install_effect(ShakeEffect.new())

Step 3: Enable BBCode

Set bbcode_enabled = true in the Inspector. Without it, the label treats [shake] as literal text, not a tag.

Using Parameters

$RichTextLabel.text = "Hello [shake mag=4]world[/shake]!"

The effect receives mag=4 via char_fx.env. Read as char_fx.env.get("mag", default). Allows tuning per-instance from the text itself.

Verifying

Run the scene. The text inside [shake] tags should wiggle on every render frame; the text outside should stay static. If everything is static, BBCode isn’t parsing — check the enable flag. If text inside the tag renders but doesn’t wiggle, the effect isn’t installed — check custom_effects.

“Three checkpoints. Get all three right and the tag fires; miss one and your effect is invisible.”

Add a small “effect test” debug label at startup that exercises every custom effect — catches wiring regressions immediately.