Shoppingate Logo
HomePartnerSell on E-commAbout UsContact UsDevelopers
Documentation
  • Getting started
  • Choosing your path
  • Introduction
    • Introduction
    • Sign up & login
    • Dashboard & analytics
    • Mini-Programs
    • Submit & publish a version
    • App Binding
    • Privacy Settings
    • Allowed URL
    • Users & roles
    • Introduction
    • Install & log in
    • Create a project
    • The workspace
    • Preview & upload
  • app.json config
  • Lifecycle
  • Routing & query
  • Host functions
  • orderPayment
  • Permissions
  • User info
  • Deep links
  • Overview & SDK
  • The wrapper
  • What a page can reach
  • Host functions
  • Permissions
  • Look and feel
  • Testing
  • Authentication
  • Endpoints
  • Webhooks
Runtime

Mini-app lifecycle

Two levels of lifecycle — the App (the whole mini app) and the Page (one screen). Getting this right is why data loads once, screens stay fresh, and timers don't leak after the user leaves.

Part 1 · App vs Page

A Shoppingate mini app has two levels of lifecycle. The App lifecycle is about the whole mini app: starting, going to the background, coming back, and global errors. The Page lifecycle is about one screen: being opened, shown, hidden and closed, plus what the user does on that screen.

A simple way to think about it: the App is the shop building, and Pages are the rooms inside it. The building opens once in the morning (onLaunch), and it can close and reopen its doors (onHide / onShow). People walk in and out of the rooms all day (Page onLoad, onShow, onHide, onUnload).

Side-by-side comparison

 
App
Page
Defined in
app.js
Each page's .js file, e.g. pages/product/product.js
Registered with
App({...})
Page({...})
How many
Exactly one per Mini App
One per screen, many in total
Lives for
The whole time the Mini App is running
From when the page opens until it is closed
Data
globalData, shared by all pages
data, only for that page
Updates the screen?
No, the App has no view of its own
Yes, with this.setData({...})
Accessed from a page
getApp()
this (inside the page)
Typical jobs
Login check, loading shared data, error reporting, missing-page redirects
Loading the page's content, pagination, refresh, sharing, scroll effects
Lifecycle functions
onLaunch, onShow, onHide, onError, onPageNotFound, onUnhandledRejection, onThemeChange
onLoad, onShow, onReady, onHide, onUnload, onPullDownRefresh, onReachBottom, onShareAppMessage, onPageScroll, onTabItemTap, onResize, onAddToFavorites

Same name, different meaning: onShow and onHide

Both App and Page have onShow and onHide, and they are easy to mix up.

 
App onShow / onHide
Page onShow / onHide
Is about
The whole Mini App
One page
onShow runs when
The Mini App starts, or comes back from the background
The page opens, the user comes back to it, or the Mini App comes back from the background
onHide runs when
The Mini App goes to the background
The user goes to another page, switches tabs, or the Mini App goes to the background
Parameters
onShow(options): path, query, scene, shareTicket, referrerInfo
None
Going to another page inside the mini app triggers Page onHide only. Leaving the mini app (home screen, another app) triggers both Page onHide and App onHide.

How they run together

This is the order of calls in common situations:

1. User opens the Mini App
App onLaunch → App onShow → Page A onLoad → Page A onShow → Page A onReady
2. User goes from Page A to Page B (ft.navigateTo)
Page A onHide → Page B onLoad → Page B onShow → Page B onReady
3. User goes back from Page B to Page A (ft.navigateBack)
Page B onUnload → Page A onShow
4. User leaves the Mini App (home screen / another app)
Page A onHide → App onHide
5. User comes back to the Mini App
App onShow → Page A onShow

Notice that App onLaunch and Page onLoad run only once each, while onShow and onHide can run many times.

Part 2 · App lifecycle

The full sweep of an app.js from launch to shutdown, plus the four side-channels for errors, missing pages, uncaught rejections, and theme changes.

Mini App starts
    │
App onLaunch(options)     ← once: app initialized
    │
App onShow(options)       ← app visible (start, or back from background)
    │
Page onLoad → Page onShow → Page onReady   (first page opens)
    │
    │── script or API error         → onError(error)
    │── page does not exist         → onPageNotFound(options)
    │── Promise rejected, no catch  → onUnhandledRejection(res)
    │── system theme changes        → onThemeChange(res)
    │
Page onHide → App onHide()   ← app goes to background
    │
App onShow(options)          ← app comes back to foreground

Rules for the App examples:

  1. App({...}) is called only once, in app.js. Pages are defined with Page({...}), the app with App({...}).
  2. Data that every page needs goes in globalData. A page reads it with getApp().globalData.

01onLaunch()

When: Runs when the mini app has finished initializing. It runs only once.

