Artist: Daryna Mikhailenko
Boot.gs is a lightweight framework designed to help build structured Google Apps Script applications. It aims to bring familiar development patterns, such as decorators and dependency injection, to the Apps Script environment to aid in code organization.
Install the framework via npm:
npm install bootgsCreate a class to handle your application's logic. Decorators make it easy to map methods to specific endpoints or events.
import {Get, RestController} from "bootgs";
@RestController("api/sheet")
export class SheetController {
/**
* Handles GET requests to /api/sheet/active-range
*/
@Get("active-range")
getActiveRange(): string {
return "This action returns the active sheet range.";
}
}Bootstrap your application by creating an App instance and delegating the standard Apps Script entry points (doGet, doPost) to it.
Important
The framework requires that you delegate these global entry points so it can intercept and route the incoming events.
Use App for synchronous execution:
import {App} from "bootgs";
import {SheetController} from "./SheetController";
/**
* Global entry point for GET requests.
*/
export function doGet(event: GoogleAppsScript.Events.DoGet) {
const app = App.create({
controllers: [SheetController]
});
return app.doGet(event);
}
/**
* Global entry point for POST requests.
*/
export function doPost(event: GoogleAppsScript.Events.DoPost) {
const app = App.create({
controllers: [SheetController]
});
return app.doPost(event);
}Use AsyncApp when you need to handle asynchronous operations (e.g., UrlFetchApp promises or other async tasks) in your controllers:
Tip
Only use AsyncApp if your controller methods are async or return a Promise. For standard synchronous tasks, the regular App is more lightweight.
import {AsyncApp} from "bootgs";
import {SheetController} from "./SheetController";
/**
* Global entry point for GET requests.
*/
export async function doGet(event: GoogleAppsScript.Events.DoGet) {
const app = AsyncApp.create({
controllers: [SheetController]
});
return await app.doGet(event);
}
/**
* Global entry point for POST requests.
*/
export async function doPost(event: GoogleAppsScript.Events.DoPost) {
const app = AsyncApp.create({
controllers: [SheetController]
});
return await app.doPost(event);
}- Decorator-based Routing: Intuitive mapping of HTTP and Apps Script events (GET, POST, etc.).
- Spring Boot & NestJS Patterns: Familiar decorators like
@RequestMapping,@RestController,@ResponseBody, and theResponseEntityclass. - Flexible Responses: Full control over HTTP status codes, headers, and MIME types using
ResponseEntity. - Validation: Declarative parameter validation using Spring Boot-style decorators like
@Min,@Max,@Email, etc. - Pipes & Validation: Transform and validate incoming data with
@UsePipesand built-in pipes (e.g.,ParseNumberPipe). - Global Error Handling: Centralized exception management using
@ControllerAdviceand@ExceptionHandler. - Dependency Injection: Fully-featured DI for better decoupling and testability.
- Type Safety: Built with TypeScript for a robust development experience.
- Modern Architecture: Inspired by frameworks like NestJS and Spring Boot.
The primary goal of Boot.gs is to ensure your code remains environment-agnostic. It should function identically whether it’s triggered via doGet/doPost or google.script.run.
Since Google Apps Script (GAS) has certain constraints on headers and routing, the framework implements a Virtual Transport Layer. This layer "tucks" your request metadata (like the HTTP method and path) into parameters so the framework handles the routing for you seamlessly.
To simulate a standard HTTP request, you pass these key parameters to the framework:
method: Simulates the HTTP method (GET,POST,PUT,DELETE, etc.).pathname(orpath): The virtual resource path (e.g.,/api/users/1).headers: A JSON-stringified object containing request headers.
Caution
Never pass sensitive secrets in the headers object via query parameters!
Since the Virtual Transport Layer passes all request metadata (including headers) via URL query parameters, you must never include sensitive information like API keys or Bearer tokens inside the headers object when calling the script via its Web App URL. URLs (and their query strings) are frequently logged in plain text. For sensitive data, always use the payload body of a POST request.
The framework supports a variety of output formats. You can specify the desired format using the produces property in the @RequestMapping decorator (or its aliases like @Get, @Post) or by returning a ResponseEntity with a specific MIME type.
| Enum Value | MIME Type | Output |
|---|---|---|
JSON |
application/json |
Standard JSON via ContentService |
TEXT |
text/plain |
Plain text output via ContentService |
HTML |
text/html |
HTML content via HtmlService |
XML |
application/xml |
XML content via ContentService |
RSS |
application/rss+xml |
RSS feed via ContentService |
ATOM |
application/atom+xml |
Atom feed via ContentService |
CSV |
text/csv |
CSV data via ContentService |
ICAL |
text/calendar |
iCalendar data via ContentService |
VCARD |
text/vcard |
vCard data via ContentService |
JAVASCRIPT |
application/javascript |
JavaScript output via ContentService |
Use this when building Sidebars, Modals, or Add-ons.
Tip
To receive a raw string (which is faster and easier to parse in client-side JS), include the X-Request-Source: internal header in your request.
Example (Client-side JS):
const path = "/api/users/123";
const method = "GET";
const headers = JSON.stringify({
"X-Request-Source": "internal"
});
// Constructing the Virtual Transport Event
const event = {
pathInfo: path,
parameter: {
method,
pathname: path,
headers
},
parameters: {
method: [method],
pathname: [path],
headers: [headers]
},
queryString: `method=${method}&pathname=${encodeURIComponent(path)}&headers=${encodeURIComponent(headers)}`
};
google.script.run
.withSuccessHandler((response) => {
// Parse the optimized string response
const result = typeof response === "string" ? JSON.parse(response) : response;
console.log("Status:", result.status);
console.log("Data:", result.body);
})
.doGet(event);Use this when accessing the script via a direct link, a webhook, or a third-party service. This returns a standard GAS TextOutput or HtmlOutput.
Example Request URL:
https://script.google.com/.../exec?method=GET&pathname=%2Fapi%2Fusers%2F123
The framework automatically handles your controller's return value based on whether the @ResponseBody decorator is used (note that @RestController applies this by default):
If the controller method is not marked with @ResponseBody, the framework returns a full HTTP-like payload:
{
"status": 200,
"statusText": "OK",
"ok": true,
"headers": { "Content-Type": "application/json" },
"body": { "id": 123, "name": "John Doe" }
}If the method is marked with @ResponseBody, the framework bypasses the payload wrapper and returns only the data directly.
Tip
Custom Axios Adapter
Building those google.script.run payloads manually can be tedious. A custom Axios adapter specifically for GAS Web Apps is currently in development. It will completely abstract the virtual transport layer, allowing you to use standard axios.get() or axios.post() in your frontend.
Note
Added full support for XML, RSS, and other MIME types as requested!
Class decorators
| Decorator | Returns | Description |
|---|---|---|
@Controller(type?: string, options?: object) |
ClassDecorator |
Marks a class as a general-purpose controller. |
@RequestMapping(path?: string, method?: RequestMethod | RequestMethod[]) |
ClassDecorator & MethodDecorator |
Maps a specific request path onto a controller or a handler method. |
@HttpController(basePath?: string) |
ClassDecorator |
Marks a class as an HTTP request controller. Default base path is /. |
@RestController(basePath?: string) |
ClassDecorator |
Marks a class as a REST controller. Automatically applies @ResponseBody to all handler methods. |
@ResponseBody() |
ClassDecorator & MethodDecorator |
Indicates that the return value should be bound to the response body. |
@ControllerAdvice() |
ClassDecorator |
Marks a class as a global exception handler and data binder. |
@SheetController(sheetName?: string | string[] | RegExp) |
ClassDecorator |
Marks a class as a Google Sheets event controller. Can be filtered by sheet name (string, array, or RegExp). |
@DocController() |
ClassDecorator |
Marks a class as a Google Docs event controller. |
@SlideController() |
ClassDecorator |
Marks a class as a Google Slides event controller. |
@FormController() |
ClassDecorator |
Marks a class as a Google Forms event controller. |
@Service() |
ClassDecorator |
Marks a class as a service, typically holding business logic. |
@Repository() |
ClassDecorator |
Marks a class as a repository, abstracting data access logic. |
@Injectable() |
ClassDecorator |
Marks a class as available for dependency injection. |
| Aliases | ||
@DocsController() |
ClassDecorator |
Alias for @DocController(). |
@SlidesController() |
ClassDecorator |
Alias for @SlideController(). |
@FormsController() |
ClassDecorator |
Alias for @FormController(). |
Method decorators
| Decorator | Returns | Description |
|---|---|---|
@Install() |
MethodDecorator |
Handles onInstall event. |
@Open() |
MethodDecorator |
Handles onOpen event. |
@Edit(...range?: (string | RegExp | string[])[]) |
MethodDecorator |
Handles onEdit event. Filter by A1-notation, sheet name, or RegExp. |
@Change(changeType?: SheetsOnChangeChangeType | SheetsOnChangeChangeType[]) |
MethodDecorator |
Handles onChange event. Filter by SheetsOnChangeChangeType. |
@SelectionChange() |
MethodDecorator |
Handles onSelectionChange event. |
@FormSubmit(...formId?: (string | string[])[]) |
MethodDecorator |
Handles onFormSubmit event. Filter by one or more form IDs. |
| HTTP Methods | ||
@RequestMapping(path?: string, method?: RequestMethod | RequestMethod[]) |
ClassDecorator & MethodDecorator |
Maps a specific request path onto a controller or a handler method. |
@Get(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Maps a method to handle HTTP GET requests. Default path is /. |
@Post(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Maps a method to handle HTTP POST requests. |
@Put(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Maps a method to handle HTTP PUT requests. |
@Patch(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Maps a method to handle HTTP PATCH requests. |
@Delete(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Maps a method to handle HTTP DELETE requests. |
@Head(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Maps a method to handle HTTP HEAD requests. |
@Options(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Maps a method to handle HTTP OPTIONS requests. |
| Error Handling & Security | ||
@ExceptionHandler(value?: Newable | Newable[]) |
MethodDecorator |
Annotation for handling exceptions in specific handler classes and/or handler methods. |
@ResponseStatus(value: number) |
MethodDecorator & ClassDecorator |
Marks a method or exception class with the status code that should be returned. |
@UsePipes(...pipes: any[]) |
MethodDecorator & ClassDecorator |
Specifies the pipes to be used for a controller or method. |
| Aliases | ||
@GetMapping(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Alias for @Get(). |
@PostMapping(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Alias for @Post(). |
@PutMapping(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Alias for @Put(). |
@PatchMapping(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Alias for @Patch(). |
@DeleteMapping(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Alias for @Delete(). |
@HeadMapping(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Alias for @Head(). |
@OptionsMapping(options?: string | HttpDecoratorOptions) |
MethodDecorator |
Alias for @Options(). |
Parameter decorators
| Decorator | Returns | Description |
|---|---|---|
@Event() |
ParameterDecorator |
Injects the full Google Apps Script event object. |
@Request(key?: string) |
ParameterDecorator |
Injects the full request object or a specific property. |
@Headers(key?: string) |
ParameterDecorator |
Injects request headers or a specific header value. |
@Body(key?: string, ...pipes: any[]) |
ParameterDecorator |
Injects the full request body or a specific key. Supports transformation pipes. |
@Param(key?: string, ...pipes: any[]) |
ParameterDecorator |
Injects values from URL path parameters. Supports transformation pipes. |
@Query(key?: string, ...pipes: any[]) |
ParameterDecorator |
Injects values from URL query parameters. Supports transformation pipes. |
@Inject(token: any) |
ParameterDecorator |
Explicitly specifies an injection token for a dependency. |
@Value(key: string) |
ParameterDecorator & PropertyDecorator |
Injects a value from the application configuration. |
| Aliases | ||
@Autowired(token?: any) |
ParameterDecorator & PropertyDecorator |
Alias for @Inject(). |
@RequestBody(key?: string, ...pipes: any[]) |
ParameterDecorator |
Alias for @Body(). |
@PathVariable(key?: string, ...pipes: any[]) |
ParameterDecorator |
Alias for @Param(). |
@RequestParam(key?: string, ...pipes: any[]) |
ParameterDecorator |
Alias for @Query(). |
| Validation Decorators (Spring Boot style) | ||
@AssertFalse() |
ParameterDecorator |
Validates that the value is false. |
@AssertTrue() |
ParameterDecorator |
Validates that the value is true. |
@Email() |
ParameterDecorator |
Validates that the value is a valid email address. |
@Max(value: number) |
ParameterDecorator |
Validates that the value is less than or equal to the specified maximum. |
@Min(value: number) |
ParameterDecorator |
Validates that the value is greater than or equal to the specified minimum. |
@Negative() |
ParameterDecorator |
Validates that the value is strictly negative. |
@NegativeOrZero() |
ParameterDecorator |
Validates that the value is negative or zero. |
@NotBlank() |
ParameterDecorator |
Validates that the value is not null and contains at least one non-whitespace character. |
@NotEmpty() |
ParameterDecorator |
Validates that the value is not null and not empty (works for strings, arrays, and objects). |
@Pattern(regexp: string | RegExp) |
ParameterDecorator |
Validates that the value matches the specified regular expression. |
@Positive() |
ParameterDecorator |
Validates that the value is strictly positive. |
@PositiveOrZero() |
ParameterDecorator |
Validates that the value is positive or zero. |
@Size(options: { min?: number, max?: number }) |
ParameterDecorator |
Validates that the size of the value is between the specified minimum and maximum. |
Pipes can be used to transform data before it reaches your handler:
| Pipe | Description |
|---|---|
ParseNumberPipe |
Transforms a string to a number. |
ParseFloatPipe |
Transforms a string to a float. |
ParseBooleanPipe |
Transforms a string to a boolean. |
AssertFalsePipe |
Validates that the value is false. |
AssertTruePipe |
Validates that the value is true. |
EmailPipe |
Validates that the value is a valid email address. |
MaxPipe |
Validates that the value is less than or equal to the specified maximum. |
MinPipe |
Validates that the value is greater than or equal to the specified minimum. |
NegativePipe |
Validates that the value is strictly negative. |
NegativeOrZeroPipe |
Validates that the value is negative or zero. |
NotBlankPipe |
Validates that the value is not blank. |
NotEmptyPipe |
Validates that the value is not empty. |
PatternPipe |
Validates that the value matches the specified regular expression. |
PositivePipe |
Validates that the value is strictly positive. |
PositiveOrZeroPipe |
Validates that the value is positive or zero. |
SizePipe |
Validates that the size of the value is within range. |
The ResponseEntity class provides a flexible way to build full HTTP responses, including status codes, headers, and MIME types.
import { Get, RestController, ResponseEntity, HttpStatus, ContentMimeType, Param } from "bootgs";
@RestController("users")
export class UserController {
@Get("{id}")
getUser(@Param("id") id: string): ResponseEntity {
const user = { id, name: "John Doe" };
if (!user) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.ok()
.header("X-Custom-Header", "Value")
.body(user);
}
@Get({ path: "export", produces: ContentMimeType.CSV })
exportData(): ResponseEntity<string> {
const csvData = "id,name\n1,John Doe";
return ResponseEntity.ok(csvData);
}
}The @ResponseBody decorator indicates that the return value of a method should be bound directly to the response body, bypassing the default framework wrapper (which normally includes status, ok, and body fields in the JSON response).
Note
@RestController automatically applies @ResponseBody to all its methods.
import { Get, HttpController, ResponseBody } from "bootgs";
@HttpController("raw")
export class RawController {
@Get("data")
@ResponseBody()
getRawData(): object {
return { message: "This will be returned as the root JSON object" };
}
}Transform parameters with pipes:
import {Get, RestController, Query, ParseNumberPipe} from "bootgs";
@RestController("users")
export class UserController {
@Get("details")
getUserDetails(@Query("id", ParseNumberPipe) id: number): object {
return {
userId: id,
message: "Success!"
};
}
}Use @ControllerAdvice to handle exceptions globally across the whole application:
import {ControllerAdvice, ExceptionHandler, ResponseStatus} from "bootgs";
@ControllerAdvice()
export class GlobalExceptionHandler {
@ExceptionHandler(Error)
@ResponseStatus(500)
handleError(error: Error): object {
return {
status: "Error",
message: error.message
};
}
}Tip
For enhanced development with Google Apps Script, we recommend using apps-script-utils, a collection of utility functions and classes that complement this framework.
We welcome contributions! Please see our Contributing Guidelines for details on our code of conduct, and the process for submitting pull requests.
Check out our Roadmap to see what we have planned for future releases.
For a detailed list of changes and updates, please refer to the CHANGELOG.
This project is licensed under the Apache-2.0 License.
⭐ Like this project? Give it a star on GitHub!

