Shoppingate Logo
HomePartnerSell on E-commAbout UsContact UsDevelopers
Documentation
Get started
  • Getting started
  • Environments
Mini-app development
  • Authoring
  • app.json config
  • Lifecycle
  • Routing & query
  • Host functions
  • · orderPayment
  • Permissions
  • Deep links
Backend integration
  • Authentication
  • Endpoints
  • Webhooks
Mini-app config

Global configuration — app.json

app.json sits in the mini-app root and configures the whole mini-app. The file is a single JSON object.

Extension file: you can add an app.ext.json alongside it. The two are merged, and app.ext.json wins on any key they share. The same works for pages and components (index.ext.json).

1. Top-level keys

Key
Type
Required
What it does
pages
string[]
Yes
Every page in the mini-app
entryPagePath
string
No
Which page opens first
window
Object
No
Default navigation bar and window behaviour
tabBar
Object
No
The bottom (or top) tab bar
permission
Object
No
The text shown when requesting a permission
networkTimeout
Object
No
Request timeouts
usingComponents
Object
No
Globally available custom components
subpackages
Object[]
No
Split the code into downloadable chunks
preloadRule
Object
No
Which subpackages to pre-download, and when
plugins
Object
No
Plugins the mini-app uses
darkmode
boolean
No
Opt in to dark mode
themeLocation
string
No
Path to theme.json — required when darkmode is true
style
string
No
"v2" for the upgraded base-component styles
useExtendedLib
Object
No
Pull in an extended library without package-size cost
resizable
boolean
No
Allow window resizing on desktop / rotation on tablets
requiredBackgroundModes
Object
No
Capabilities needed in the background, e.g. audio
referer
string
No
A referer header added to HTTP requests
lazyCodeLoading
string
No
"requiredComponents" — load component code lazily
debug
boolean
No
Deprecated. Adds a console button

2. pages — required

Each entry is a path without a file extension. The framework finds the matching .json, .js, .fxml and .ftss itself.

For this directory layout:

├── app.js
├── app.json
├── app.ftss
├── pages
│   ├── index
│   │   ├── index.fxml
│   │   ├── index.js
│   │   ├── index.json
│   │   ├── index.ftss
│   ├── logs
│       ├── logs.fxml
│       ├── logs.js
├── utils

…you write:

{
  "pages": ["pages/index/index", "pages/logs/logs"]
}

Rules that bite:

  • Adding or removing a page means editing this array. A page that exists on disk but not here cannot be navigated to.
  • Without entryPagePath, the first item is the home page index.js.
  • A path sent by the host, or a navigateTo url, must match an entry here exactly — or the mini-app quietly opens the home page instead.

3. entryPagePath

Sets which page opens first. Defaults to the first item in pages.

{ "entryPagePath": "pages/home/home" }

It takes no query parameters — a path only.

4. window

Status bar, navigation bar, title, background.

Property
Type
Default
What it does
navigationBarTitleText
string
—
Title text
navigationBarBackgroundColor
HexColor
#000000
Navigation bar background
navigationBarTextStyle
string
white
Title colour — only black or white
navigationStyle
string
default
default, custom, customV2, hide
navigationBarHideCloseButton
boolean
false
Hide the capsule's close button
navigationBarHideMoreButton
boolean
false
Hide the capsule's menu button
homeButton
boolean
false
Show a home button on non-home pages
backgroundColor
HexColor
#ffffff
Window background
backgroundColorContent
HexColor
—
Page container background
backgroundTextStyle
string
dark
Pull-down spinner — only dark or light
backgroundColorTop
string
#ffffff
Top overscroll area (iOS only)
backgroundColorBottom
string
#ffffff
Bottom overscroll area (iOS only)
enablePullDownRefresh
boolean
false
Enable pull-to-refresh everywhere
onReachBottomDistance
number
—
Distance from the bottom, in px, that fires onReachBottom
pageOrientation
string
portrait
auto, portrait, landscape
disableBackForwardGesture
boolean
false
Turn off the swipe-back gesture
initialRenderingCache
string
—
static or dynamic
handleWebviewPreload
string
static
When to preload the next page — static or manual
{
  "window": {
    "navigationBarTitleText": "Luxe Spa",
    "navigationBarBackgroundColor": "#1A2E2A",
    "navigationBarTextStyle": "white",
    "backgroundTextStyle": "light",
    "enablePullDownRefresh": true
  }
}

