微信小程序第三方平台ext.json配置全攻略:uni-app实战解析
2026/9/13 6:19:45 网站建设 项目流程

1. 先搞懂机制:第三方平台为什么要下发一份ext.json

1.1 第三方平台模式的运行链路

做微信小程序第三方平台开发的兄弟,对ext.json肯定不陌生。这个东西在普通小程序开发里根本不会出现,但是一旦你开始做“第三方平台、代开发、SaaS多租户”这类业务,几乎每天都要和它打交道。我第一次接触时也困惑了很久:明明小程序有app.json、project.config.json,为什么还要来一个ext.json?

先理清第三方平台的运行链路。微信提供了一套“服务商模式”:服务商开发一套小程序代码模板,上传到微信的模板库;不同客户(比如不同商家)在小程序后台扫码授权后,服务商拿到授权凭证,再调用微信接口,把“模板代码 + 这份客户的个性化配置”一起提交上传。微信会把配置注入到代码包里,每个客户最终得到的小程序,功能逻辑相同,但商家ID、接口域名、页面标题、底部导航这些都可能不一样。

这里的“个性化配置”就是ext.json。它相当于一张装修清单:模板代码是毛坯房,ext.json告诉施工队每个房间要刷什么颜色、摆什么家具。没有它,服务商就得为每个客户复制一份代码,那模板机制就完全失去意义了。

1.2 ext.json与app.json、project.config.json的区别

很多第一次做第三方平台的开发者会把几个JSON文件搞混,我单独说一下区别:

文件作用范围生效时机谁在使用
app.json小程序本身的全局配置(页面路径、窗口、tabBar等)编译进代码,一直在小程序运行时
project.config.json微信开发者工具的工程配置(appid、编译设置等)本地开发调试时开发者工具
ext.json第三方平台下发的动态配置,覆盖或补充app.json中的部分内容第三方平台接口上传代码时注入小程序运行时,通过 wx.getExtConfigSync() 读取

简单说:app.json是“房子自带的设计图”,ext.json是“业主后加的装修要求”。普通小程序开发不会接触ext.json,只有走第三方平台通道上传代码时,微信才会把ext.json的内容注进去。这也是为什么很多在uni-app里开发第三方平台项目的同学,网上搜不到太完整的配置说明。

2. uni-app里ext.json的正确位置与构建产物

2.1 源码放在src目录,不是项目根目录

这是最多人踩的坑。uni-app工程默认的源码目录是src,编译时会把src下的内容映射到dist目录下的微信小程序产物目录(比如dist/dev/mp-weixin)。如果你把ext.json放在项目根目录(和src同级),uni-app编译器根本不会管它,打包产物里自然就没有ext.json。

正确的做法是在src目录下创建一个ext.json文件:

你的uni-app项目/ ├── src/ │ ├── pages/ │ ├── static/ │ ├── App.vue │ ├── main.js │ ├── manifest.json │ ├── pages.json │ └── ext.json ← 必须在这里 ├── dist/ │ └── dev/ │ └── mp-weixin/ │ ├── app.js │ ├── app.json │ └── ext.json ← 编译后会自动生成到这里 ├── package.json └── vite.config.js

至于ext.json里写什么内容,第3部分会完整拆解,这里先说位置。

2.2 HBuilderX与CLI命令行开发时的编译产物

如果你用的是HBuilderX,点菜单栏“运行到小程序模拟器→微信开发者工具”,uni-app会先执行编译,把src里的文件处理到dist/dev/mp-weixin目录。这个过程是增量且自动的,只要src/ext.json存在,编译后dist/dev/mp-weixin/ext.json就会一起出现。

如果你用的是CLI工程,命令行执行:

npm run dev:mp-weixin

同样会在dist/dev/mp-weixin目录下生成微信小程序产物。记住一个原则:所有需要进微信小程序包的配置,都放src根目录,让uni-app编译器帮你拷贝。我见过有人直接去dist目录里手动改ext.json,结果下一次编译又被覆盖,白改。

2.3 微信开发者工具应该导入哪个目录

打开微信开发者工具,选择“导入项目”,目录指向dist/dev/mp-weixin,AppID填你正在调试的目标小程序(或第三方平台测试号)。如果项目是通过第三方平台模式调试,开发者工具还支持“按第三方平台模式打开”,这种方式下ext.json和wx.getExtConfigSync()才能按线上链路生效。

3. ext.json核心字段逐个拆解与配置示例

3.1 字段总览表

微信官方对ext.json的配置项不算多,但每个字段的边界和用法都有讲究。先放一张总表:

