Quick answer: RichTextLabel does not handle [url] clicks for you. Enable bbcode_enabled, connect the meta_clicked signal, and write your own handler. Also confirm that mouse_filter on the label and every ancestor Control does not swallow the click before it reaches the link region. Style the link with meta_underlined or BBCode color tags so players can find it.

You drop a RichTextLabel into a dialog scene, type Visit [url=https://example.com]our website[/url] for more., and run the game. The text appears with no formatting whatsoever — the BBCode tags display as literal characters. You enable BBCode, the link now renders as plain text, click it, and nothing happens. The label has no built-in browser. You need to wire up the signal yourself, and there are several places along the chain where it can break.

The Symptom

You add a clickable link with [url=...]label[/url] inside a RichTextLabel. Several behaviors are possible depending on how things are configured:

None of these are bugs in Godot. RichTextLabel is intentionally minimalist about link handling because there is no single “right” thing to do when a player clicks a meta tag — some games open a URL, others trigger an in-game tooltip, others swap scenes, and the engine refuses to guess.

What Causes This

1. bbcode_enabled is off. By default, RichTextLabel treats text as plain. The [url], [b], [color], and other tags are only parsed when this property is true.

2. No meta_clicked handler is connected. Even when BBCode is enabled, Godot does not auto-open URLs. You must connect the signal and call OS.shell_open() or perform whatever in-game action you intend.

3. mouse_filter blocks input. If the RichTextLabel or any ancestor Control has mouse_filter set to MOUSE_FILTER_IGNORE, clicks pass straight through. If an ancestor has MOUSE_FILTER_STOP and consumes the event in its own input handler, the label never gets to see it.

4. fit_content is off in a stretched container. When the label is the only child of a VBoxContainer with no explicit size, the rendered text region may be much smaller than the visible area. The link region is only inside the rendered region, so clicks outside it (but inside the visually empty area) appear to fail.

5. The signal is connected to a freed object. Common after scene reloads: a previous instance was connected, the connection survived weakly, and the new label has nothing wired up.

The Fix

Step 1: Enable BBCode and confirm rendering. Set the property in the inspector or in code.

extends RichTextLabel

func _ready() -> void:
    bbcode_enabled = true
    fit_content = true
    selection_enabled = true
    meta_underlined = true

    text = "Visit [color=#7aa2f7][url=https://example.com]our website[/url][/color] for more info."

    # Connect the signal to a method on this script.
    meta_clicked.connect(_on_meta_clicked)
    meta_hover_started.connect(_on_meta_hover_started)
    meta_hover_ended.connect(_on_meta_hover_ended)

func _on_meta_clicked(meta: Variant) -> void:
    if meta is String and meta.begins_with("https://"):
        OS.shell_open(meta)
    else:
        # Custom in-game action keyed by meta string.
        _handle_in_game_link(meta)

func _on_meta_hover_started(meta: Variant) -> void:
    Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND)

func _on_meta_hover_ended(meta: Variant) -> void:
    Input.set_default_cursor_shape(Input.CURSOR_ARROW)

Step 2: Verify mouse_filter from root to label. Walk the scene tree from the RichTextLabel up to the root. Every Control between them should be set to MOUSE_FILTER_PASS or have a deliberate reason to STOP. The label itself should be MOUSE_FILTER_STOP so it can capture the click.

func _ready() -> void:
    mouse_filter = Control.MOUSE_FILTER_STOP

    # Sanity check: log any ancestor that swallows mouse events.
    var node: Node = get_parent()
    while node and node is Control:
        var ctrl: Control = node
        if ctrl.mouse_filter == Control.MOUSE_FILTER_STOP:
            push_warning("Ancestor " + ctrl.name + " stops mouse events")
        node = node.get_parent()

Step 3: Use fit_content or set an explicit size. When the label sits inside a container, set fit_content = true so the clickable region matches the rendered text. If the label sizes to its container, set a minimum size large enough for your wrapped lines.

Step 4: Style the link so players can find it. meta_underlined = true is the simplest visual cue. For more control, wrap the BBCode tag in [color] tags or use a custom RichTextEffect.

# Hover styling done with a theme override or rich text effect:
text = "[color=#7aa2f7][url=quest_001]Talk to the blacksmith[/url][/color]"

# Programmatic theme override for link text:
add_theme_color_override("default_color", Color("#cdd6f4"))
add_theme_constant_override("line_separation", 4)

Step 5: Encode useful data inside the meta. The meta does not need to be a URL. Use a structured string or even a Variant to drive in-game logic.

# Quest-system style: prefix-tagged meta values.
text = "Accept the [url=quest:slay_wolf]bounty[/url] or [url=dialog:back]return later[/url]."

func _on_meta_clicked(meta: Variant) -> void:
    var parts: PackedStringArray = String(meta).split(":", false, 1)
    match parts[0]:
        "quest": QuestManager.accept(parts[1])
        "dialog": DialogSystem.go_to(parts[1])
        _: push_warning("Unknown meta scheme: " + String(meta))

Step 6: Reconnect on scene reload. If you instance dialog scenes dynamically, always connect meta_clicked in _ready rather than once globally. Connections do not transfer across freed nodes.

Why This Works

RichTextLabel is a generic text renderer with a generic meta system. The [url] tag is just a way to associate an arbitrary Variant with a span of text. The engine emits a signal when that span is clicked or hovered; what your game does next is intentionally outside the engine’s scope. This decoupling is why the same control supports browser links, quest hooks, dialog branches, item tooltips, and hyperlinked patch notes — all in one place.

The mouse_filter chain matters because Godot routes events from root to leaf, with each Control free to consume the event. If an ancestor consumes it, no descendant ever sees the click, no matter how perfectly your label is configured. The fit_content option matters because the engine considers a meta region clickable only where text is actually drawn — not in the empty space your container reserves.

"RichTextLabel hands you a span and a value and asks what you want to do. The engine does not assume URLs. That is a feature, not a missing piece — it is what makes the same control work for quests, tooltips, and external links."

Related Issues

If your Control nodes refuse mouse input in general, see Fix: Godot Mouse Input Not Reaching Control Nodes. For button-driven UIs that fail to register clicks at all, check Fix: Godot Button Click Events Not Registering.

BBCode renders the link. Your code makes it do something.