Skip to content

Getting started

This tutorial walks you through your first safe URL open, end to end. You will open a valid https URL, then deliberately try a dangerous one and see the validation gate reject it — and learn how to tell the two failure kinds apart.

Everything happens in one self-contained Go program so you can see each moving part. By the end you will understand what OpenURL guarantees before it ever reaches the operating system.

Prerequisites

  • Go 1.26 or newer.
  • A new module to experiment in:
mkdir browser-tutorial && cd browser-tutorial
go mod init example.test/browser-tutorial
go get gitlab.com/phpboyscout/go/browser

Step 1 — Open a valid URL

OpenURL takes a context.Context, the raw URL string, and zero or more options. With a well-formed https URL it validates, then hands the URL to the OS handler for the user's default browser:

ctx := context.Background()

if err := browser.OpenURL(ctx, "https://example.com/docs"); err != nil {
    log.Fatalf("open failed: %v", err)
}

The context is respected up to the moment of opening: if it is already cancelled when OpenURL runs, the open is skipped and the context's error is returned. The opener itself is not context-aware — once the OS has spawned a browser, there is nothing left to cancel.

Step 2 — Watch a dangerous scheme get rejected

Now try a scheme that is not on the allowlist. javascript: is exactly the kind of URL you never want reaching an OS handler:

err := browser.OpenURL(ctx, "javascript:alert(1)")
// err is non-nil, and errors.Is(err, browser.ErrDisallowedScheme) is true.

Only https, http, and mailto pass the gate. file://, data:, javascript:, and custom protocol handlers all fail with ErrDisallowedScheme. You can read the current allowlist at runtime:

fmt.Println(browser.AllowedSchemes()) // [https http mailto]

Step 3 — Tell the two failure kinds apart

OpenURL returns two sentinel errors, and you match them with errors.Is:

  • ErrDisallowedScheme — the URL parsed fine, but its scheme is not permitted.
  • ErrInvalidURL — the URL failed hygiene: empty, longer than MaxURLLength (8192 bytes), contained an ASCII control character, or could not be parsed.
switch {
case errors.Is(err, browser.ErrDisallowedScheme):
    fmt.Println("scheme not allowed — only https, http, mailto")
case errors.Is(err, browser.ErrInvalidURL):
    fmt.Println("malformed URL — empty, too long, control chars, or unparseable")
case err != nil:
    fmt.Println("the OS opener failed:", err)
}

The complete program

package main

import (
    "context"
    "fmt"

    "github.com/cockroachdb/errors"
    "gitlab.com/phpboyscout/go/browser"
)

func main() {
    ctx := context.Background()

    // A valid URL opens in the default browser.
    if err := browser.OpenURL(ctx, "https://example.com/docs"); err != nil {
        reportOpenError(err)
    }

    // A disallowed scheme is rejected before the OS ever sees it.
    if err := browser.OpenURL(ctx, "javascript:alert(1)"); err != nil {
        reportOpenError(err)
    }
}

func reportOpenError(err error) {
    switch {
    case errors.Is(err, browser.ErrDisallowedScheme):
        fmt.Println("scheme not allowed — only", browser.AllowedSchemes())
    case errors.Is(err, browser.ErrInvalidURL):
        fmt.Println("malformed URL:", err)
    default:
        fmt.Println("OS opener failed:", err)
    }
}

Run it:

go run .

Success criterion

The first OpenURL call opens https://example.com/docs in your default browser (or returns an OS-opener error if you are on a headless machine with no browser). The second call prints scheme not allowed — [https http mailto] and opens nothing. If you never see a browser launch for the javascript: URL, the gate is doing its job.

Next steps