Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Loading JavaScript from a URL in Go takes two separate steps: use Go’s net/http client to download the response, then pass the response text to a JavaScript runtime such as Goja. Goja’s RunString evaluates source in the runtime’s global context; it does not fetch URLs for you.

What “load JavaScript from a URL” means in Go

A browser combines networking, script loading, and a large host environment. A Go program does not. Your application must decide which URL is allowed, make the HTTP request, verify the response, read a bounded amount of source, and explicitly execute that source in an embedded JavaScript engine.

This separation is important for both correctness and security. A successful HTTP response only proves that bytes were returned. It does not prove that the body is JavaScript, that the code is compatible with your runtime, or that executing it is safe.

Complete Go example: fetch a URL and execute it with Goja

The following program accepts an http or https URL, applies a ten-second deadline, rejects non-2xx responses, limits the source to 2 MiB, and executes it with Goja. It also detects an oversized response instead of silently executing truncated code.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "time"

    "github.com/dop251/goja"
)

const maxScriptBytes int64 = 2 << 20 // 2 MiB

func loadAndRun(ctx context.Context, client *http.Client, scriptURL string) (goja.Value, error) {
    parsed, err := url.Parse(scriptURL)
    if err != nil {
        return nil, fmt.Errorf("parse script URL: %w", err)
    }
    if parsed.Scheme != "http" && parsed.Scheme != "https" {
        return nil, fmt.Errorf("unsupported URL scheme %q", parsed.Scheme)
    }
    if parsed.Host == "" {
        return nil, fmt.Errorf("script URL has no host")
    }

    req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil)
    if err != nil {
        return nil, fmt.Errorf("create request: %w", err)
    }

    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("fetch script: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return nil, fmt.Errorf("fetch script: %s", resp.Status)
    }

    limited := io.LimitReader(resp.Body, maxScriptBytes+1)
    source, err := io.ReadAll(limited)
    if err != nil {
        return nil, fmt.Errorf("read script body: %w", err)
    }
    if int64(len(source)) > maxScriptBytes {
        return nil, fmt.Errorf("script exceeds %d-byte limit", maxScriptBytes)
    }

    vm := goja.New()
    value, err := vm.RunString(string(source))
    if err != nil {
        return nil, fmt.Errorf("execute JavaScript: %w", err)
    }
    return value, nil
}

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "usage: %s https://example.com/script.jsn", os.Args[0])
        os.Exit(2)
    }

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    client := &http.Client{
        Timeout: 10 * time.Second,
        // Set CheckRedirect here if your policy must restrict or reject redirects.
    }

    value, err := loadAndRun(ctx, client, os.Args[1])
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Printf("script result: %vn", value.Export())
}

Initialize the module and run it with:

go mod init example.com/urljs
go get github.com/dop251/goja
go run . https://example.com/script.js

The example uses Go’s net/http package for transport and Goja for evaluation. Replace the example URL with a source you trust and are authorized to execute.

How each stage works

1. Validate the destination before making a request

The sample permits only HTTP and HTTPS and rejects a missing host. Production policy should be stricter when the URL comes from a user, webhook, database, or remote configuration. Decide whether redirects are allowed, whether private or loopback destinations are forbidden, which ports are acceptable, and whether authentication headers may be sent. These are application controls; neither net/http nor Goja automatically makes a remote-code loader safe.

2. Fetch with a context and an explicit timeout

http.NewRequestWithContext connects cancellation to DNS lookup, connection setup, response headers, and body reads. The client timeout is a second upper bound. Always check the request error, close the response body, and handle status codes before interpreting the body as source.

3. Bound the response body

io.LimitReader reads at most one byte beyond the configured limit. That extra byte lets the program distinguish a complete script from an oversized response. A fixed limit protects memory and prevents an unexpectedly large download from becoming executable input. Choose the limit for your application rather than treating 2 MiB as a universal value.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Execute source explicitly

Goja describes itself as an ECMAScript/JavaScript engine in pure Go. Its package documentation states that RunString “executes the given string in the global context.” The URL fetch is therefore finished before RunString is called; passing a URL to RunString does not make Goja download it.

5. Export results or call a JavaScript function

The value returned by RunString can be converted with Export(). If the script creates a named function, retrieve it from the runtime and invoke it through Goja’s documented function APIs:

vm := goja.New()
if _, err := vm.RunString(`function greet(name) { return "Hello, " + name }`); err != nil {
    return err
}

value := vm.Get("greet")
fn, ok := goja.AssertFunction(value)
if !ok {
    return fmt.Errorf("greet is not callable")
}
result, err := fn(goja.Undefined(), vm.ToValue("Go"))
if err != nil {
    return err
}
fmt.Println(result.String())

For structured data, pass Go values into the runtime with vm.ToValue and convert JavaScript values back with Goja’s value-export mechanisms such as Runtime.ExportTo(). Validate the resulting type before using it in application logic.

What Goja does—and does not—provide

Choosing an engine is primarily a compatibility decision. Check the source’s syntax and required globals before deploying it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Requirement What to verify
ECMAScript syntax Goja supports JavaScript, but its project documentation notes that some Annex B functionality is missing. Test the exact script or transpile it for your target.
Browser APIs A Goja runtime is not a browser page. Do not assume window, the DOM, browser fetch, cookies, layout, or other browser globals exist.
Node.js APIs Goja does not automatically provide Node.js globals. Its documentation points to a separate project for Node.js functionality; use an environment that supplies the APIs your script requires.
Network or filesystem access Expose such capabilities deliberately from Go, with narrow functions and validation. A runtime cannot infer your application’s trust policy.
Results and errors JavaScript exceptions are returned as Go errors; values can be retrieved, exported, and passed between Go and JavaScript.