navigationBarTextStyle and backgroundTextStyle take two values each. Any other value is ignored, and the default silently applies — a common reason a title looks unreadable on a dark bar.

navigationStyle: "custom"

You get the top-right capsule and nothing else — you draw the entire bar yourself, including the status-bar inset. Budget for that before choosing it. It has no effect inside a web-view component.

5. tabBar

Property
Type
Required
Default
What it does
list
Array
Yes
—
The tabs — minimum 2, maximum 5
color
HexColor
Yes
—
Unselected label colour
selectedColor
HexColor
Yes
—
Selected label colour
backgroundColor
HexColor
Yes
—
Bar background
borderStyle
String
No
black
Top border — only black or white
position
String
No
bottom
bottom or top

Each entry in list:

Property
Type
Required
What it does
pagePath
String
Yes
Must already appear in pages
text
String
Yes
The label
iconPath
String
No
Unselected icon — local file only, max 40 kb, ~81*81 px
selectedIconPath
String
No
Selected icon, same rules
{
  "tabBar": {
    "color": "#999999",
    "selectedColor": "#C5A059",
    "backgroundColor": "#ffffff",
    "borderStyle": "black",
    "list": [
      { "pagePath": "pages/index/index", "text": "Home" },
      { "pagePath": "pages/services/services", "text": "Explore" },
      { "pagePath": "pages/bookings/bookings", "text": "Bookings" }
    ]
  }
}

Things that catch people:

  • Tabs render in array order.
  • Network images are not supported for icons — local paths only.
  • With position: "top", icons are not displayed at all.
  • Only pages listed here get a tab bar. Everything else is pushed on top of them.
  • Navigate to a tab page with ft.switchTab, never ft.navigateTo — and switchTab drops the query string, so pass data through globalData instead.

6. permission

The sentence a user sees when the mini-app asks for something. Without it the prompt has no explanation, and users deny more often.

{
  "permission": {
    "scope.userLocation": {
      "desc": "Your location is used to show the nearest branch"
    },
    "scope.camera": {
      "desc": "The camera is used to attach a photo to a support ticket"
    }
  }
}

desc is the only field, and it is required. Supported scopes:

scope.userLocation · scope.userLocationBackground · scope.record · scope.camera · scope.bluetooth · scope.writePhotosAlbum · scope.addPhoneContact · scope.addPhoneCalendar · scope.userInfo · scope.getPhoneNumber

7. networkTimeout

Milliseconds. Each defaults to 60000.

Property
Applies to
request
ft.request
connectSocket
ft.connectSocket
uploadFile
ft.uploadFile
downloadFile
ft.downloadFile
{
  "networkTimeout": {
    "request": 15000,
    "uploadFile": 60000
  }
}

60 seconds is a long time to stare at a spinner. Drop request to 10–20 s and handle the failure; leave uploads and downloads high.

8. usingComponents

A component declared here is available in every page and component without re-declaring it:

{
  "usingComponents": {
    "loading-spinner": "/components/loading-spinner/index"
  }
}

Use this sparingly. A global component is treated as a dependency of every page, is initialised at startup, and counts against the main package size. A component used by one page belongs in that page's own .json.

9. Dark mode

{
  "darkmode": true,
  "themeLocation": "theme.json"
}

themeLocation is required when darkmode is true. Base components then pick their own default styles, and the navigation and tab bars switch automatically.

Everything beyond the base styles is yours to adapt. Two warnings:

  • Android implements this by colour inversion — test thoroughly before shipping.
  • In dark mode, stylesheets cannot use prefers-color-scheme. Drive your colours from theme.json instead.

10. The rest

style — "v2" opts into the upgraded styles for button, icon, radio, checkbox, switch and slider.