The options parameter tells you how the mini app was opened: path (the page opened), query (its parameters), scene (a number for the entry point, such as scanning a code or opening a shared card), shareTicket, and referrerInfo (the appId and extraData of the mini app or app that opened this one). A common use is getting the user's info with ft.getUserInfo() and saving it in globalData so every page can use it — see the Permissions page for the full flow.
// app.js
App({
  globalData: { userInfo: null },

  onLaunch(options) {
    console.log('Opened page:', options.path)
    console.log('Query:', options.query)
    console.log('Scene:', options.scene)

    ft.getUserInfo({
      success: (res) => {
        this.globalData.userInfo = res.userInfo
      },
      fail: (err) => {
        console.log('getUserInfo failed:', err)
      }
    })
  }
})

02onShow()

When: Runs when the mini app starts, or when it comes from the background to the foreground.

It can run multiple times. It receives the same options as onLaunch, so you can see how the user came back — for example from a shared card or a scanned code.
App({
  onShow(options) {
    console.log('App is visible, scene:', options.scene)

    if (options.referrerInfo) {
      console.log('Opened by appId:', options.referrerInfo.appId)
      console.log('Extra data:', options.referrerInfo.extraData)
    }
  }
})

03onHide()

When: Runs when the mini app moves from the foreground to the background.

For example, when the user goes to the home screen or switches to another app. Use it to save things or stop work that does not need to run in the background.
App({
  globalData: { cart: [] },

  onHide() {
    console.log('App moved to background')
    ft.setStorageSync('cart', this.globalData.cart)
  }
})

04onError()

When: Runs when a script error happens, or when an API call fails.

The error parameter is a string with the error message and stack. A common use is sending errors to your server so you know what went wrong on users' devices.
App({
  onError(error) {
    console.log('App error:', error)

    ft.request({
      url: 'https://api.example.com/log/error',
      method: 'POST',
      data: { message: error },
      success: () => {
        console.log('Error reported')
      },
      fail: (err) => {
        console.log('Request failed:', err)
      }
    })
  }
})

05onPageNotFound()

When: Runs when the mini app tries to open a page that does not exist.

options contains path (the missing page), query (its parameters) and isEntryPage (true if it is the first page of this launch — for example opened from an old shared link). Redirect the user to a page that exists. Use ft.switchTab instead if the target is a tab page.
App({
  onPageNotFound(options) {
    console.log('Page not found:', options.path)
    console.log('Is entry page:', options.isEntryPage)

    ft.redirectTo({
      url: '/pages/home/home'
    })
  }
})

06onUnhandledRejection()

When: Runs when a Promise is rejected and nothing catches it.

The res parameter contains reason (why it was rejected) and promise (the rejected Promise). It can also be set up with ft.onUnhandledRejection.
App({
  onUnhandledRejection(res) {
    console.log('Unhandled rejection:', res.reason)
  }
})

// Somewhere in a page: this rejection has no .catch(),
// so onUnhandledRejection runs
new Promise((resolve, reject) => {
  reject('Something went wrong')
})

07onThemeChange()

When: Runs when the system switches between light and dark theme.

The res parameter contains theme, which is 'light' or 'dark'. It only works when "darkmode": true is set in app.json. It can also be set up with ft.onThemeChange.
// app.json
{ "darkmode": true }

// app.js
App({
  globalData: { theme: 'light' },

  onThemeChange(res) {
    console.log('New theme:', res.theme)   // 'light' or 'dark'
    this.globalData.theme = res.theme
  }
})

getApp(): using the app from a page

getApp() returns the App instance, so any page can read globalData or call functions defined in App. Do not call it inside App itself — use this there.

// pages/profile/profile.js
Page({
  data: { userInfo: null },

  onLoad() {
    const app = getApp()

    this.setData({
      userInfo: app.globalData.userInfo
    })
  }
})

Putting it all together: one complete app.js

A real app usually uses only some of these. Here is a complete app.js that combines them.

// app.js
App({
  globalData: {
    userInfo: null,
    cart: [],
    theme: 'light'
  },

  onLaunch(options) {
    console.log('App launched, scene:', options.scene)
    this.globalData.cart = ft.getStorageSync('cart') || []

    ft.getUserInfo({
      success: (res) => {
        this.globalData.userInfo = res.userInfo
      },
      fail: (err) => {
        console.log('getUserInfo failed:', err)
      }
    })
  },

  onShow(options) {
    console.log('App shown, path:', options.path)
  },

  onHide() {
    ft.setStorageSync('cart', this.globalData.cart)
  },

  onError(error) {
    ft.request({
      url: 'https://api.example.com/log/error',
      method: 'POST',
      data: { message: error },
      success: () => {
        console.log('Error reported')
      },
      fail: (err) => {
        console.log('Request failed:', err)
      }
    })
  },

  onPageNotFound(options) {
    console.log('Page not found:', options.path)
    ft.redirectTo({ url: '/pages/home/home' })
  },

  onUnhandledRejection(res) {
    console.log('Unhandled rejection:', res.reason)
  },

  onThemeChange(res) {
    this.globalData.theme = res.theme
  }
})

