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

User info

How a Mini App reads the signed-in user's info, where that call belongs in the lifecycle, and how to keep it current when the user changes something in the host app.

1. What you get

The super app shares the signed-in user's info with your Mini App. By the time your code runs, there is a signed-in account behind it — a user reaches your Mini App from inside the super app.

One call returns everything:

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

There are no per-field APIs. getUserInfo is the only call, and it returns the whole object at once. It is a built-in API, so it does not go in SGConf.js. It is gated by one permission: scope.userInfo.

2. The permission

Read the current state with ft.getSetting before calling anything. The value has three states, not two:

authSetting['scope.userInfo']
Meaning
What to do
undefined
Never asked
ft.authorize(...) — this shows the prompt
true
Granted
Call ft.getUserInfo directly
false
Denied
ft.openSetting() — authorize will never prompt again
Never write if (!res.authSetting['scope.userInfo']). undefined is falsy, so that collapses "never asked" into "denied" and sends a brand-new user to the settings page instead of showing them the prompt. On a fresh install that is wrong 100% of the time.

Once the user taps Deny, ft.authorize fails instantly and forever with no dialog. The only way back is ft.openSetting() — and that page only lists scopes you have already requested at least once, so sending an undefined user there shows them an empty screen with nothing to toggle.

The three states, the three branches:

onGetUserInfo() {
    ft.getSetting({
        success: (res) => {
            const state = res.authSetting['scope.userInfo'];

            if (state === true) {
                // Already granted — just read it
                ft.getUserInfo({
                    success: (r) => console.log(r.userInfo.first_name, r.userInfo.mobile),
                    fail: (err) => console.log('getUserInfo fail', err),
                });

            } else if (state === false) {
                // Denied before — authorize cannot prompt again, send them to settings
                ft.showModal({
                    title: 'Profile access needed',                 // ar: 'نحتاج إذن الملف الشخصي'
                    content: 'Turn on profile access to continue.', // ar: 'فعّل إذن الملف الشخصي للمتابعة.'
                    confirmText: 'Open settings',                   // ar: 'فتح الإعدادات'
                    cancelText: 'Not now',                          // ar: 'ليس الآن'
                    success: (r) => { if (r.confirm) ft.openSetting(); }
                });

            } else {
                // undefined — never asked, the prompt can still appear
                ft.authorize({
                    scope: 'scope.userInfo',
                    success: () => {
                        ft.getUserInfo({
                            success: (r) => console.log(r.userInfo.first_name, r.userInfo.mobile),
                            fail: (err) => console.log('getUserInfo fail', err),
                        });
                    },
                    fail: (err) => console.log('authorize fail', err),
                });
            }
        },
        fail: (err) => console.log('getSetting fail', err),
    });
},

ft.authorize only moves the permission. It does not return the user info, so call ft.getUserInfo yourself inside its success. The same rule applies to every permission — see Permissions.

3. What comes back

Granted
{
  "errMsg": "getUserInfo:ok",
  "rawData": "{\"id\":\"1234\", ... }",
  "userInfo": {
    "id": "1234",
    "mobile": "500000000",
    "token": "example_token_value",
    "email": "[email protected]",
    "first_name": "John",
    "last_name": "Doe",
    "nickName": "",
    "avatarUrl": "",
    "gender": "0",
    "language": "en",
    "city": "",
    "province": "",
    "country": "",
    "address": {
      "latitude": "24.7136",
      "longitude": "46.6753",
      "location": "1234 Example Street, Riyadh, ABCD1234, Saudi Arabia"
    }
  },
  "signature": ""
}
Denied
{ "errMsg": "getUserInfo:fail auth deny" }

There is no userInfo and no rawData on failure. Code that reaches for res.userInfo.id inside a fail handler will throw.

