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.
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.
Read the current state with ft.getSetting before calling anything. The value has three states, not two:
undefinedft.authorize(...) — this shows the prompttrueft.getUserInfo directlyfalseft.openSetting() — authorize will never prompt againif (!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.
{
"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": ""
}{ "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.
rawData is a JSON string, not an object — the same data, unparsed. Use res.userInfo, which is already an object.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.gender is the string "0", not a number. "0" is truthy in JavaScript, so if (userInfo.gender) is always true. Compare explicitly.address is an object, not a string: { latitude, longitude, location }, with both coordinates as strings. location is the human-readable line.token identifies the user to your backend. Send it to your backend, which verifies it with Shoppingate — see Authentication.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.
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:
scope.userInfo off, or back onSome 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.
App.onLaunchPage.onShowPage.onPullDownRefreshApp.onShow does not fire here.Two jobs, kept separate: asking happens once, reading happens on every show.
onLaunch runs onceApp.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:
onThemeChange, error reportingft.getStorageSyncft.getAccountInfoSyncAnything 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.
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:
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.
Pick your project type:
Native App({}) / Page({}) syntax, no framework and no state management.
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.
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.
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.
if (!authSetting['scope.userInfo'])Page.onLoadglobalData.userInfo instead of in the callbackApp.onShowApp.onShow to cover a pull-to-refreshonLaunchonLaunch, so a name changed in the host app never arrivesft.openSetting() on an undefined scopeft.authorizeauthorize only grants. Call getUserInfo in its success.nickName for the namefirst_name + last_name.if (userInfo.gender)gender is the string "0"res.userInfo in a fail handleruserInfo.