What you'll learn
Quick Answer
WebGPU is a JavaScript API that gives web pages direct, low-level access to the GPU for both graphics and general-purpose compute, replacing WebGL for anything serious. It exposes real GPU concepts — adapters, devices, buffers, compute pipelines — and a dedicated shading language called WGSL. As of 2026 it ships by default in Chrome, Edge, Firefox, and Safari, reaching about 76 percent of browsers globally according to caniuse.
WebGPU vs. the Browser's Own GPU Rendering, and vs. WebGL
If you've read about how browsers use the GPU to render pages, that's about compositing: the browser's own rendering pipeline promoting certain CSS properties (transform, opacity) to GPU layers so animations run smoothly without re-painting the whole page. WebGPU is a completely different thing. It's a JavaScript API that gives your own code — code you write, not the browser's internal rendering engine — direct, low-level access to the GPU, for both drawing graphics and for running general-purpose parallel computation that has nothing to do with rendering a page at all. The browser's rendering pipeline uses the GPU behind the scenes to make your page smooth; WebGPU hands that same hardware to your JavaScript.
The API it replaces for serious work is WebGL, which has been available in browsers since 2011. WebGL is itself a JavaScript binding to OpenGL ES, a graphics API designed in the mid-2000s around a much older GPU programming model: a fairly rigid state machine, limited support for general compute, and a shading language (GLSL) tied to that older model. WebGPU was designed from scratch, modeled on how modern native graphics APIs actually work — Vulkan on Linux/Android, Metal on Apple platforms, Direct3D 12 on Windows. It exposes the same underlying concepts those APIs use: explicit GPU memory buffers, precompiled pipelines, command buffers submitted to a queue, and first-class support for compute shaders (GPU programs that do math, not drawing).
That compute-shader support is the real headline. WebGL technically allows some abuse of its graphics pipeline to do general computation, but it's a hack — you're smuggling numbers through texture formats meant for pixels. WebGPU has a dedicated, first-class compute pipeline type that was part of the design from day one, which is why it's the API people reach for when they want to run real parallel computation — physics, image processing, machine learning inference — directly in a web page, using the GPU the same way native applications already do.
The Core Objects: Adapter, Device, and Buffers
Every WebGPU program starts with the same two-step handshake, and it's deliberately asynchronous because acquiring a GPU context can involve real work (driver negotiation, picking between an integrated and discrete GPU, and so on). You first ask the browser for a GPUAdapter, which represents one specific, available GPU implementation on the user's machine; from that adapter you then request a GPUDevice, which is the actual object you use to create every other resource. This two-step split exists because an adapter can report its features and hardware limits before you commit to a device, letting your code check whether the hardware can do what you need before you start allocating anything:
// Feature-detect first: WebGPU may not be available
if (!navigator.gpu) {
throw new Error("WebGPU is not supported in this browser");
}
// Request an adapter: a representation of a specific GPU + driver
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
throw new Error("No suitable GPU adapter found");
}
// Request a device: the main interface you actually use to do work
const device = await adapter.requestDevice();
// The device can tell you when it's unexpectedly lost (driver crash,
// GPU reset) - this promise resolves only when something goes wrong
device.lost.then((info) => {
console.error("WebGPU device was lost:", info.message);
});Once you have a device, everything you do goes through a small set of resource types. A GPUBuffer is a block of memory living on the GPU side, created with explicit usage flags that tell the driver what you intend to do with it (e.g. GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC for a buffer a compute shader will write into, which you'll later copy back out to read on the CPU). A GPUTexture is the equivalent for image-shaped data. You don't read and write these directly from JavaScript the way you would a normal array; you either map a buffer into CPU-visible memory explicitly, or you write the data your shader needs and then dispatch work against it and read the result back out afterward, all through the device's command queue.
Pipelines and WGSL: How You Actually Write GPU Code
The actual GPU program you write is not JavaScript — it's WGSL (WebGPU Shading Language), a new shading language designed specifically for this API rather than reused from an older graphics standard. A WGSL compute shader declares which buffers it binds to with @group/@binding annotations, marks its entry function with @compute, and specifies a @workgroup_size — how many GPU threads run together as one unit, since GPUs execute work in these grouped batches rather than one thread at a time:
// WGSL: WebGPU's own shading language, used for both compute and
// render pipelines. This is a minimal compute shader that doubles
// every element of an input array.
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
let i = id.x;
data[i] = data[i] * 2.0;
}On the JavaScript side, you compile that WGSL source into a GPUShaderModule, wrap it in a GPUComputePipeline (the GPU-side equivalent of compiling and linking a program), then build a command encoder, begin a compute pass, bind your buffers, and call dispatchWorkgroups() with however many workgroups you need to cover your data. None of this runs synchronously on the main thread — you're recording a list of commands into a buffer and submitting the whole batch to the device's queue, which is what lets WebGPU keep JavaScript responsive even while the GPU is busy:
const shaderModule = device.createShaderModule({ code: wgslSource });
const pipeline = device.createComputePipeline({
layout: "auto",
compute: { module: shaderModule, entryPoint: "main" },
});
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatchWorkgroups(Math.ceil(dataLength / 64));
passEncoder.end();
device.queue.submit([commandEncoder.finish()]);This is, deliberately, a verified description of the documented API shape rather than something run in this environment — WebGPU requires an actual browser with GPU access, which this sandbox doesn't have. The object names, method signatures, and WGSL syntax above (GPU, GPUAdapter, GPUDevice, GPUBuffer, GPUComputePipeline, the @compute/@workgroup_size annotations, dispatchWorkgroups) were checked directly against MDN's current WebGPU API documentation and the WGSL specification, not recited from memory, but the code samples themselves are illustrative of the real shape of the API rather than output from an executed program.
Why Compute Matters: What People Actually Build With This
The reason compute shaders are worth the trouble is that a GPU isn't just "a faster CPU" — it's built around a completely different execution model: thousands of simple cores running the same instruction across many pieces of data simultaneously (SIMD at massive scale), versus a CPU's handful of cores optimized for running different instructions quickly in sequence. Work that's naturally data-parallel — the same small operation repeated independently across a huge array — is exactly what GPUs are built for, and exactly the kind of work a browser JavaScript engine running on the CPU is relatively bad at.
That's why WebGPU compute is showing up in a specific, recognizable set of use cases: running small machine learning models directly in the browser for inference (projects like transformers.js and ONNX Runtime Web use WebGPU as a backend when it's available, instead of shipping inference to a server), real-time image and video processing (filters, background removal, upscaling) without a round trip to an API, physics simulations and particle systems for browser games and creative coding, and scientific or data visualization that needs to crunch large datasets client-side. None of these are new ideas — native applications and CUDA have done GPU compute for years — what's new is doing it portably, inside a web page, without installing anything.
Browser Support Right Now
Browser support reached a real milestone in 2026: WebGPU now ships enabled by default, not behind a flag, across all four major browser engines, though the rollout wasn't simultaneous. Chrome and Edge have supported it since version 113 on Windows, macOS, and ChromeOS, and added Android support (on devices with Qualcomm or ARM GPUs running at least Android 12) in Chrome 121. Firefox shipped it on Windows first, in Firefox 141, with Mac, Linux, and Android support following afterward. Safari ships WebGPU enabled on macOS Tahoe 26, iOS 26, iPadOS 26, and visionOS 26. Taken together, caniuse puts global adoption at roughly 76 percent as of early 2026, with the remaining gap concentrated in older devices and Android GPU drivers that haven't picked it up yet.
Because that gap is real, production code treats WebGPU as a progressive enhancement, not a given: check if (navigator.gpu) before attempting anything, and have a fallback path (WebGL for graphics, or a CPU-based implementation for compute) ready for browsers or devices that don't support it. A requestAdapter() call can also legitimately return null even in a browser that supports the API, if the specific GPU and driver combination can't satisfy WebGPU's requirements, which is why the code above checks for that explicitly rather than assuming the promise resolving means you're guaranteed a usable adapter.
Common Gotchas When You Start
A few things catch people off guard the first time they use this API, beyond the async handshake already shown above. Everything is asynchronous and queue-based by design: you don't call a function and get a GPU result back directly, you record commands, submit them, and separately read results back once the GPU has processed them, which takes some getting used to if you're used to synchronous CPU-side array code. Error handling is also different from a normal JavaScript exception model — WebGPU validates your API usage extensively and will raise errors through the device's error scopes rather than throwing synchronously in most cases, and a device can be lost entirely (driver crash, GPU reset, another tab hogging the GPU) which your code needs to handle via the device.lost promise rather than assuming the device you acquired stays valid forever.
It's also worth knowing this API isn't strictly confined to the browser tab. The same underlying native libraries that power WebGPU in browsers — Google's Dawn and Mozilla's wgpu — are also usable outside a browser context, and some JavaScript runtimes have experimented with exposing a navigator.gpu-shaped API outside the DOM entirely for server-side or CLI GPU compute. That's a secondary detail, though; the overwhelming majority of WebGPU code today is written for, and runs inside, an actual web page.
