---
name: 'grabjs-superapp-sdk'
description: 'API reference for `@grabjs/superapp-sdk`. Use when building a MiniApp that runs in the Grab SuperApp WebView and needs to call native features (camera, payments, authorization, authentication, permission, location, device storage, container UI customization) via the Grab `JSBridge`. Keywords: miniapp, webview, android, ios, jsbridge, grab, superapp.'
license: 'MIT'
---
# @grabjs/superapp-sdk
Use this SDK to call native Grab SuperApp features from a MiniApp running in the WebView. Each module covers one domain (camera, payments, location, etc.) and communicates with the native layer via `JSBridge`.
## Setup
### Installation
Install `@grabjs/superapp-sdk` with your package manager of choice.
#### NPM
```bash
npm install @grabjs/superapp-sdk
```
#### Yarn
```bash
yarn add @grabjs/superapp-sdk
```
### Importing
#### ES Modules (recommended)
Import only the modules you need:
```typescript
import { ContainerModule, ScopeModule } from '@grabjs/superapp-sdk';
```
Type guards and response types are also available as named exports:
```typescript
import { isSuccess, isError } from '@grabjs/superapp-sdk';
```
#### CDN (UMD Bundle)
If you are not using a bundler, load the SDK from a CDN and access it via the `SuperAppSDK` global.
**Always pin to a specific version** (e.g., `@x.y.z`) — omitting the version always fetches the latest release, which may contain breaking changes.
```html
```
## Core Concepts
SDK methods communicate with the native Grab SuperApp layer via `JSBridge`. They only work when your page is running inside the **Grab SuperApp WebView**. Calling a method outside that environment returns `{ status_code: 501 }`.
### Response Pattern
Every SDK method returns a response object with an HTTP-style `status_code`. SDK methods never throw — use type guards instead of try/catch.
```typescript
import { ProfileModule, isSuccess, isError } from '@grabjs/superapp-sdk';
const profile = new ProfileModule();
const response = await profile.fetchEmail();
if (isSuccess(response)) {
console.log('Result:', response.result);
} else if (isError(response)) {
switch (response.status_code) {
case 403:
// Missing OAuth scope - call IdentityModule.authorize() then ScopeModule.reloadScopes()
break;
case 426:
// Grab app version too old - prompt user to update their app
break;
default:
console.error(`Error ${response.status_code}: ${response.error}`);
}
}
```
### Status Codes
The SDK uses HTTP-style status codes for all responses:
| Code | Type | Description |
| :---- | :---------------- | :---------------------------------------------------------- |
| `200` | OK | Request successful, `result` contains response data |
| `204` | No Content | Request successful, no data returned |
| `302` | Redirect | Redirect in progress |
| `400` | Bad Request | Invalid request parameters |
| `401` | Unauthorized | Authentication required |
| `403` | Forbidden | Insufficient permission (see `@requiredOAuthScope` tag) |
| `404` | Not Found | Resource not found |
| `424` | Failed Dependency | Underlying native request failed |
| `426` | Upgrade Required | Grab app version too old (see `@minimumGrabAppVersion` tag) |
| `500` | Internal Error | Unexpected SDK error |
| `501` | Not Implemented | Outside Grab SuperApp environment |
### Type Guards
Type guards narrow the response type so TypeScript knows which fields are available:
| Guard | Matches |
| --------------------------------- | ------------------------------------------------------ |
| `isSuccess(r)` | `200`, `204` |
| `isOk(r)` | `200` |
| `isNoContent(r)` | `204` |
| `isRedirection(r)` / `isFound(r)` | `302` |
| `isClientError(r)` | `400`, `401`, `403`, `404`, `424`, `426` |
| `isServerError(r)` | `500`, `501` |
| `isError(r)` | `400`, `401`, `403`, `404`, `424`, `426`, `500`, `501` |
```typescript
import { isSuccess, isOk, isNoContent, isError } from '@grabjs/superapp-sdk';
if (isSuccess(response)) {
// narrow further if needed
if (isOk(response)) console.log(response.result);
if (isNoContent(response)) console.log('done, no data');
}
if (isError(response)) {
// response.error: string is guaranteed here
console.error(response.error);
}
```
### Streams
Some modules provide streaming methods for real-time data (location updates, media events). Subscribe to receive values over time:
```typescript
import { LocationModule, isSuccess } from '@grabjs/superapp-sdk';
const location = new LocationModule();
const subscription = location.observeLocationChange().subscribe({
next: (response) => {
if (isSuccess(response)) console.log(response.result);
},
complete: () => console.log('Stream ended'),
});
// Always unsubscribe when done to conserve battery and resources
subscription.unsubscribe();
```
You can also `await` a stream method directly to get its first value.
### Scopes and Permissions
The SDK categorizes permissions into two distinct types based on their execution context:
#### Permission Types
- **Backend Scopes** (`openid`, `profile.read`, `phone`)
- **Purpose**: Access protected resources and user data via your server.
- **Flow**: Requires a backend token exchange after authorization to retrieve data.
- **Mobile Scopes** (`mobile.geolocation`, `mobile.checkout`)
- **Purpose**: Access native device capabilities directly within the MiniApp.
- **Flow**: Grants in-app permission immediately; no backend exchange is necessary.
#### Authorization Patterns
When designing your MiniApp, you can choose between two common patterns for requesting scopes:
- **Upfront Authorization**
- Request all required scopes during app initialisation, typically alongside backend sign-in.
- _Best for_: Core permissions essential for the app to function.
- **Deferred Authorization**
- Request scopes only when the user triggers a specific feature that requires them.
- _Best for_: Optional permissions (e.g., location) to improve user experience and build trust.
#### Permission Verification Strategies
You can verify permissions either proactively before calling a method, or reactively by handling errors.
##### Proactive Checking
Proactively verify if the current session has the necessary permissions for a method using `ScopeModule.hasAccessTo()`. This is recommended before calling gated methods, as users can revoke permissions at any time via the Grab app settings.
```typescript
const scope = new ScopeModule();
const hasAccess = await scope.hasAccessTo('LocationModule', 'getCoordinate');
if (isSuccess(hasAccess) && hasAccess.result) {
// Permission is available, safe to call the method
const location = await location.getCoordinate();
}
```
##### Reactive Checking (Handling 403 Forbidden)
Methods tagged with `@requiredOAuthScope` require specific permissions. If the user hasn't granted the required scope, the method returns `403`. You must request authorization and reload scopes before retrying:
1. Call `IdentityModule.authorize()` to request the scope.
2. Call `ScopeModule.reloadScopes()` to refresh the SDK's internal permission state.
3. Retry the original method call.
```typescript
import {
LocationModule,
IdentityModule,
ScopeModule,
isSuccess,
isError,
} from '@grabjs/superapp-sdk';
const location = new LocationModule();
const identity = new IdentityModule();
const scope = new ScopeModule();
const response = await location.getCoordinate();
if (isError(response) && response.status_code === 403) {
// 1. Request authorization for the required scope
const auth = await identity.authorize({
clientId: 'your-client-id',
redirectUri: 'https://your-app.com/callback',
scope: 'mobile.geolocation', // The scope defined in @requiredOAuthScope
environment: 'production',
responseMode: 'in_place',
});
if (isSuccess(auth)) {
// 2. Reload scopes so the new permission is available
await scope.reloadScopes();
// 3. Retry the original call
const retry = await location.getCoordinate();
if (isSuccess(retry)) {
console.log('Result:', retry.result);
}
}
}
```
## Integration Guide
This guide covers the recommended setup for a MiniApp entry point — loading scopes, configuring the container UI, signalling readiness, and handling permissions.
> **Note:** The [`demo`](https://github.com/grab/superapp-sdk/tree/master/demo) folder contains two complete MiniApp samples demonstrating these integration patterns in action — one using CDN (vanilla HTML/JS) and one using React. Both implement the same user flow: OAuth authorization, user profile display, deferred location permissions, and checkout payment.
### Initialization
Follow these steps when your MiniApp launches to configure the container, authenticate the user, and track the entry event.
```typescript
import {
ContainerModule,
ScopeModule,
ContainerAnalyticsEventState,
isSuccess,
} from '@grabjs/superapp-sdk';
const container = new ContainerModule();
const scope = new ScopeModule();
async function init() {
// 1. Verify the environment
const connection = await container.isConnected();
if (!isSuccess(connection) || !connection.result?.connected) {
// Handle case where app is opened outside Grab SuperApp
return;
}
// 2. Configure the container UI
await container.setTitle('My MiniApp');
await container.setBackgroundColor('#FFFFFF');
await container.hideBackButton();
await container.hideRefreshButton();
// 3. Dismiss the native loader
await container.hideLoader();
// 4. Track app launch
await container.sendAnalyticsEvent({
state: ContainerAnalyticsEventState.HOMEPAGE,
name: 'DEFAULT',
});
// 5. Authenticate the user
// (Implementation detailed in the Authentication section below)
await signIn();
// 6. Load permission scopes — always do this before making module calls
await scope.reloadScopes();
}
init();
```
### Authentication
Trigger `IdentityModule.authorize()` to start the authorization process and request user permissions.
When authorization completes with `status_code: 200` (native `in_place` flow), `response.result` already includes `code`, `state`, and the PKCE values (`codeVerifier`, `nonce`, `redirectUri`), so you do not need `getAuthorizationArtifacts()`.
If the flow uses the web redirect instead (`status_code: 302`), the page navigates away; after the redirect lands on your callback URL, read the `code` from the query string and retrieve the stored PKCE artifacts with `IdentityModule.getAuthorizationArtifacts()`.
In either case, send those values to your backend so it can exchange the authorization code for tokens, validate the `id_token`, fetch user info, and establish the user's session.
After the session is established, call `IdentityModule.clearAuthorizationArtifacts()` and `ScopeModule.reloadScopes()` so your MiniApp can begin using the newly granted permissions.
Use `isRedirection` for `302`: that branch is separate from `isSuccess`, which only matches `200` and `204` for `authorize()`.
```typescript
import {
IdentityModule,
ScopeModule,
isSuccess,
isError,
isRedirection,
} from '@grabjs/superapp-sdk';
const identity = new IdentityModule();
const scope = new ScopeModule();
async function signIn() {
const response = await identity.authorize({
clientId: 'your-client-id',
redirectUri: 'https://your-miniapp.example.com/callback',
scope: 'openid profile.read phone mobile.storage',
environment: 'production',
responseMode: 'in_place',
});
if (isSuccess(response)) {
if (response.status_code === 200) {
const { code, state, codeVerifier, nonce, redirectUri } = response.result;
// 1. Send the values to your backend for token exchange (see Backend Token Exchange section below)
// await myBackend.exchangeTokens({ code, codeVerifier, nonce, redirectUri, state });
// 2. Clear artifacts and reload scopes
await identity.clearAuthorizationArtifacts();
await scope.reloadScopes();
} else if (response.status_code === 204) {
// User cancelled the authorization flow
await identity.clearAuthorizationArtifacts();
}
} else if (isRedirection(response)) {
// `302`: web consent — the SDK redirected the browser to GrabID. After the user returns to
// `redirectUri` with `?code=...&state=...`, read the code from the URL and call
// `getAuthorizationArtifacts()` for PKCE values, then exchange tokens (see paragraphs above).
return;
} else if (isError(response)) {
console.error('Authorization failed:', response.error);
await identity.clearAuthorizationArtifacts();
}
}
```
### Container UI & Navigation
Control the native container's appearance and behavior to match your MiniApp's branding and navigation flow.
#### Title and Background
Set the title and background color for the native container.
```typescript
await container.setTitle('My MiniApp');
await container.setBackgroundColor('#FFFFFF');
```
#### Back and Refresh Buttons
Hide these buttons when your MiniApp manages its own navigation or requires a focused, non-refreshable view. Restore them when appropriate.
```typescript
// Hide buttons
await container.hideBackButton();
await container.hideRefreshButton();
// Restore buttons
await container.showBackButton();
await container.showRefreshButton();
```
#### Closing the MiniApp
Programmatically close the MiniApp and return the user to the Grab SuperApp.
```typescript
await container.close();
```
### Opening External Links
Use `ContainerModule.openExternalLink()` to open URLs in the system browser instead of navigating away from the MiniApp WebView.
```typescript
const response = await container.openExternalLink('https://example.com');
if (isError(response)) {
console.error('Failed to open link:', response.error);
}
```
### Analytics Event Tracking
Track user interactions to monitor performance and conversion. Events are categorised by journey stage using `ContainerAnalyticsEventState`.
```typescript
import { ContainerModule, ContainerAnalyticsEventState, isSuccess } from '@grabjs/superapp-sdk';
const container = new ContainerModule();
// 1. System Event (DEFAULT)
// Send when a user lands on a key page
await container.sendAnalyticsEvent({
state: ContainerAnalyticsEventState.HOMEPAGE,
name: 'DEFAULT',
});
// 2. Named Action (INITIATE / TRANSACT)
// Send when a user performs a primary action
await container.sendAnalyticsEvent({
state: ContainerAnalyticsEventState.HOMEPAGE,
name: 'INITIATE',
});
// 3. Custom Interaction
// Send for specific interactions with additional metadata
await container.sendAnalyticsEvent({
state: ContainerAnalyticsEventState.CUSTOM,
name: 'BANNER_CLICK',
data: {
page: 'homepage',
banner_id: 'promo-summer-2024',
},
});
```
#### Journey Stages
| State | Description |
| :----------------- | :----------------------------------------------- |
| `HOMEPAGE` | Entry point or main landing page. |
| `CHECKOUT_PAGE` | Transaction confirmation or payment selection. |
| `COMPLETION_POINT` | Post-transaction or success page. |
| `CUSTOM` | Any other interaction outside the standard flow. |
#### Best Practices
- Track system events automatically when users navigate to the corresponding pages.
- Always include required data fields for transaction events to enable accurate revenue tracking.
- Use descriptive names for custom events that clearly indicate the user action being tracked.
- Never include Personally Identifiable Information (PII) in event data.
### Checkout
The checkout flow is a two-step process: your backend first initializes a transaction using your partner credentials, then your frontend triggers the native payment interface using the response from your backend.
```typescript
import {
CheckoutModule,
IdentityModule,
ScopeModule,
isSuccess,
isError,
} from '@grabjs/superapp-sdk';
const checkout = new CheckoutModule();
const identity = new IdentityModule();
const scope = new ScopeModule();
async function processPayment() {
// 1. Proactively check for checkout permission
const hasAccess = await scope.hasAccessTo('CheckoutModule', 'triggerCheckout');
if (!isSuccess(hasAccess) || !hasAccess.result) {
// Request authorization for mobile.checkout
// Note: mobile.checkout is a mobile scope; no backend exchange is needed for auth.
const authResponse = await identity.authorize({
clientId: 'your-client-id',
redirectUri: window.location.href,
scope: 'mobile.checkout',
environment: 'production',
responseMode: 'in_place',
});
if (isSuccess(authResponse) && authResponse.status_code === 200) {
await scope.reloadScopes();
} else {
return;
}
}
// 2. Fetch the initialized transaction payload from your backend
const response = await fetch('https://your-backend.example.com/init-transaction', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId: 'order-123' }),
});
const { partnerTxID, request, sessionID } = await response.json();
// 3. Trigger checkout with the backend response
const checkoutResult = await checkout.triggerCheckout({
partnerTxID,
request,
sessionID,
});
if (isSuccess(checkoutResult)) {
console.log(checkoutResult.result);
} else if (isError(checkoutResult)) {
console.error('Checkout error:', checkoutResult.error);
}
}
```
For the complete API reference, see [GrabPay API](https://developer.grab.com/docs/partner-apps/pages/developer-resources/payment/) and [CheckoutModule](https://grab.github.io/superapp-sdk/classes/CheckoutModule.html).
## API Reference
### Classes
#### `CameraModule`
SDK module for accessing the device camera via `JSBridge`.
- `scanQRCode(request: ScanQRCodeRequest): Promise` — Opens the native camera to scan a QR code.
#### `CheckoutModule`
SDK module for triggering native payment flows via `JSBridge`.
- `triggerCheckout(request: TriggerCheckoutRequest): Promise` — Triggers the native checkout flow for payment processing. (**OAuth Scope:** mobile.checkout)
#### `ContainerModule`
SDK module for controlling the WebView container via `JSBridge`.
- `close(): Promise` — Close the container and return to the previous screen.
- `getSessionParams(): Promise` — Get the session parameters from the container.
- `hideBackButton(): Promise` — Hide the back button on the container header.
- `hideLoader(): Promise` — Hide the full-screen loading indicator.
- `hideRefreshButton(): Promise` — Hide the refresh button on the container header.
- `isConnected(): Promise` — Check if the web app is connected to the Grab SuperApp via `JSBridge`.
- `onContentLoaded(): Promise` — Notify the Grab SuperApp that the page content has loaded.
- `onCtaTap(request: string): Promise` — Notify the client that the user has tapped a call-to-action (CTA).
- `openExternalLink(request: string): Promise` — Open a link in the external browser.
- `sendAnalyticsEvent(request: SendAnalyticsEventRequest): Promise` — Use this method to track user interactions and page transitions.
- `setBackgroundColor(request: string): Promise` — Set the background color of the container header.
- `setTitle(request: string): Promise` — Set the title of the container header.
- `showBackButton(): Promise` — Show the back button on the container header.
- `showLoader(): Promise` — Show the full-screen loading indicator.
- `showRefreshButton(): Promise` — Show the refresh button on the container header.
#### `DeviceModule`
SDK module for querying native device information via `JSBridge`.
- `isEsimSupported(): Promise` — Checks whether the current device supports eSIM. (**OAuth Scope:** mobile.device | **Minimum Grab App Version:** Android: 5.402.0, iOS: 5.402.0)
#### `FileModule`
SDK module for downloading files to the user's device via `JSBridge`.
- `downloadFile(request: DownloadFileRequest): Promise` — Downloads a file through `JSBridge`.
#### `IdentityModule`
SDK module for authenticating users with GrabID via `JSBridge`.
- `authorize(request: AuthorizeRequest): Promise` — Initiates an OAuth2 authorization flow with PKCE (Proof Key for Code Exchange).
This method handles both native in-app consent and web-based fallback flows.
- `clearAuthorizationArtifacts(): Promise` — Clears all stored PKCE authorization artifacts from local storage.
This should be called after a successful token exchange or when you need to
reset the authorization state (e.g., on error or logout).
- `getAuthorizationArtifacts(): Promise` — Retrieves stored PKCE authorization artifacts from local storage.
These artifacts are used to complete the OAuth2 authorization code exchange.
#### `LocaleModule`
SDK module for accessing device locale settings via `JSBridge`.
- `getLanguageLocaleIdentifier(): Promise` — Retrieves the current language locale identifier from the device.
#### `LocationModule`
SDK module for accessing device location services via `JSBridge`.
- `getCoordinate(): Promise` — Get the current geographic coordinates of the device. (**OAuth Scope:** mobile.geolocation)
- `getCountryCode(): Promise` — Get the country code based on the device's current location. (**OAuth Scope:** mobile.geolocation)
- `observeLocationChange(): ObserveLocationChangeResponse` — Subscribe to location change updates from the device. (**OAuth Scope:** mobile.geolocation)
#### `Logger`
Provides scoped logging for SDK modules.
#### `MediaModule`
SDK module for playing DRM-protected media content via `JSBridge`.
- `observePlayDRMContent(data: DRMContentConfig): ObserveDRMPlaybackResponse` — Observes DRM-protected media content playback events. (**OAuth Scope:** mobile.media)
- `playDRMContent(data: DRMContentConfig): Promise` — Plays DRM-protected media content in the native media player. (**OAuth Scope:** mobile.media)
#### `NetworkModule`
SDK module for making network requests through the native layer via `JSBridge`.
- `send(request: SendRequest): Promise` — Sends a network request through `JSBridge`.
#### `PlatformModule`
SDK module for controlling platform navigation via `JSBridge`.
- `back(): Promise` — Triggers the native platform back navigation.
This navigates back in the native navigation stack.
#### `ProfileModule`
SDK module for accessing user profile information via `JSBridge`.
- `fetchEmail(): Promise` — Fetches the user's email address from their Grab profile. (**OAuth Scope:** mobile.profile | **Minimum Grab App Version:** Android: 5.399.0, iOS: 5.399.0)
- `verifyEmail(request?: VerifyEmailRequest): Promise` — Verifies the user's email address by triggering email capture bottom sheet and OTP verification. (**OAuth Scope:** mobile.profile | **Minimum Grab App Version:** Android: 5.399.0, iOS: 5.399.0)
#### `ScopeModule`
SDK module for checking and refreshing API access permissions via `JSBridge`.
- `hasAccessTo(module: string, method: string): Promise` — Checks if the current client has access to a specific `JSBridge` API method.
- `reloadScopes(): Promise` — Requests to reload the consented OAuth scopes for the current client.
This refreshes the permissions from the server.
#### `SplashScreenModule`
SDK module for controlling the native splash / Lottie loading screen via `JSBridge`.
- `dismiss(): Promise` — Dismisses the native splash (Lottie) loading view if it is presented.
#### `StorageModule`
SDK module for persisting key-value data to native storage via `JSBridge`.
- `getBoolean(key: string): Promise` — Retrieves a boolean value from the native storage. (**OAuth Scope:** mobile.storage)
- `getDouble(key: string): Promise` — Retrieves a double (floating point) value from the native storage. (**OAuth Scope:** mobile.storage)
- `getInt(key: string): Promise` — Retrieves an integer value from the native storage. (**OAuth Scope:** mobile.storage)
- `getString(key: string): Promise` — Retrieves a string value from the native storage. (**OAuth Scope:** mobile.storage)
- `remove(key: string): Promise` — Removes a single value from the native storage by key. (**OAuth Scope:** mobile.storage)
- `removeAll(): Promise` — Removes all values from the native storage. (**OAuth Scope:** mobile.storage)
- `setBoolean(key: string, value: boolean): Promise` — Stores a boolean value in the native storage. (**OAuth Scope:** mobile.storage)
- `setDouble(key: string, value: number): Promise` — Stores a double (floating point) value in the native storage. (**OAuth Scope:** mobile.storage)
- `setInt(key: string, value: number): Promise` — Stores an integer value in the native storage. (**OAuth Scope:** mobile.storage)
- `setString(key: string, value: string): Promise` — Stores a string value in the native storage. (**OAuth Scope:** mobile.storage)
#### `SystemWebViewKitModule`
SDK module for opening URLs in the device's system browser via `JSBridge`.
- `redirectToSystemWebView(request: RedirectToSystemWebViewRequest): Promise` — Opens a URL in the device's system web browser or web view.
#### `UserAttributesModule`
SDK module for reading user-related attributes from native code via `JSBridge`.
- `getSelectedTravelDestination(): Promise` — Returns the currently selected travel destination as a lowercase ISO 3166-1 alpha-2 country code.
### Functions
#### `hasResult`
Type guard to check if an SDK response has a `result` that is neither `null` nor `undefined`.
```ts
hasResult(response: T): response is Extract
```
#### `isClientError`
Type guard to check if an SDK response has a client error status code (`400`, `401`, `403`, `404`, `424`, `426`).
```ts
isClientError(response: T): response is Extract
```
#### `isError`
Type guard to check if an SDK response has an error status code (`400`, `401`, `403`, `404`, `424`, `426`, `500`, `501`).
```ts
isError(response: T): response is Extract
```
#### `isFound`
Type guard to check if an SDK response has a `302` status code.
```ts
isFound(response: T): response is Extract
```
#### `isNoContent`
Type guard to check if an SDK response has a `204` status code.
```ts
isNoContent(response: T): response is Extract
```
#### `isOk`
Type guard to check if an SDK response has a `200` status code.
```ts
isOk(response: T): response is Extract
```
#### `isRedirection`
Type guard to check if an SDK response has a `302` status code.
```ts
isRedirection(response: T): response is Extract
```
#### `isServerError`
Type guard to check if an SDK response has a server error status code (`500`, `501`).
```ts
isServerError(response: T): response is Extract
```
#### `isSuccess`
Type guard to check if an SDK response has a success status code (`200`, `204`).
```ts
isSuccess(response: T): response is Extract
```