字段类型必填说明
extEnableBoolean是否启用ext配置,必须为true,否则ext和extPages都不会生效
extAppidString第三方平台的AppID,不是小程序AppID
extPagesObject覆盖小程序非首页页面的窗口配置
extObject自定义业务数据,通过wx.getExtConfigSync()读取
replaceModeBoolean为true时,extPages配置的字段会覆盖代码中app.json的对应字段
allowAuthorizeBoolean基础库2.2.2起支持,是否允许启动阶段触发授权逻辑
permissionObject隐私接口的授权说明,等价于app.json中的permission
tabBarObject覆盖小程序app.json中的tabBar配置
supportVersionString指定最低基础库版本,低于该版本无法运行

3.2 extEnable、extAppid、ext:最核心的铁三角

extEnable是总开关。官方文档明确要求,要让ext配置生效,extEnable必须为true。如果它是false,哪怕ext、extPages都写了,微信也会当不存在。

extAppid不是目标小程序的AppID,而是第三方平台自身的AppID。这个ID在微信开放平台后台的“第三方平台→基本信息”里能看到,通常以wx开头。有些旧文档示例写成t开头,那是占位符,千万别直接抄。我第一次配置时就把小程序AppID填了进去,结果wx.getExtConfigSync()一直返回空,排查了半天才发现是AppID填错了对象。

ext是业务数据的核心载体,它的结构完全由你自己定义。比如一个连锁店小程序,希望不同门店使用不同API域名、不同门店编号、不同主题色,这些都可以塞进ext:

{ "extEnable": true, "extAppid": "wx1234567890abcdef", "ext": { "apiBase": "https://api.store1001.example.com", "storeId": "1001", "storeName": "示例门店", "themeColor": "#FF6600" } }

小程序端读取时,只需要一行代码:

const extConfig = wx.getExtConfigSync ? wx.getExtConfigSync() : {}; console.log('当前门店配置:', extConfig.storeName);

需要注意的是,wx.getExtConfigSync()要求基础库不低于1.1.0,现在绝大多数用户微信版本都满足,但做兼容判断总归稳妥。

3.3 extPages与tabBar:给不同商家做差异化页面

extPages用于覆盖小程序非首页页面的窗口配置,比如标题、下拉刷新、导航栏背景色等。它的key是页面路径,value是窗口配置对象:

{ "extEnable": true, "extAppid": "wx1234567890abcdef", "extPages": { "pages/order/detail": { "navigationBarTitleText": "订单详情", "enablePullDownRefresh": true }, "pages/user/center": { "navigationBarTitleText": "会员中心" } } }

这里有个容易写错的地方:页面路径不要带前导斜杠,和app.json里的pages写法保持一致。你写成"/pages/order/detail"虽然开发工具不一定报错,但实际覆盖不一定生效。

tabBar在ext.json里可以理解为“对app.json中tabBar的整段覆盖”。如果你的每个商家小程序底部导航都不同,就可以在ext.json里单独配置。它的字段和app.json的tabBar完全一致:color、selectedColor、backgroundColor、borderStyle、list、custom等。第三方平台下发tabBar时,list里的pagePath必须真实存在于代码中,否则会导致页面打不开。

3.4 replaceMode、allowAuthorize、permission、supportVersion的进阶玩法

replaceMode控制的是“覆盖”还是“合并”。官方语义是:当replaceMode为true时,extPages里配置的字段会覆盖小程序代码中app.json里同名字段的配置;如果为false,则以代码内的配置优先。这个字段初看晦涩,但理解了“业主与施工方”的关系就行:replaceMode为true,等于告诉施工方“某些细节听业主的,不要听设计师的”。我建议在需要严格控制页面差异化的场景下开启,其余情况保持false更安全。

allowAuthorize和permission都涉及授权。permission用来声明小程序使用位置、相册等接口的原因说明,它等价于app.json的permission字段。allowAuthorize设置为true后,允许在小程序启动阶段就触发授权弹窗逻辑,适合一些必须在首页立刻用上地理位置或用户信息的业务。

supportVersion用于声明该项目最低支持的基础库版本,比如"2.20.1"。如果某个商家的用户微信基础库版本过低,小程序可能无法正常运行,设置好这个字段至少能在启动阶段给出更友好的提示。

4. 实战场景:一套代码给N个商家下发不同配置

4.1 业务需求拆解

假设有个小程序开发团队,做了一个“连锁门店会员小程序”,现在要卖给100家门店。每家门店有独立的logo、门店编号、API域名、客服电话,但页面结构、业务流程完全一致。如果复制100套代码再分别改,维护成本会爆炸。

