Explains bootgs' Virtual Transport Layer contract (how doGet/doPost events must be shaped for the router to resolve a controller method) and how to build a client that calls a bootgs backend from a web app UI, an HtmlService sidebar/dialog via google.script.run, or a deployed web app URL. Use when building a frontend that talks to bootgs controllers, or when requests aren't reaching the expected route.
Scanned 9/19/2026
Install to Claude Code
npx -y skills add bootgs/skills --skill bootgs-client --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Bootgs Client?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/bootgs-bootgs-client)More formats (shields.io, HTML) on the badges page.
---
name: bootgs-client
description: Explains bootgs' Virtual Transport Layer contract (how doGet/doPost events must be shaped for the router to resolve a controller method) and how to build a client that calls a bootgs backend from a web app UI, an HtmlService sidebar/dialog via google.script.run, or a deployed web app URL. Use when building a frontend that talks to bootgs controllers, or when requests aren't reaching the expected route.
license: Apache-2.0
metadata:
author: Maksym Stoianov
version: "1.0.0"
framework: bootgs
---
# Bootgs Client
## Available files
- **`assets/gas-http-client.ts`** — a working `callBootgsApi()` client built on `google.script.run` (see below). Copy it in and adapt.
## Why this exists
A bootgs controller method is triggered by GAS calling the native `doGet(event)`/`doPost(event)` globals — GAS hands you an event object shaped by its own trigger system, not a URL and HTTP method your `fetch` call understands. bootgs's `RequestFactory` reconstructs an `HttpRequest` (method, pathname, query, body) out of that native event. The **Virtual Transport Layer** is the convention for what to put into the event so that reconstruction resolves to the route you intend. There is no bootgs-provided client — you build the caller side yourself, against this contract.
## The contract
| Field | Where it comes from | Notes |
|---|---|---|
| `method` | a `method` parameter (`GET`, `POST`, `PUT`, `DELETE`, ...) | This is virtual — GAS itself only ever actually invokes `doGet` or `doPost`. `method` tells the router which route to match, independent of which GAS global fired. |
| `pathname` (or `path`) | a `pathname`/`path` parameter | Must include the `apiPrefix` your app was configured with (default `"/api"`) — see `bootgs-quickstart`. Route placeholders (`{id}`) must already be substituted with real values; the router does not template on the client's behalf. |
| query params | plain key/value entries in the event's `parameter`/`parameters` map | Same map that carries `method`/`pathname` — GAS doesn't distinguish "framework" params from "your" params, they're all just query-string-derived key/values. |
| body | `postData.contents` (string) + `postData.type` | Only present on `doPost`. Send a JSON string and set `postData.type` to bootgs's `ContentMimeType.JSON` so the body is parsed rather than treated as opaque text. |
| headers | not natively supported | GAS triggers don't carry HTTP headers. If you need to pass metadata that isn't a query param or body field, JSON-stringify it into a `headers` parameter — this is a workaround, not a real header channel. |
## Two transport modes
**1. Deployed web app, called over real HTTP.** If `webapp.access` allows it, the `/exec` URL accepts real requests — but GAS always receives them as `doGet` or `doPost` regardless of the actual verb the browser sent, so `method` still travels as a query/body parameter, not as the request's real HTTP verb. A plain `fetch`/`axios` call works; just always target `doGet` with `POST`/`PUT`/`DELETE` mapped through the `method` param if you want to avoid CORS preflights, or `doPost` directly if the body needs to travel as `postData`.
**2. Embedded in Sheets/Docs/Slides (sidebar or dialog), via `google.script.run`.** No real HTTP exists here — you call the global proxy directly and construct the event object yourself:
```ts
function callBootgs(fnName: "doGet" | "doPost", event: Partial<GoogleAppsScript.Events.DoGet | GoogleAppsScript.Events.DoPost>) {
return new Promise((resolve, reject) => {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
[fnName](event);
});
}
```
See `assets/gas-http-client.ts` for a complete, minimal client built on this that exposes a `fetch`-like `callBootgsApi(method, pathname, { query, body })` function — copy it in and adapt the parameter-map construction to your controllers.
## Gotchas
- **`apiPrefix` mismatch is the #1 "route not found" cause.** If the app was created with the default config, every path must start with `/api` — a client calling `/widgets` instead of `/api/widgets` gets a clean miss with no obvious error pointing at the prefix.
- **Path parameters are not templated for you.** bootgs's routes use `{id}`-style placeholders server-side (OpenAPI-compatible — see `bootgs-openapi`), but the client must send the already-substituted concrete path (`/api/v1/widgets/42`), not the placeholder form.
- **`google.script.run` callbacks are not promises.** Always wrap `withSuccessHandler`/`withFailureHandler` yourself (as above) before treating the call as awaitable — awaiting the bare `google.script.run[...]` call resolves immediately with the proxy object, not the result.
- **No real headers.** Don't design an auth scheme that depends on an `Authorization` header reaching the controller — it won't. Pass tokens as a body field or query param instead.
- **This pattern is safe from the private-function-naming pitfall, but only because it always targets `doGet`/`doPost`.** If the same add-on also exposes plain `google.script.run` calls outside the bootgs router — a menu action, a settings dialog — those functions must be public (no trailing `_`) or the call silently does nothing. See `apps-script-ui`.
## Verification
Before debugging further into bootgs internals, log the exact object your client builds and check it field-by-field against the contract table above (`method`, `pathname`, query params, `postData.contents`) — a malformed transport-layer payload is a far more common cause of "wrong controller called" or "404" than a router bug.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!