App quick reference

Function
Think of it as
Runs
onLaunch(options)
App started, initialize
Once
onShow(options)
App became visible
Many times
onHide()
App went to background
Many times
onError(error)
Something crashed
Per error
onPageNotFound(options)
Page does not exist
Per missing page
onUnhandledRejection(res)
Promise rejected, no catch
Per rejection
onThemeChange(res)
Light/dark theme changed
Per change

Learn onLaunch, onShow and onHide first. The rest are handlers for errors and special situations.

Part 3 · Page lifecycle

One page's life: created, shown, ready, hidden, destroyed — plus every event hook a page can react to.

Page opens
    │
onLoad(query)  ← once: read URL params, first API request
    │
onShow()       ← every time the page becomes visible
    │
onReady()      ← once: first render finished, UI is usable
    │
    │── user scrolls           → onPageScroll()
    │── user pulls down        → onPullDownRefresh()
    │── user reaches bottom    → onReachBottom()
    │── user shares            → onShareAppMessage()
    │── user favorites         → onAddToFavorites()
    │── user taps a tab        → onTabItemTap()
    │── window size changes    → onResize()
    │
onHide()       ← page hidden (navigateTo, tab switch, app to background)
    │            (can come back → onShow() again)
onUnload()     ← page destroyed (redirectTo, navigateBack)

Rules for the Page examples:

  1. Page data lives in data, and you update the screen with this.setData({...}). Writing this.data.x = 1 or this.x = 1 does not update the view.
  2. Things that are not displayed (timers, flags like loading) can be kept as plain fields on this instead of in data, which avoids unnecessary re-renders.

01onLoad()

When: Runs when the page is loaded for the first time. It normally runs once.

The query parameter contains parameters passed in the page URL. For example, a page opened with /pages/detail?id=123 can read query.id.
// Opened with: ft.navigateTo({ url: '/pages/product/product?id=123' })
Page({
  data: { product: null },

  onLoad(query) {
    const id = query.id   // "123" (always a string!)

    ft.request({
      url: 'https://api.example.com/products/' + id,
      success: (res) => {
        this.setData({ product: res.data })
      },
      fail: (err) => {
        console.log('Request failed:', err)
      }
    })
  }
})

02onShow()

When: Runs when the page becomes visible.

It can run multiple times, such as when returning to a page after navigating away or when the mini app returns to the foreground.
Page({
  data: { cartCount: 0 },

  onShow() {
    ft.request({
      url: 'https://api.example.com/cart/count',
      success: (res) => {
        this.setData({ cartCount: res.data.count })
      },
      fail: (err) => {
        console.log('Request failed:', err)
      }
    })
  }
})

03onReady()

When: Runs when the page's initial rendering has finished.

At this point the UI is ready for interaction. It runs once. APIs that modify page UI, such as changing the navigation bar title, should be used after this point.
Page({
  onReady() {
    ft.setNavigationBarTitle({ title: 'Product Details' })
  }
})

04onHide()

When: Runs when the page becomes hidden.

For example, this happens when navigating to another page, switching tabs, or when the mini app moves into the background.
Page({
  onHide() {
    const video = ft.createVideoContext('promo')
    video.pause()
  }
})

05onUnload()

When: Runs when the page is unloaded and removed.

This can happen with navigation operations such as redirecting to another page or navigating back. It is commonly used for cleanup.
Page({
  onLoad() {
    this.timer = setInterval(() => {
      console.log('Running...')
    }, 1000)
  },

  onUnload() {
    clearInterval(this.timer)
  }
})

06onPullDownRefresh()

When: Runs when the user performs a pull-down refresh.

Pull-down refresh must first be enabled in the app or page configuration. After refreshing the data, ft.stopPullDownRefresh() can stop the refresh animation.
// product-list.json
{ "enablePullDownRefresh": true }

// product-list.js
Page({
  data: { products: [] },

  onPullDownRefresh() {
    ft.request({
      url: 'https://api.example.com/products',
      success: (res) => {
        this.setData({ products: res.data })
      },
      fail: (err) => {
        console.log('Request failed:', err)
      },
      complete: () => {
        ft.stopPullDownRefresh()   // always stop the animation
      }
    })
  }
})

07onReachBottom()

When: Runs when the user scrolls near the bottom of the page.

A common use is pagination or infinite scrolling — load more data when the user reaches the bottom.
Page({
  data: { products: [], page: 1 },

  onReachBottom() {
    const nextPage = this.data.page + 1

    ft.request({
      url: 'https://api.example.com/products',
      data: { page: nextPage },
      success: (res) => {
        this.setData({
          products: this.data.products.concat(res.data),
          page: nextPage
        })
      },
      fail: (err) => {
        console.log('Request failed:', err)
      }
    })
  }
})