用第三方平台+ext.json的方案就清晰了:

  1. 开发一套通用模板代码;
  2. 在src/ext.json里配置好每个商家的差异化数据;
  3. 通过服务端接口,把模板ID和对应商家的ext.json一起上传;
  4. 商家的小程序运行时自行读取配置,按配置展示不同的门店信息。

4.2 一份可直接抄作业的ext.json

这里给一个完整的配置示例,业务代码可以直接参考:

{ "extEnable": true, "extAppid": "wx1234567890abcdef", "ext": { "apiBase": "https://api.store001.example.com", "storeId": "store001", "storeName": "示例门店", "phone": "400-800-1000", "homeSlogan": "欢迎光临示例门店", "payEnabled": true }, "extPages": { "pages/order/list": { "navigationBarTitleText": "我的订单" }, "pages/coupon/index": { "navigationBarTitleText": "优惠券" } }, "tabBar": { "color": "#999999", "selectedColor": "#FF6600", "backgroundColor": "#ffffff", "borderStyle": "black", "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/coupon/index", "text": "优惠" }, { "pagePath": "pages/mine/mine", "text": "我的" } ] }, "permission": { "scope.userLocation": { "desc": "用于查找附近门店" } }, "replaceMode": true, "supportVersion": "2.20.1" }

4.3 小程序端读取配置的代码与注意点

在uni-app里,建议在App.vue的onLaunch阶段读取一次,存到全局变量,避免每个页面都重复读:

// App.vue export default { onLaunch() { let extConfig = {}; try { extConfig = wx.getExtConfigSync ? wx.getExtConfigSync() : {}; } catch (e) { console.error('读取ext.json失败', e); } // 挂到globalData,后面所有页面都能用 this.globalData.extConfig = extConfig; this.globalData.apiBase = extConfig.apiBase || 'https://default.example.com'; }, globalData: { extConfig: {}, apiBase: '' } }

页面里使用时:

// 某个页面 const extConfig = getApp().globalData.extConfig; this.storeName = extConfig.storeName || '默认门店'; wx.setNavigationBarTitle({ title: extConfig.storeName });

注意,extConfig在某些场景下可能是空对象。比如开发者在普通模式下打开项目调试,没有走第三方平台上传链路,这时getExtConfigSync()拿到的就是{}。所以代码里一定要有兜底默认值,不能假设字段一定存在。

4.4 服务端上传代码时如何携带ext_json

服务端把ext.json传给微信,不是传文件路径,而是把ext.json的内容作为JSON字符串放到请求体里。接口地址是:

POST https://api.weixin.qq.com/wxa/upload?access_token=ACCESS_TOKEN

关键参数:

参数说明
template_id第三方平台模板库中的模板ID
ext_jsonext.json文件的JSON字符串
user_version用户自定义版本号
user_desc版本描述

Node.js服务端示例:

const axios = require('axios'); async function uploadMiniProgramCode(accessToken, templateId, extJson, userVersion, userDesc) { const url = `https://api.weixin.qq.com/wxa/upload?access_token=${accessToken}`; const res = await axios.post(url, { template_id: templateId, ext_json: JSON.stringify(extJson), user_version: userVersion, user_desc: userDesc }); return res.data; } const extJson = { extEnable: true, extAppid: 'wx1234567890abcdef', ext: { apiBase: 'https://api.store001.example.com', storeId: 'store001' } }; uploadMiniProgramCode( 'ACCESS_TOKEN', 'TPL_123', extJson, '1.0.0', '示例门店第一个版本' ).then(res => console.log(res));

我在实际对接时踩过一个坑:ext_json参数忘加JSON.stringify,直接把对象传过去,微信返回的报错是“invalid ext_json”,看起来像字段错误,其实是类型不对。用curl调试时也容易犯这个错:

curl -d '{"template_id":"TPL_ID","ext_json":"{\"extEnable\":true,\"extAppid\":\"wx1234567890abcdef\",\"ext\":{}}","user_version":"1.0.0","user_desc":"test"}' "https://api.weixin.qq.com/wxa/upload?access_token=ACCESS_TOKEN"

服务端用的ext_json内容,尽量和开发环境测试的真实配置保持同一结构,避免“开发时能用,线上读不到字段”这种问题。

5. 完整流程:从开放平台注册到审核发布

5.1 开放平台侧的前置准备

做第三方平台开发,先得有一个通过微信认证的开放平台账号。登录后创建第三方平台,拿到四个核心信息:

  • 第三方平台AppID
  • 第三方平台AppSecret
  • 消息校验Token
  • 消息加密Key