Things to know about the payload
  1. rawData is a JSON string, not an object — the same data, unparsed. Use res.userInfo, which is already an object.
  2. The populated fields are id, mobile, token, email, first_name, last_name and address. The fields nickName, avatarUrl, city, province and country exist in the response but come back as empty strings. Build the display name from first_name + last_name — nickName will not fill it in.
  3. gender is the string "0", not a number. "0" is truthy in JavaScript, so if (userInfo.gender) is always true. Compare explicitly.
  4. address is an object, not a string: { latitude, longitude, location }, with both coordinates as strings. location is the human-readable line.
  5. token identifies the user to your backend. Send it to your backend, which verifies it with Shoppingate — see Authentication.

4. Empty fields are normal

A user can sign in to the host app with only a mobile number and leave everything else blank. That account is complete and valid as far as the super app is concerned, and getUserInfo succeeds for it — it just comes back mostly empty:

{
  "errMsg": "getUserInfo:ok",
  "userInfo": {
    "id": "1234",
    "token": "example_token_value",
    "mobile": "500000000",
    "first_name": "",
    "last_name": "",
    "email": ""
  }
}

errMsg is still getUserInfo:ok. Empty fields are a successful response — not an error, not a refusal, and not a signed-out user. They mean the user never filled those in.

const name = ((userInfo.first_name || '') + ' ' + (userInfo.last_name || '')).trim();
// '' when the user never filled in a name

What your Mini App does with a missing name, email or address is your decision.

5. Where to call it — and call it again

Call getUserInfo in the place where your Mini App needs the user's info — the page or screen that shows the name, fills a form, or sends the token to your backend.

Then call it again every time the Mini App is reopened and the user comes back to that place. The user can change their info in the host app while your Mini App is closed or in the background, and your Mini App is not told about it. The only way to see the change is to read the user info again:

  • the user changes their name, email or address
  • the user changes the language
  • the user signs out and signs in as someone else
  • the user turns scope.userInfo off, or back on

Some of these make the host app clear your Mini App cache, some do not. You cannot tell which, so do not depend on it — just read the user info again.

If other places in your Mini App use the user info, update them too. Keep the result in one shared place — state management, or globalData in a blank project — and update every page or component that shows it when the new value arrives. Section 6 shows how for each project type.

Ask once, read on every show
Hook
Runs
What belongs here
App.onLaunch
Once — a warm start skips it
Ask for the permission. The only place that prompts.
Page.onShow
Every time the page becomes visible
Read the user info, then render. Never ask.
Page.onPullDownRefresh
Every pull-to-refresh
Read again — App.onShow does not fire here.

Two jobs, kept separate: asking happens once, reading happens on every show.

onLaunch runs once

App.onLaunch runs once, on a cold start. The host's close button keeps your Mini App in memory, so reopening it is a warm start — onLaunch does not run again. App.onShow does.

So onLaunch is for one-time cold-start work:

  • creating your store
  • registering global listeners — onThemeChange, error reporting
  • reading cached values out of storage with ft.getStorageSync
  • resolving the account / Mini App id with ft.getAccountInfoSync
  • asking for the permission, the one time

Anything that can change while your Mini App is closed does not belong in onLaunch — and the user info is one of those things. Read it on every open, not at launch.

Do not ask in a page either: put ft.authorize in a page and the user is prompted again on every page they open.

The full order of the app and page hooks is on the Lifecycle page.

The async gap

ft.getUserInfo is asynchronous. Whoever starts the call, the lifecycle keeps running while the host works on the answer:

App.onShow      starts the call, returns immediately
Page.onLoad     ~0 ms   — the answer is not here yet
Page.onShow     ~0 ms   — still not here
Page.onReady    first render done, still not here
   ...
success callback        — the answer finally arrives

Page.onShow can run before App.onShow has finished. So writing the result into app-level state alone will not refresh the page. The page already rendered from the previous values, and nothing told it to look again.

The symptom is specific: the user changes their name in the host app, your log shows the new name arriving, and the screen keeps showing the old one until the user pulls to refresh or switches tabs.

The fix is the same idea in every project type — the page has to be updated when the call returns. Only the mechanism differs:

