API Surface
The package exposes browser APIs under the WebAPI namespace. In normal application code,
add the package to rescript.json:
{ "dependencies": ["@rescript/webapi"]}Use WebAPI.DOM.window for the browser window. Interface-specific methods live on
public modules such as WebAPI.Window, WebAPI.Location, WebAPI.Document,
WebAPI.Element, WebAPI.Request, and WebAPI.Response.
let location = WebAPI.DOM.window->WebAPI.Window.locationlet href = location.href
location->WebAPI.Location.reloadPublic module shape
Section titled “Public module shape”The public API is flat under WebAPI. Use owner modules directly rather than generated
helper modules.
let req: WebAPI.Request.t = WebAPI.Request.fromURL("https://example.com")let headers = WebAPI.Headers.make()let document = WebAPI.DOM.window->WebAPI.Window.documentlet element = document->WebAPI.Document.createElement("button")Generated implementation modules such as DomTypes, FetchTypes, EventTypes, and
UiEventsTypes are internal. If you need to name a public type, use the public interface
module’s t type when it has one, or use a dedicated public type module. Otherwise, let
the value type be inferred from constructors and accessors.
Consumer feature bundles
Section titled “Consumer feature bundles”Feature names select related bindings and their transitive dependencies. They do not add
another namespace segment. For example, selecting WebAPI.Fetch enables flat modules such
as WebAPI.Fetch, WebAPI.Request, WebAPI.Response, and WebAPI.Headers.
{ "dependencies": [ { "name": "@rescript/webapi", "features": ["WebAPI.Fetch", "WebAPI.HTML"] } ]}The supported feature bundles are:
WebAPI.DOM WebAPI.Event WebAPI.DOMPlatformWebAPI.DOMNodes WebAPI.File WebAPI.HTMLWebAPI.Window WebAPI.CSSOM WebAPI.CSSFontLoadingWebAPI.Geometry WebAPI.SVG WebAPI.AnimationWebAPI.Device WebAPI.Navigator WebAPI.CanvasWebAPI.URL WebAPI.Fetch WebAPI.UIEventsWebAPI.Observers WebAPI.Media WebAPI.WebAudioWebAPI.Storage WebAPI.Messaging WebAPI.WorkersWebAPI.Crypto WebAPI.Performance WebAPI.ViewTransitionsWebAPI.SafeFetchThe package owns each bundle as one internal source folder. Those internal folder feature
names are an implementation detail; consumers should use only the qualified names above.
WebAPI.SafeFetch transitively enables WebAPI.Fetch, so consumers that want the result-based
helpers can select only the safe feature:
{ "experimental-features": { "LetUnwrap": true }, "dependencies": [ { "name": "@rescript/webapi", "features": ["WebAPI.SafeFetch"] } ]}The let? examples below require the experimental LetUnwrap setting with the current ReScript 13
alpha compiler. The SafeFetch API itself can also be used with ordinary switch expressions.
Use WebAPI.Fetch.fetch for string URLs and WebAPI.Fetch.fetchWithRequest
when you already have a WebAPI.Request.t.
let response = await WebAPI.Fetch.fetch("https://example.com/api/users")
if response.ok { let body = await response->WebAPI.Response.json Console.log(body)} else { Console.log("Request failed") Console.log(response.status)}For JSON-style POST requests, construct headers with HeadersInit and the body with
BodyInit.
let response = await WebAPI.Fetch.fetch( "https://example.com/api/users", ~init={ method: "POST", headers: WebAPI.HeadersInit.fromDict( dict{ "Accept": "application/json", "Content-Type": "application/json", }, ), body: WebAPI.BodyInit.fromString(`{"name":"ReScript"}`), },)
let text = await response->WebAPI.Response.textResult-based workflows
Section titled “Result-based workflows”WebAPI.Fetch and WebAPI.Response are the raw browser bindings and remain unchanged. Use the
separate, opt-in WebAPI.SafeFetch module when request, HTTP status, and body-reading failures should
be handled as result values. The separate feature is an intentional breaking change for consumers
that restrict enabled features: add WebAPI.SafeFetch to use these helpers. Existing raw Fetch
clients are unaffected.
| Stage | Result | Preserved context |
|---|---|---|
| Fetch throws or rejects | Error(#FetchRejected(cause)) |
Original rejection as exn |
| Response is not OK | Error(#ResponseNotOk({response})) |
Complete response |
| Body reader rejects | Error(#BodyReadRejected({response, cause})) |
Response and original rejection |
SafeFetch.fetch accepts a URL, while SafeFetch.fetchWithRequest accepts an existing
WebAPI.Request.t. Both promises fulfill with a result for these expected failures. A fulfilled
response is classified with the platform’s response.ok value, so a 404 or 500 is distinct from a
request that did not produce a response. The public fetchError type contains #FetchRejected(exn)
and #ResponseNotOk({response}); bodyError contains #BodyReadRejected(readError). readError has
response and cause fields, and response<'body> retains the original response alongside the
successful body. The function signatures expose extensible error rows ([> fetchError] and
[> bodyError]), so application errors can be added directly with let?, without mapError.
let fetchJson = async url => { let? Ok(response) = await WebAPI.SafeFetch.fetch(url) let? Ok({body}) = await response->WebAPI.SafeFetch.json Ok(body)}The body reader reports malformed JSON as #BodyReadRejected; valid JSON with the wrong shape is an
application decoding failure instead. Here is one way to compose the stages and retain an application
error variant in the result:
type user = {name: string}
let decodeUser = json => switch json { | JSON.Object(dict{"name": JSON.String(name)}) => Ok({name: name}) | _ => Error(#InvalidUser) }
let loadUser = async url => { let? Ok(response) = await WebAPI.SafeFetch.fetch(url) let? Ok({body}) = await response->WebAPI.SafeFetch.json let? Ok(user) = body->decodeUser Ok(user)}The composed result has open error rows, so its caller can handle each stage exhaustively, including
the application-specific #InvalidUser:
switch await loadUser("https://example.com/api/users/42") {| Error(#FetchRejected(cause)) => reportUnavailable(cause)| Error(#ResponseNotOk({response})) => handleHttpError(response.status)| Error(#BodyReadRejected({response, cause})) => handleInvalidBody(response.status, cause)| Error(#InvalidUser) => handleInvalidUser()| Ok(user) => showUser(user)}Use checkOk to classify a response obtained from the raw API without reading its body. Its
non-success result is tagged as #ResponseNotOk({response}).
let response = await WebAPI.Fetch.fetch("https://example.com/api/users")
switch response->WebAPI.SafeFetch.checkOk {| Ok(response) => useResponse(response)| Error(#ResponseNotOk({response})) => handleHttpError(response.status)}A non-OK response remains readable. This is useful when a server returns structured error details;
reading that body can itself fail with #BodyReadRejected.
switch await WebAPI.SafeFetch.fetch("https://example.com/api/users/42") {| Error(#ResponseNotOk({response})) => switch await response->WebAPI.SafeFetch.text { | Ok({body}) => Console.error(body) | Error(#BodyReadRejected(_)) => Console.error(`Request failed with ${Int.toString(response.status)}`) }| Error(#FetchRejected(cause)) => reportUnavailable(cause)| Ok(response) => useResponse(response)}The safe body readers cover arrayBuffer, blob, bytes, formData, json, and text. A
successful read contains both the body and original response, preserving status, headers, URL, and
other metadata. Reading consumes the native body; clone the response before reading when another
consumer also needs it.
SafeFetch.json returns JSON.t; validating that JSON against an application’s domain types remains
the application’s responsibility. A body read preserves its response and original rejection in
the readError payload. This keeps malformed JSON text separate from valid JSON that does not match
an application schema.
BodyInit has constructors for the common JavaScript body shapes.
let params = WebAPI.URLSearchParams.fromString("q=rescript")let formData = WebAPI.FormData.make()let blob = WebAPI.Blob.make(~blobParts=[])
let stringBody = WebAPI.BodyInit.fromString("plain text")let paramsBody = WebAPI.BodyInit.fromURLSearchParams(params)let formDataBody = WebAPI.BodyInit.fromFormData(formData)let blobBody = WebAPI.BodyInit.fromBlob(blob)Headers
Section titled “Headers”Use WebAPI.HeadersInit when a fetch, request, or response initializer expects the
JavaScript HeadersInit shape. Use WebAPI.Headers when you want a mutable Headers
object with methods.
let initHeaders = WebAPI.HeadersInit.fromDict( dict{ "Accept": "application/json", "X-Client": "web", },)
let response = await WebAPI.Fetch.fetch( "https://example.com/api/users", ~init={headers: initHeaders},)Build and edit a Headers object directly when you need to append, set, inspect, or pass
headers around.
let headers = WebAPI.Headers.fromDict(dict{"Accept": "application/json"})
headers->WebAPI.Headers.set(~name="X-Request-ID", ~value="abc-123")headers->WebAPI.Headers.append(~name="X-Trace", ~value="enabled")
switch headers->WebAPI.Headers.get("Accept") {| Null.Value(value) => Console.log(value)| Null => Console.log("missing")}
let hasTrace = headers->WebAPI.Headers.has("X-Trace")Headers can also be created from key-value arrays or copied from another Headers object.
let fromPairs = WebAPI.Headers.fromKeyValueArray([ ("Accept", "application/json"), ("X-Client", "web"),])
let clonedHeaders = WebAPI.Headers.fromHeaders(fromPairs)let initFromHeaders = WebAPI.HeadersInit.fromHeaders(clonedHeaders)Response headers are normal Headers values.
let contentType = response.headers->WebAPI.Headers.get("Content-Type")
switch contentType {| Null.Value(value) => Console.log(value)| Null => Console.log("unknown content type")}Requests
Section titled “Requests”Create requests from URLs, then pass them to fetch or clone/read them with request methods.
let req = WebAPI.Request.fromURL( "https://example.com/form", ~init={ method: "POST", headers: WebAPI.HeadersInit.fromDict( dict{"Content-Type": "application/x-www-form-urlencoded"}, ), body: WebAPI.BodyInit.fromString("name=ReScript"), },)
let response = await WebAPI.Fetch.fetchWithRequest(req)let cloned: WebAPI.Request.t = req->WebAPI.Request.clonelet bodyText = await cloned->WebAPI.Request.textUse fromRequest to copy an existing request and override initializer fields.
let authenticatedHeaders = WebAPI.HeadersInit.fromDict( dict{ "Accept": "application/json", "Authorization": "Bearer token", },)
let authenticatedReq = WebAPI.Request.fromRequest( req, ~init={headers: authenticatedHeaders},)
let authenticatedResponse = await WebAPI.Fetch.fetchWithRequest(authenticatedReq)Request body readers mirror the browser API and return promises.
let requestText = await req->WebAPI.Request.textlet requestJson = await req->WebAPI.Request.jsonlet requestBlob = await req->WebAPI.Request.blobResponses
Section titled “Responses”Construct responses with the body constructor that matches the body value.
let created = WebAPI.Response.fromString( "created", ~init={ status: 201, statusText: "Created", headers: WebAPI.HeadersInit.fromDict(dict{"Content-Type": "text/plain"}), },)
let empty = WebAPI.Response.fromNull(~init={status: 204})Responses expose record fields for status metadata and methods for cloning or reading the body.
if created.ok { let text = await created->WebAPI.Response.text Console.log(text)} else { Console.log(created.statusText)}
let copy = created->WebAPI.Response.clonelet copyText = await copy->WebAPI.Response.textUse the static helpers for browser response factories.
let failed = WebAPI.Response.error()let redirect = WebAPI.Response.redirect(~url="/login", ~status=302)DOM values are operated on through public interface modules.
let document = WebAPI.DOM.window->WebAPI.Window.document
let maybeButton = document->WebAPI.Document.querySelector("button")->Null.toOption
switch maybeButton {| Some(button) => switch button->WebAPI.Element.getAttribute("data-user-id") { | Null.Value(id) => Console.log(id) | Null => Console.log("anonymous") }| None => Console.log("button not found")}Use conversion helpers when moving between related DOM interface types.
let element = document->WebAPI.Document.createElement("div")let node = element->WebAPI.Element.asNodeClassifying DOM values
Section titled “Classifying DOM values”Event libraries and browser APIs sometimes return a type that is broader than the value present at runtime. A React form event target, for example, does not tell ReScript whether the target is an input, select, or another element. Calling an input-specific API then requires narrowing that value.
Use classify instead of repeating an instanceof binding and an unchecked Obj.magic cast. The
classifier performs the runtime check and returns the correctly typed value as an option.
let readInputValue = target => switch target->WebAPI.HTMLInputElement.classify { | Some(input) => Some(input.value) | None => None }The classifier parameter is polymorphic, so a library-owned target can be passed directly without an
identity cast to a WebAPI type first. Element.classify, Document.classify,
HTMLElement.classify, HTMLInputElement.classify, HTMLImageElement.classify, and
HTMLCanvasElement.classify follow the same contract. Their matching isInstanceOf functions are
available when only a boolean check is needed.
Classification uses the corresponding constructor from globalThis. It returns None rather than
raising when that constructor is unavailable in a server or worker environment. Like JavaScript
instanceof, it only recognizes values created in the current realm; values from another window or
iframe may not match. Enable the WebAPI.HTML feature for HTML-specific classifiers and
WebAPI.Canvas for HTMLCanvasElement.classify.
Visual Viewport
Section titled “Visual Viewport”Window.visualViewport returns a nullable WebAPI.VisualViewport.t.
let maybeViewport = WebAPI.DOM.window->WebAPI.Window.visualViewport->Null.toOption
switch maybeViewport {| Some(viewport) => Console.log(Float.toString(viewport.width)) Console.log(Float.toString(viewport.scale))| None => Console.log("visual viewport unavailable")}URL helpers expose constructors and static functions on the same public module.
let url = WebAPI.URL.make(~url="/docs", ~base="https://rescript-lang.org")let canParse = WebAPI.URL.canParse(~url="/docs", ~base="https://rescript-lang.org")let json = url->WebAPI.URL.toJSONURLSearchParams values can be used directly as fetch request bodies through BodyInit.
let params = WebAPI.URLSearchParams.fromString("search=rescript")let response = await WebAPI.Fetch.fetch( "https://example.com/search", ~init={ method: "POST", body: WebAPI.BodyInit.fromURLSearchParams(params), },)Canvas
Section titled “Canvas”Canvas constructors use make and semantic from* names.
let path = WebAPI.Path2D.make()
path->WebAPI.Path2D.moveTo(~x=0., ~y=0.)path->WebAPI.Path2D.lineTo(~x=100., ~y=100.)
let copiedPath = WebAPI.Path2D.fromPath2D(path)let svgPath = WebAPI.Path2D.fromString("M0 0 L10 10")Streams
Section titled “Streams”Generic Web API types stay typed through their public owner modules.
let stream: WebAPI.ReadableStream.t<string> = WebAPI.ReadableStream.make()let reader = stream->WebAPI.ReadableStream.getReader