HTTP & Files
Fetch makes HTTP requests straight from your WASM component and returns a Response — with the status code, headers, and body. GetFileBytes reads a file the user picked. TriggerDownload pushes a file back to the browser.
Important: You must dot-import core/wasm (import . "github.com/gothicframework/core/wasm") to use these helpers, otherwise you will get a tinygo compile error.
Note: these calls are blocking. Go's goroutine model handles concurrency for you, so always call them inside a goroutine or a CreateWasmFunc handler.
// Inside CreateWasmFunc — already runs in its own goroutine
CreateWasmFunc("load", func() {
resp, err := Fetch("https://api.example.com/data")
if err != nil {
SetText("result", "error: "+err.Error())
return
}
SetText("result", resp.Text())
})
// On mount — use the go keyword so the blocking call never freezes the WASM thread
go func() {
resp, err := Fetch("https://api.example.com/data")
if err == nil {
SetText("result", resp.Text())
}
}()The Response carries everything about the reply. Read resp.Status, check resp.OK() (true for 200–299), inspect resp.Headers, and get the body as resp.Text() (string) or resp.Bytes() (raw bytes).
// Response{ Status int; Headers map[string]string; Body []byte }
CreateWasmFunc("load", func() {
resp, err := Fetch("https://api.example.com/todos/1")
if err != nil {
SetText("result", "error: "+err.Error())
return
}
SetText("status", strconv.Itoa(resp.Status)) // "200"
SetText("ok", strconv.FormatBool(resp.OK())) // "true" for 200..299
SetText("ctype", resp.Headers["content-type"]) // "application/json"
SetText("result", resp.Text()) // body as a string
})Need more control? Pass a FetchConfig to set Method, Headers, Body, BodyBytes, or Query parameters.
CreateWasmFunc("submit", func() {
resp, err := Fetch("https://api.example.com/todos", FetchConfig{
Method: "POST",
Headers: map[string]string{"Content-Type": "application/json"},
Body: "{\"title\":\"buy milk\"}",
Query: map[string]string{"notify": "true"}, // ?notify=true
})
if err != nil {
SetText("result", "error: "+err.Error())
return
}
if resp.OK() {
SetText("result", "created!")
}
})To handle uploads, read the user's selected file with GetFileBytes and send the []byte through BodyBytes.
CreateWasmFunc("uploadFile", func() {
data := GetFileBytes("upload")
if data == nil {
SetText("status", "no file selected")
return
}
resp, err := Fetch("/api/upload", FetchConfig{
Method: "POST",
Headers: map[string]string{"Content-Type": "application/octet-stream"},
BodyBytes: data,
})
if err != nil || !resp.OK() {
SetText("status", "upload failed")
return
}
SetText("status", "uploaded!")
})Going the other way? Read resp.Bytes() for binary data and hand it to TriggerDownload as a file. Migrating from before: Fetch used to return a plain string and there was a separate FetchBytes. Now Fetch always returns a Response — use .Text() for a string and .Bytes() for bytes. FetchBytes was removed.
CreateWasmFunc("downloadReport", func() {
resp, err := Fetch("/api/report.csv")
if err != nil {
return
}
// resp.Bytes() returns the raw []byte body (read via arrayBuffer, so
// binary payloads are never UTF-8 corrupted). FetchBytes no longer exists.
TriggerDownload("report.csv", resp.Bytes(), "text/csv")
})On AWS, same-origin bodies are signed for you
Deployed on AWS, your app sits behind CloudFront with Origin Access Control, which rejects any request whose body hash is missing. Fetch handles that: when the request targets your own origin, it adds x-amz-content-sha256 over the exact bytes you passed. Nothing to enable.
A call to a third-party API is never signed — the examples above using https://api.example.com go out untouched. Only your own origin gets the header, which is what the check on your distribution expects.
One request, one wait. Want to fire many at once, run a callback when it finishes, or cancel in-flight requests? Learn Async Fetch next!