Project type
Mechanism
Taro + React
State management — the page subscribes and re-renders itself
Taro + Vue
State management — same
Blank project
No reactivity available, so something else has to tell the page — a callback
Pull-to-refresh

Pull-to-refresh does not call App.onShow. The Mini App never went to the background, so nothing at app level fires. Read the user info again in onPullDownRefresh and update the same state the page renders from.

6. In your project

Pick your project type:

Native App({}) / Page({}) syntax, no framework and no state management.

Read in the page's onShow, render in the callback

Page.onShow fires when the page opens, when the user comes back to it, and when the Mini App returns to the foreground. That covers every case in section 5.

Give the read a callback, and render inside it. The page can finish loading before the answer arrives; if you render in the callback, it cannot show the previous values.

// app.js — one method, shared by every page.
// Reads only: no ft.authorize, no modal, safe to call from anywhere.
App({
    globalData: {
        userInfo: null,
    },

    refreshUserInfo(callback) {
        const that = this;
        const done = function () { if (callback) callback(); };

        ft.getSetting({
            success: (res) => {
                if (res.authSetting['scope.userInfo'] !== true) {
                    that.globalData.userInfo = null;   // turned off
                    done();
                    return;
                }

                ft.getUserInfo({
                    success: (r) => {
                        that.globalData.userInfo = r.userInfo;
                        done();
                    },
                    fail: (err) => {
                        console.log('getUserInfo fail', err);
                        that.globalData.userInfo = null;
                        done();
                    },
                });
            },
            fail: (err) => {
                console.log('getSetting fail', err);
                done();
            },
        });
    },
})
// any page
const app = getApp()

Page({
    data: {
        userName: '',
    },

    onShow() {
        app.refreshUserInfo(() => this.render());
    },

    render() {
        const info = app.globalData.userInfo;

        this.setData({
            userName: info ? info.first_name : '',
        });
    },
})

done() is called on every branch — granted, denied, failed. A callback that only runs on success leaves the page waiting forever the one time something goes wrong.

Pull-to-refresh
onPullDownRefresh() {
    app.refreshUserInfo(() => {
        this.render();
        ft.stopPullDownRefresh();
    });
},

Stopping the spinner inside the callback keeps it turning until the data is actually in, instead of ending instantly on the old values.

Why not read it in App.onShow

A blank project has no state management and no reactivity. If you read in App.onShow and save the result to globalData, nothing tells the page to look again — and App.onShow starts the call, then the page renders from the previous values long before the answer comes back.

So something has to tell the page when the call returns. The callback above is one way. An event emitter, your own observer, or pushing to getCurrentPages() all solve the same problem. Pick whichever suits your app — the page must be updated when the call returns, not before.

The cost of the callback approach is one getUserInfo per page shown, so switching tabs reads again. It is a call to the host app, not to a server, and it is what keeps the screen current.

7. Common mistakes

Mistake
What happens
if (!authSetting['scope.userInfo'])
A first-time user is sent to the settings page instead of getting the prompt
Asking in Page.onLoad
The user is prompted again on every page
Rendering from globalData.userInfo instead of in the callback
The old values stay on screen until the user pulls to refresh or switches tabs
A branch that never calls the callback
The page waits forever the one time the call fails
Asking in App.onShow
A prompt or a modal every single time the user comes back
Expecting App.onShow to cover a pull-to-refresh
It never fires — the app did not go to the background
Loading the user info in onLaunch
A warm start skips onLaunch, so a name changed in the host app never arrives
Reading only when you have no user info
A name changed in the host app never appears
Calling ft.openSetting() on an undefined scope
An empty settings screen with nothing to toggle
Expecting the user info from ft.authorize
authorize only grants. Call getUserInfo in its success.
Using nickName for the name
Empty string. Use first_name + last_name.
if (userInfo.gender)
Always true — gender is the string "0"
Reading res.userInfo in a fail handler
Throws. The failure response has no userInfo.

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