Skip to content

API reference

Every exported symbol in gitlab.com/phpboyscout/go/browser, what it does, and what it guarantees. The package is deliberately small: one function that opens a URL, one option that swaps the opener, one accessor, one constant, and two sentinel errors.

For the generated godoc — including runnable examples — see pkg.go.dev. This page covers the behaviour the signatures do not show.

The complete exported surface

const MaxURLLength = 8192

var ErrInvalidURL error
var ErrDisallowedScheme error

type Opener func(rawURL string) error
type Option func(*options)

func AllowedSchemes() []string
func OpenURL(ctx context.Context, rawURL string, opts ...Option) error
func WithOpener(opener Opener) Option

That is the whole API. There is no config struct, no Client to construct, and nothing to initialise before the first call.

OpenURL — validate a URL, then open it

func OpenURL(ctx context.Context, rawURL string, opts ...Option) error

Validates rawURL and, if it passes, hands it to the opener — by default the operating system's URL handler for the user's browser or mail client.

Parameter Notes
ctx Must not be nil. Checked after validation and before the open; a cancelled or expired context means the open is skipped and ctx.Err() is returned. A nil context panics with a nil-pointer dereference.
rawURL The URL exactly as it will be handed to the opener. OpenURL never rewrites, normalises, or re-encodes it — the opener receives the identical string.
opts Zero or more options. Today WithOpener is the only one.

Returns nil once the opener returns nil. For everything else see Errors; for exactly which URLs pass, see Validation rules.

OpenURL blocks until the opener returns. With the default opener that means it waits for the platform launcher process to exit — see Platform behaviour.

WithOpener — replace the opener that performs the open

func WithOpener(opener Opener) Option

Replaces the function that performs the actual open. The intended use is testing — see Test without launching a browser — but it is also the seam for an unusual deployment that needs its own handler.

Three behaviours worth knowing, all of them observable:

  • Validation still runs. A custom opener sits behind the gate, not in front of it. A rejected URL never reaches it.
  • Last non-nil option wins. Supply WithOpener twice and the second one is used.
  • WithOpener(nil) is ignored. It leaves whatever opener is already configured in place — the default if no earlier option set one, or an earlier WithOpener's function if one did. It is not a way to reset to the default, and it is not a way to disable opening.

The option is scoped to the single OpenURL call it is passed to. There is no global opener to set or restore, so parallel tests cannot interfere with each other.

Opener — the opener function signature

type Opener func(rawURL string) error

Receives the validated URL and returns an error if the open failed. OpenURL wraps a non-nil return with invoking URL opener, preserving the original for errors.Is.

An Opener is called at most once per OpenURL call, and only when validation and the context check have both passed. It receives no context: the signature has no cancellation channel because the platform handlers behind it have none either.

Option — why you cannot write your own

type Option func(*options)

Option is exported but the options struct it configures is not, so Option values can only be produced by constructors in this package. Adding an option is a change to this module, not something a caller can do from outside. That is deliberate: the same reasoning that keeps the scheme allowlist unexported keeps the option set closed.

AllowedSchemes — read the permitted schemes at runtime

func AllowedSchemes() []string

Returns []string{"https", "http", "mailto"} — the schemes OpenURL permits, in the order they are checked (the order has no effect on the outcome).

The slice is a fresh copy on every call. Mutating it, appending to it, or replacing its elements changes nothing about validation: the allowlist backing OpenURL is an unexported package variable and there is no exported way to widen it. Adding a scheme requires a change to this module and a security review.

Use it to build an error message or a help string rather than hardcoding the list where it can drift:

fmt.Printf("only these URL schemes can be opened: %v\n", browser.AllowedSchemes())
// only these URL schemes can be opened: [https http mailto]

MaxURLLength — the byte cap on a URL

const MaxURLLength = 8192

The largest URL OpenURL accepts, in byteslen(rawURL), not runes and not characters. A URL of 8192 bytes is accepted; 8193 bytes is rejected with ErrInvalidURL.

The byte/rune distinction bites with non-ASCII URLs. A URL of 4,110 characters that is mostly two-byte runes measures 8,200 bytes and is rejected, even though it is only half the cap in characters. If you need to check a URL against the cap yourself, use len(), not len([]rune(...)).

It is a const, so it can be used in array sizes and constant expressions, and it cannot be changed by a caller at runtime. Why 8192 and not something larger is covered in the threat model.

ErrInvalidURL and ErrDisallowedScheme — the two sentinels

var ErrInvalidURL error       // "invalid URL"
var ErrDisallowedScheme error // "disallowed URL scheme"

The two failure classes OpenURL distinguishes. Match them with errors.Is, never by comparing strings. Full details, including the hint attached to each case, are on the Errors page.

Is OpenURL safe to call concurrently?

Yes. OpenURL holds no package-level mutable state: the allowlist is read-only after initialisation, options are built into a fresh struct per call, and AllowedSchemes returns a copy. Concurrent calls from multiple goroutines are safe, including under -race.

What is not guaranteed is what happens on the other side of the opener. Launching several browser windows at once is the operating system's business, and the default opener has no queue or rate limit.

What the package does not export

There is no way, from outside this module, to:

  • add a scheme to the allowlist or remove one from it;
  • change MaxURLLength;
  • disable validation for a single call;
  • install a global opener that applies to every call;
  • ask "would this URL be accepted?" without also opening it — validation is not exported separately.

The last one is worth planning for: if you want to check a URL early (say, to disable a menu item), replicate the check by matching the sentinel from an OpenURL call against a no-op opener, or apply your own scheme check first.

See also