08onShareAppMessage()

When: Runs when the user shares the mini app.

It lets you customize the shared content. It is triggered by the share button or the top-right Share menu. Defining this handler enables the top-right Share option.
Page({
  onShareAppMessage(options) {
    console.log('Share from:', options.from)   // 'button' or 'menu'

    return {
      title: 'Check out this product!',
      path: '/pages/product/product?id=123'
    }
  }
})

09onPageScroll()

When: Runs when the user scrolls the page.

It can be used for scroll-based UI behavior, such as showing a Back-to-top button.
Page({
  data: { showBackToTop: false },

  onPageScroll(options) {
    const show = options.scrollTop > 300

    if (show !== this.data.showBackToTop) {
      this.setData({ showBackToTop: show })
    }
  }
})

10onTabItemTap()

When: Runs when the user taps a bottom tab-bar item.

This applies to pages that are part of the mini app's tab bar.
Page({
  onTabItemTap(options) {
    console.log('Tab index:', options.index)
    console.log('Page path:', options.pagePath)
    console.log('Tab text:', options.text)
  }
})

11onResize()

When: Runs when the window or screen size changes.

It can be used to respond to changes in the available display dimensions.
// product-list.json
{ "pageOrientation": "auto" }

// product-list.js
Page({
  data: { layout: 'small' },

  onResize(options) {
    const width = options.size.windowWidth

    this.setData({
      layout: width >= 600 ? 'large' : 'small'
    })
  }
})

12onAddToFavorites()

When: Runs when the user adds the page to Favorites.

It allows you to customize the information associated with the page when it is added to Favorites.
Page({
  onAddToFavorites(options) {
    return {
      title: 'My Product',
      query: 'id=123'
    }
  }
})

Putting it all together: one complete page

A real product page usually uses only some of these. Here is a complete page that combines the most common ones.

// pages/product/product.js
Page({
  data: {
    product: null,
    reviews: [],
    page: 1,
    showBackToTop: false
  },

  onLoad(query) {
    this.id = query.id

    ft.request({
      url: 'https://api.example.com/products/' + this.id,
      success: (res) => {
        this.setData({ product: res.data })
      },
      fail: (err) => {
        console.log('Request failed:', err)
      }
    })
  },

  onShow() {
    console.log('Page shown')
  },

  onReady() {
    ft.setNavigationBarTitle({ title: 'Product Details' })
    ft.setNavigationBarColor({
      frontColor: '#ffffff',
      backgroundColor: '#07A35A'
    })

    ft.createSelectorQuery()
      .select('#header')
      .boundingClientRect((rect) => {
        console.log('Header height:', rect.height)
      })
      .exec()
  },

  onHide() {
    console.log('Page hidden')
  },

  onUnload() {
    console.log('Page destroyed')
  },

  onPullDownRefresh() {
    ft.request({
      url: 'https://api.example.com/products/' + this.id,
      success: (res) => {
        this.setData({ product: res.data })
      },
      fail: (err) => {
        console.log('Request failed:', err)
      },
      complete: () => {
        ft.stopPullDownRefresh()
      }
    })
  },

  onReachBottom() {
    const nextPage = this.data.page + 1

    ft.request({
      url: 'https://api.example.com/products/' + this.id + '/reviews',
      data: { page: nextPage },
      success: (res) => {
        this.setData({
          reviews: this.data.reviews.concat(res.data),
          page: nextPage
        })
      },
      fail: (err) => {
        console.log('Request failed:', err)
      }
    })
  },

  onPageScroll(options) {
    const show = options.scrollTop > 300
    if (show !== this.data.showBackToTop) {
      this.setData({ showBackToTop: show })
    }
  },

  onShareAppMessage() {
    return {
      title: 'Check out this product!',
      path: '/pages/product/product?id=' + this.id
    }
  },

  onAddToFavorites() {
    return { title: 'My Product', query: 'id=' + this.id }
  }
})

Page quick reference

Function
Think of it as
Runs
onLoad(query)
Page created, read URL params
Once
onShow()
Page became visible
Many times
onReady()
UI is ready
Once
onHide()
Page hidden (still alive)
Many times
onUnload()
Page destroyed, clean up
Once
onPullDownRefresh()
User pulled down
Per pull
onReachBottom()
User reached bottom
Per bottom reach
onShareAppMessage()
Share to chat
Per share
onPageScroll()
User scrolls
Very often
onTabItemTap()
Tab tapped (tab pages)
Per tap
onResize()
Window size changed
Per resize
onAddToFavorites()
User favorites page
Per favorite

Learn onLoad, onShow, onReady, onHide and onUnload first. The rest are event handlers you add when you need a specific feature.

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