其中AppID就是ext.json里的extAppid,AppSecret和Token用于后续获取接口调用凭证。创建完成后,还需要配置授权事件接收URL、消息接收URL等服务器地址,这些URL要能处理微信服务器发来的GET和POST请求,完成token校验和消息解密。

5.2 用开发者工具测试授权模式

开发阶段,通常会用“测试号”或者一个真实的普通小程序来做授权测试。给前端同学的建议是:在微信开发者工具里选择“第三方平台”模式,填入第三方平台AppID,再绑定一个已授权的小程序,这样ext.json才能真正按线上链路生效。

如果没有走第三方平台模式,直接用普通AppID打开项目,ext.json虽然躺在代码包里,但wx.getExtConfigSync()拿到的多半是空对象。这一点非常容易让新手误判成“代码写错了”。

5.3 服务端上传、提审、发布的全链路

一个小程序要从模板变成线上正式版,完整链路大概是:

  1. 商家在H5页面发起授权,微信返回授权跳转链接;
  2. 商家确认授权后,服务端拿到auth_code;
  3. 服务端用auth_code换取该商家的authorizer_access_token和authorizer_refresh_token;
  4. 服务端调用接口设置小程序服务器域名(modify_domain),把ext.json里的apiBase加进白名单;
  5. 服务端调用wxa/upload上传代码,携带模板ID和该商家的ext_json;
  6. 代码上传后状态为“开发版本”,可先提交为“体验版本”供内部测试;
  7. 测试通过后,调用接口提交审核;
  8. 微信审核通过后,再调用接口“发布”,商家小程序正式上线。

这条链路里最容易出问题的就是第4步和第5步。域名没配好,小程序请求会直接失败;ext_json字符串格式不对,上传就会报错。建议先把第5步单独测试通,再往前推第4步。

6. 常见问题与排查经验

6.1 配置不生效?先按这个顺序排查

每次收到“ext.json不生效”的反馈,我基本按下面这个顺序排查,效率很高:

  1. 先确认dist/dev/mp-weixin下有没有ext.json。没有,就是源码位置放错了;
  2. 有文件,打开看extEnable是不是true。false的话一切白搭;
  3. extEnable没问题,看extAppid是否是第三方平台AppID,而不是小程序AppID;
  4. AppID正确,看开发者工具是不是用“第三方平台”模式打开的;
  5. 都不是,直接在小程序启动代码里打日志,打印wx.getExtConfigSync(),看返回什么;
  6. 如果返回空对象,再确认是不是用的测试号或普通模式打开,必要时让服务端重新走一次上传流程。

这一套流程走下来,至少能解决九成问题。

6.2 常见问题速查表

现象原因解决办法
编译产物里没有ext.json文件没放在src根目录移动到src/ext.json,重新编译
getExtConfigSync()返回{}普通模式调试、extEnable为false、extAppid错误改用第三方平台模式;修正extEnable、extAppid
extPages配置的标题不生效页面路径带了斜杠、replaceMode逻辑不对去掉前导斜杠;确认replaceMode是否符合预期
上传接口报invalid ext_jsonext_json传了对象而不是字符串用JSON.stringify包装
线上小程序读不到配置没用第三方平台接口上传,走了普通上传必须走wxa/upload接口
首页没法通过ext.json直接改成别页ext.json不控制启动页在首页代码里用wx.reLaunch动态跳转

6.3 一个容易忽略的细节:启动页与“加载页”的修改思路

有朋友问我能不能用ext.json直接修改小程序“刚进入时显示的加载页面”。ext.json本身做不了这件事,小程序的启动页永远是代码包里app.json的pages数组第一项。但是你可以做变通:在首页的onLoad里读取ext配置,然后根据配置reLaunch或者redirectTo到真正想展示的页面。

// pages/index/index.vue onLoad() { const extConfig = getApp().globalData.extConfig; if (extConfig && extConfig.homePath) { wx.reLaunch({ url: extConfig.homePath }); } }

这样虽然修改的是代码逻辑,但表现效果上,不同商家打开小程序看到的“首屏”可以完全不同。同理,如果你用webview加载H5页面,想把apiBase、storeId这些ext配置传给H5,可以在webview的url后面拼参数,H5通过location.search解析,再用postMessage和uni-app侧做通信,整套方案非常顺。

关于ext.json的配置,其实核心就一句话:代码模板负责“长什么样”,ext.json负责“给谁用、用在哪”。理解了这个,很多字段看一眼官方文档就能上手。我在实际项目中,因为位置放错、AppID填错、字符串格式化不对这3个问题反复折腾过,所以特别写出来,希望大家第一次配置就一次过。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询