A file that uses import/export, expects a module loader, or relies on browser or Node globals is not necessarily executable as a single script with RunString. Bundle it into a compatible script or select a runtime designed for that environment.

Security and reliability controls

Remote-code trust boundary

Downloaded JavaScript is executable code with the capabilities you expose. Prefer an allowlist of hosts or exact URLs, authenticate and integrity-check sources where appropriate, and avoid executing content that can be changed by an untrusted party. Keep the fetching and execution process separate from systems holding sensitive credentials.

Redirects, SSRF, and credentials

The default HTTP client follows its configured redirect behavior. If a redirect could move a permitted public URL to an internal service, implement a CheckRedirect policy that revalidates every destination. Do not automatically forward cookies, bearer tokens, or custom headers to a redirected host.

Execution time and non-terminating code

The HTTP context stops the download; it does not by itself stop JavaScript that enters an infinite loop after the download completes. Goja’s documentation demonstrates an interruption mechanism. Use runtime interruption for long-running evaluations and enforce process-level CPU and memory limits for untrusted code. An embedded runtime alone is not a complete sandbox.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Content checks and caching

You may inspect the response’s status and declared content type, but treat those headers as hints rather than proof. If you cache source, key the cache by the fully validated URL and an integrity or version policy, set an expiration, and retain a way to invalidate it. Never let a cache bypass destination validation.

Troubleshooting common failures

Symptom Likely cause Fix
unsupported URL scheme The input is not HTTP or HTTPS. Accept only schemes your policy supports; do not enable arbitrary schemes just to make the error disappear.
unsupported protocol scheme or request construction error The URL is missing a scheme or host. Pass an absolute URL such as https://host/path.js and validate it before creating the request.
401, 403, or another non-2xx status The endpoint requires authentication, blocks the client, or returned an error page. Confirm authorization, send only approved headers, and log the status without executing the body.
Timeout or context cancellation DNS, connection, response headers, or body delivery exceeded the deadline. Set a deadline appropriate to the job, inspect network policy, and retry only when the operation is safe to repeat.
Script exceeds the byte limit The response is larger than the configured maximum. Raise the limit only after reviewing memory and trust implications, or serve a smaller/bundled file.
Syntax error from RunString The body is not compatible with Goja, is truncated, or is an HTML/error document. Log status and bounded diagnostics, verify the exact source, and check module, browser, or Node requirements.
ReferenceError: window is not defined (or similar) The script expects browser globals. Provide narrowly scoped host APIs or run it in a browser-capable environment instead of pretending Goja is a browser.
The program hangs during evaluation The script is non-terminating or computationally expensive. Use Goja interruption and stronger process-level resource isolation; do not rely on the HTTP timeout.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, concurrency, and cost considerations

  • Reuse HTTP transports. Keep a configured http.Client and its transport rather than constructing a new client for every URL.
  • Use one runtime per isolated evaluation. A fresh runtime prevents variables from one remote script leaking into another. If you reuse a runtime for speed, define an explicit reset and trust boundary.
  • Bound both stages. Download limits protect memory before execution; interruption and process limits protect CPU during execution.
  • Measure the right phases. Record DNS/connect time, time to first byte, body-read time, and evaluation time separately so a slow network is not confused with slow JavaScript.
  • Plan retries carefully. Retrying a GET may be acceptable, but repeated execution is not harmless if the script calls host functions with side effects.
  • There is no Goja license or service charge implied by this code. Your actual costs come from bandwidth, compute, isolation, and any external service the script invokes. Review the package’s current terms before distribution.

Or skip the browser setup

If your real goal is to obtain a clean visual result from a web URL rather than execute JavaScript inside your Go process, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.

A single request returns PNG, JPEG, WebP, or PDF. The API supports full-page captures with lazy images, CSS-selector element capture, device and viewport settings, dark mode, custom CSS and JavaScript, waits, request blocking, headers, cookies, user agents, authentication, geolocation, transparent backgrounds, resizing, TTL-based caching, signed links, asynchronous webhooks, bulk capture, and a usage API. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

See the ScreenshotNeo API documentation for parameters and response headers. The same endpoint can be called from Go, Python, or Node.js:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// Go
q := url.Values{}
q.Set("access_key", "YOUR_API_KEY")
q.Set("url", "https://example.com")
resp, err := http.Get("https://api.screenshotneo.com/v1/shot?" + q.Encode())

# Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

// Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan. Create a free ScreenshotNeo account.

Practical decision checklist

  • Do you need JavaScript data or side effects inside Go? Fetch the source and execute it with a compatible runtime.
  • Does the source require DOM, browser networking, layout, or Node APIs? Goja alone is the wrong host unless you provide those APIs.
  • Can the URL or source change without your approval? Add allowlists, redirect checks, integrity controls, byte limits, interruption, and process isolation before execution.
  • Do you only need a rendered screenshot or PDF? Use a rendering service such as ScreenshotNeo instead of building a browser environment into the Go application.

Frequently Asked Questions

Can I execute an ES module directly with RunString?

Not automatically. RunString evaluates supplied source in the runtime’s global context; module loading and module-resolution behavior must be provided by your host or handled by bundling the module into compatible script source.

What should I log when a remote script fails?

Record the validated URL, HTTP status, elapsed fetch and evaluation times, and the error type. Avoid logging credentials or the full remote source, especially when the source may contain secrets.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.