useExtendedLib — pulls in an extended library at no package-size cost:

{ "useExtendedLib": { "kbone": true, "weui": true } }

subpackages / preloadRule — split a large mini-app so the first launch downloads less, and declare which chunks to fetch in the background.

lazyCodeLoading — "requiredComponents" loads only the component code a page actually needs, which cuts startup time on component-heavy apps.

resizable — lets a desktop window be resized or maximised, and allows rotation on tablets. Off by default.

referer — adds a referer to HTTP requests from video, image (Android only), request, downloadFile, uploadFile, loadFontFace, previewImage, previewMedia and getImageInfo. The value is built as {referer}/{appid}/{version}/view.html.

requiredBackgroundModes — declares what must keep running in the background, such as audio playback.

plugins — declares plugins the mini-app depends on.

debug — deprecated. Use ft.setEnableDebug instead, or enable debugging from the More menu on a non-production build.

11. Not currently supported

Present in the wider mini-program spec, but inert here. Setting them does nothing:

entranceDeclare · functionalPages · sitemapLocation · workers · singlePage · supportedMaterials · serviceProviderTicket · embeddedAppIdList · halfPage · restartStrategy · visualEffectInBackground

12. Per-page configuration

Each page can have its own .json that overrides the global window block for that page only. Write the window properties at the top level — there is no window wrapper:

{
  "navigationBarTitleText": "Order Details",
  "enablePullDownRefresh": true,
  "onReachBottomDistance": 100,
  "usingComponents": {
    "order-card": "/components/order-card/index"
  }
}

This is where enablePullDownRefresh usually belongs — global pull-to-refresh on every page, including ones with nothing to refresh, feels broken.

13. A complete example

{
  "entryPagePath": "pages/index/index",
  "pages": [
    "pages/index/index",
    "pages/services/services",
    "pages/bookings/bookings",
    "pages/order-detail/order-detail"
  ],
  "window": {
    "navigationBarTitleText": "Luxe Spa",
    "navigationBarBackgroundColor": "#1A2E2A",
    "navigationBarTextStyle": "white",
    "backgroundTextStyle": "light"
  },
  "tabBar": {
    "color": "#999999",
    "selectedColor": "#C5A059",
    "backgroundColor": "#ffffff",
    "borderStyle": "black",
    "list": [
      { "pagePath": "pages/index/index", "text": "Home" },
      { "pagePath": "pages/services/services", "text": "Explore" },
      { "pagePath": "pages/bookings/bookings", "text": "Bookings" }
    ]
  },
  "permission": {
    "scope.camera": {
      "desc": "The camera is used to attach a photo to a support ticket"
    }
  },
  "networkTimeout": {
    "request": 15000
  },
  "style": "v2"
}
Reminder — silent-drop keys. The eleven keys in section 11 above parse without error but have no runtime effect. Do not rely on them.

Symptom index

What you see
Cause
A page cannot be navigated to
Missing from the pages array
The host's path opens the home page instead
path does not match a pages entry exactly
Navigation bar title is unreadable
navigationBarTextStyle only accepts black or white
Tab bar does not render
Fewer than 2 or more than 5 entries in list, or a missing required colour
Tab icons never appear
Network image used, over 40 kb, or position: "top"
Query string lost when opening a tab
switchTab drops it — use globalData
Pull-to-refresh does nothing
enablePullDownRefresh not set globally or on that page
Permission prompt has no explanation
No permission.<scope>.desc
Slow first launch
Global usingComponents, or no subpackages
prefers-color-scheme has no effect
Not supported in dark mode — use theme.json
Requests hang for a minute
Default 60 s networkTimeout.request

Need help? We’re here.

Our team is ready to support you in every step of your experience.

Email Us
[email protected]
Sell With SGContact UsTerms of ServicePrivacy PolicyFAQTravel FAQ
Connect with us
X / TwitterLinkedInInstagramFacebookTiktok

Copyright © Shoppingate 2026

Mada
Visa
Mastercard
Apple Pay
Tamara