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
Web view

The wrapper

The shell mini-app that hosts your page: six files, and only four values in them that you need to change. Studio's Webview template generates it; this page is what it generates and why each piece is there.

What it consists of

miniapp/
  app.js                 siteUrl, the mini-app id, any launch-time caching
  app.json               two pages, the window, the scopes you ask for
  SGConf.js              EMPTY — a web-view wrap declares no extension APIs
  permissions.js         one table the shell dispatches from and the page renders
  pages/index/           the <web-view> and the relay. The whole mechanism.
  pages/settings/        the native button that opens the permission settings screen

Two pages, not one. The second exists only because of a platform rule covered in permissions: ft.openSetting() called from JavaScript does nothing, so re-opening the permission screen needs a real tap on a native control.

The <web-view> tag

One tag fills the page. The attributes you bind on it are the entire inbound half of the bridge, and choosing the wrong event is the most common early mistake.

<!--
  bindevent   fires as soon as the page sends. Use this for anything live.
  bindmessage fires only in a batch when the page is LEFT — not when it is sent.
  bindload / binderror are the only signal that the URL loaded at all.
-->
<web-view
  src="{{src}}"
  bindevent="onPageEvent"
  bindmessage="onPageMessage"
  bindload="onPageLoad"
  binderror="onPageError">
</web-view>
Event
When
What it is for
bindevent
live
Fires as soon as the page sends. Use this for anything interactive.
bindmessage
batched
Fires only when the page is left — on back, component destroy or share. Not when the message is sent. Useful for a summary on exit, useless for a request.
bindload
once
The URL loaded. The only positive confirmation you get.
binderror
once
The URL did not load. Usually a domain that is not allow-listed, and the only hint you will get — the screen stays blank.
bindtimeout
once
The page did not call ft.complete() in time.
bindmessage sounds like the one you want and is almost never it. It delivers nothing until the user leaves the page, so a request sent through it appears to vanish. Use bindevent for anything you expect an answer to.

app.js

App({
  globalData: {
    // Where your page is served from.
    //
    //   localhost — simulator only, and needs urlCheck off. A device cannot reach your
    //               laptop, and localhost can never be allow-listed.
    //   https     — what a device needs, and the host must be registered in the
    //               developer portal or the web-view loads nothing.
    siteUrl: "https://your-page.example.com/",

    // Sent as `miniappId` on host-function calls.
    miniAppId: "",
  },
  onLaunch() {
    console.log("[shell] wrapping", this.globalData.siteUrl);
  },
});

app.json

Declare only the scopes you actually use. Asking for Bluetooth and the address book in an app that needs neither is a review risk and an obvious trust problem for the user who reads the prompt.

{
  "pages": [
    "pages/index/index",
    "pages/settings/settings"
  ],
  "window": {
    "navigationBarTitleText": "Your app",
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black",
    "backgroundTextStyle": "light",
    "backgroundColor": "#f5f5f7"
  },
  "permission": {
    "scope.userLocation": {
      "desc": "لتحديد موقعك وإظهار الخيارات القريبة\n\nUsed to find your location and show nearby options."
    }
  },
  "style": "v2",
  "sitemapLocation": "sitemap.json"
}

Each desc is rendered by the host, in its own prompt, before any of your JavaScript runs. There is no language to switch on at that moment — so put both languages in the one string, Arabic first. This is the one place in your app where you cannot localise at runtime.

SGConf.js is empty, and should stay empty

// EMPTY ON PURPOSE. A web-view wrap needs no entries here.
//
// Declaring a name is what creates `ft.<name>` on the mini-app runtime, so an entry is
// owed only when a NATIVE page calls `ft.<name>(…)`. A web page calling
// ft.callNativeAPI('<name>', …) reaches the host directly and never consults this table.
module.exports = {
  extApi: [],
};

Declaring a name in extApi is what creates ft.<name> on the mini-app runtime. That matters for a native page calling ft.orderPayment(…). It does not matter for your page calling ft.callNativeAPI('orderPayment', …), which reaches the host directly and never consults this table.

So a wrap that takes payment, reads order history or opens support tickets adds nothing here. An empty extApi is not evidence that a host function is unavailable to your page. You only owe an entry when you add a native page of your own that calls one.

If you do add one: params is a type contract, not an example. The container checks the shape of what you pass against those placeholders before dispatching, and rejects a mismatch without ever reaching an implementation — a rejection that is indistinguishable from api not implemented.

Reusing it

Four values, and then it hosts anything:

Change
Where
Your page URL
app.js → globalData.siteUrl
Your mini-app id
app.js → globalData.miniAppId, and fide.project.config.json → appid
The title the host bar shows
app.json → window.navigationBarTitleText
The scopes you remove
app.json → permission — see below

Next

What a page can reach — and how the shell relays what it cannot.

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