公司要把一套业务能力打包给合作方的小程序用,第一反应是发个 npm 包,结果发现小程序这边根本不是这个玩法,代码不能给对方看,得走插件。真开始做才知道坑不少,个人号开不了,类目不对提交不了,插件里连 wx.login 都调不了。
这篇把从申请到发布走一遍的完整流程记下来,包括插件项目长什么样、使用方怎么引、插件里哪些 API 用不了、以及绕过限制的功能页机制。如果你正在评估「这个需求能不能做成插件」,看完这篇应该能给出判断。
在本篇文章中,我们将从浅入深,和大家一起学习以下知识:
- 插件是什么,跟自定义组件和 npm 包的区别在哪
- 开放范围和服务类目,哪些账号根本申请不了
- 插件项目的目录结构,
plugin、miniprogram、doc各管什么 - 预览、上传、发布的流程,以及开发版插件怎么在真实小程序里调试
- 使用方引入插件的两种方式,主包和分包各有什么限制
- 插件和使用方之间怎么互相传东西,
export和抽象节点 - 插件里不能用的那批 API 和组件,完整清单
- 功能页机制,登录、支付、收货地址这三件事怎么绕
一、插件到底是什么
先把定位说清楚。插件是对一组 js 接口、自定义组件或页面的封装,用于嵌入到别人的小程序里。它不能独立运行,必须嵌在宿主小程序中才能被用户使用。开发和使用插件需要小程序基础库版本 1.9.6 以上。
它跟自定义组件最大的区别是代码可见性。第三方小程序在使用插件时看不到插件的代码,所以插件适合把自己的功能或服务打包给别人用,同时又不把实现暴露出去。这也是我们当初选它的唯一理由,业务逻辑不想开源给合作方。
代价是限制。插件里一些 API 无法调用,一些组件不能用。还有几个特殊接口虽然不能直接调,但可以通过功能页间接实现,这块第五节专门讲。另外框架会对小程序和它用的每个插件做数据安全保护,双方都不能偷看对方的数据,除非数据被主动传过去。
拿几个真实场景理解会更快。查快递、查天气这种通用服务,做成插件比每家自己写一遍划算。打车场景可以直接嵌滴滴提供的组件。外卖是最典型的,每个餐厅想要的小程序风格都不一样,但外卖这块功能是通用的,那就给餐厅各定制一个小程序,外卖部分直接用美团的插件。
这里有个当时让我很意外的发现:插件里居然不能直接调微信支付。做交易类插件之前一定要先确认这一点,绕法在第五节。
能做的事很多,但插件限制了开放范围和服务类目,具体清单看官方的开放类目说明。
开放范围只给企业、媒体、政府及其他组织主体,个人号不行。这条建议放在所有技术评估前面,账号主体不对,后面写得再好也发不出去。
服务类目上,开发者只能从当前小程序账号已选类目里挑一个作为插件的服务类目。下面是当时已开放的类目,官方说会逐步开放更多。
| 一级类目 | 二级类目 | 特殊说明 |
|---|---|---|
| 快递业与邮政 | 所有二级类目 | |
| 医疗 | 就医服务、互联网医院 | 仅医疗类小程序可使用 |
| 政务民生 | 所有二级类目 | |
| 金融业 | 征信业务 | |
| 出行与交通 | 所有二级类目 | |
| 生活服务 | 票务、生活缴费 | |
| IT科技 | 所有二级类目 | |
| 餐饮 | 点评与推荐、菜谱、餐厅排队、点餐平台、外卖平台 | |
| 旅游 | 所有二级类目 | |
| 文娱 | 视频、FM/电台、音乐、有声读物、动漫 | |
| 工具 | 记账、投票、日历、天气、备忘录、办公、字典、计算类、报价/比价、发票查询、企业管理 | |
| 电商平台 | 电商平台 | |
| 商业服务 | 招聘/求职 | |
| 汽车 | 所有二级类目 |
1.1 开发一个插件要走哪几步
整个流程是固定的四步,卡点也都在这四步里。
Step 1,去 小程序管理后台 的 小程序插件 里找开通入口。
Step 2,开通插件功能。条件前面说过,必须是企业、媒体、政府及其他组织主体,个人小程序不行。还有一条容易被忽略,一个小程序只能开通一个插件,想做两个不同的插件就得准备两个小程序账号。
Step 3,填写开发信息并开发。这里有个不可逆的操作,插件的基本信息和头像一旦填写就不能修改,动手前先跟产品和设计确认好文案与图,别像我一样填完才发现名字不对。
Step 4,提交审核、发布。只有在开发类目内才能提交。另外有个跟文档不符的地方,官方文档说「插件发布后才可以被其他小程序搜索并添加」,但当时实测没发布的也能被搜到和添加。这条对调试期是好事,对保密是坏事,插件名字别起得太敏感。
二、创建插件项目
用小程序的 AppID 就能创建插件项目。插件独立于小程序之外,但 AppID 是公用的,所以有一条铁律:不要在原有的小程序项目里做插件开发。两者的编译方式不一样,混在一起会互相干扰。
在开发者工具的创建项目页面,选一个空文件夹作为项目路径,勾上创建小程序插件快速启动模板。没有插件 AppID 的话要先去申请一个,专门用来开发插件。

模板建出来是三个目录加一份配置,各管各的事。
plugin 目录是插件本体,你的代码都写在这里。
miniprogram 目录放的是一个普通小程序项目,用来调试插件,同时它也是插件使用 Demo。上传插件代码时这个 Demo 会一起上传,并作为发布的审核依据。所以别把它当草稿箱,审核员是照着它看你的插件能干什么的,Demo 写得潦草是会被驳回的。
project.config.json 里要关注 compileType 字段,只有 compileType 等于 plugin 时才能正常使用插件项目。这个字段错了的表现是编译出来的东西完全不对,找半天找不到原因。
doc 目录放插件文档,而且必须放在插件项目根目录下的 doc 里。入口文件是 doc/README.md,里面引用的图片不能是网络图片,必须放在这个目录下面。
文档里的链接也有白名单,只能链到这三个域名:
- 微信开发者社区(developers.weixin.qq.com)
- 微信公众平台(mp.weixin.qq.com)
- GitHub(github.com)
写完 README.md 之后,在开发者工具左侧资源管理器里右键点它,选上传文档。要注意上传不等于发布,传完还得用账号密码登录管理后台,在 小程序插件 > 基本设置 里预览、发布插件文档。
还有个大小限制,插件文档总大小不能大于 2M,超了上传会返回错误码 80051。图多的话记得先压一遍,这个错误码提示不明显,第一次遇到很容易以为是网络问题。

2.1 插件目录结构
一个插件可以包含若干个自定义组件、页面,和一组 js 接口。这三样正好对应插件对外提供能力的三种方式,需要 UI 的给组件,需要整页跳转的给页面,纯逻辑的给 js 接口。目录长这样:
plugin |
这棵树里 plugin.json 是最关键的一份文件,它决定了插件对外露出哪些东西。没写进去的组件和页面,使用方是引不到的,哪怕文件就躺在目录里。
2.2 插件配置文件
提供给使用者小程序的自定义组件必须在 publicComponents 段里列出,这是插件的对外门面:
{ |
这份配置向使用者小程序开放了一个自定义组件 hello-component、一个页面 hello-page,以及 index.js 下导出的所有 js 接口。
把它理解成 npm 包的 exports 字段会比较容易接受。没列出来的属于内部实现,你可以随便改;列出来的就是公开 API,改了会破坏所有使用方,所以哪些组件对外开放这件事一开始就要想清楚。
2.3 预览、上传和发布
插件可以像小程序一样预览和上传,但它没有体验版。这一点跟小程序开发习惯差别很大,想给产品经理发个二维码扫一扫是做不到的。
插件会同时存在多个线上版本,具体用哪个版本由使用插件的小程序自己决定。这套设计对插件方友好,发新版不会强制所有使用方跟着升。反过来说,老版本你也得继续维护,不能说改就改。
手机预览和提审插件时,微信会用一个特殊的小程序来套用你项目里 miniprogram 文件夹下的那个 Demo,从而把插件跑起来。这也是前面说 Demo 要认真写的原因。
在真实小程序里调试开发版插件
平时把 miniprogram 下的代码当成使用方来调试就够了。但有时候你需要把插件放在真实运行的业务小程序里测,比如要验证和宿主的数据交互。这时候可以让开发版小程序直接引用开发版插件:
Step 1,在开发者工具的插件项目里上传插件,上传成功的通知信息里会带上这次上传得到的插件开发版 ID,是一串英文数字组成的随机字符串。
Step 2,点开发者工具右下角的通知按钮打开通知栏,就能看到新生成的 ID。
Step 3,在要使用开发版插件的小程序项目里,把插件 version 写成 dev- 加上那个 ID,比如 "version": "dev-abcdef0123456789abcdef0123456789"。
有个硬约束要记牢,开发版小程序引用了开发版插件之后,这个小程序就不能上传发布了。必须把插件版本改回正式版本号,小程序才能正常上传。发版前忘了改这一行,你会在发布环节卡住。
关于开发版 ID 还有几条容易踩的规则:
- 每次上传插件生成的 ID 不一定相同,同一个插件、同一个开发者,多次上传也可能变
- 每个开发者在每个插件里只会同时存在一个有效的开发版,只有最新上传的那个 ID 有效,用旧的会提示失效
- 不同开发者上传的开发版互不影响,可以同时有效,多人协作时各调各的不会打架
- 开发版插件没有时间限制,长期有效
第一条和第二条合起来看,意味着你每改一次插件重新上传,宿主小程序里那行 version 就得跟着换。团队里把这个 ID 写死在公共配置里是很痛苦的,建议放到本地不提交的配置项里。
三、使用插件
换个身份,现在你是要用别人插件的那一方。这一节讲怎么把插件接进来。
3.1 先在后台添加插件
在写任何代码之前,得先去小程序管理后台的「设置 - 第三方服务 - 插件管理」里添加插件,通过 appid 查找并添加。如果这个插件无需申请,添加完就能直接用;否则要提交申请,等插件开发者通过之后才能在小程序里使用。

需要申请的插件,等待时间完全取决于对方,排期时最好把这段留出来。我们当时因为对面审批慢,白白空了两天。
3.2 在 app.json 里声明
后台加完之后,使用者要在 app.json 中声明需要使用的插件。放在主包里是这样:
{ |
plugins 定义段里可以包含多个插件声明,每个声明以一个使用者自定义的引用名作为标识,并指明插件的 appid 和要用的版本号。这里的引用名,比如上面的 myPlugin,完全由使用者自己定,不需要和插件开发者保持一致,也不用跟对方协调。后面所有引用这个插件的地方,用的都是这个名字。
这个设计挺舒服的,同一个插件在不同项目里可以叫不同的名字,也避免了多个插件引用名撞车。
如果插件只在一个分包里用到,可以把它放进分包,这样主包体积不受影响:
{ |
分包引入有两条限制要提前知道:只能在这个分包内使用该插件,以及同一个插件不能被多个分包同时引用。第二条最容易翻车,业务长起来之后发现另一个分包也要用,那就只能把插件挪回主包,主包体积又上去了。所以分包引入之前先判断一下这个能力的使用范围会不会扩散。
3.3 三种用法,组件、页面、js 接口
用插件的时候,插件的代码对使用者是不可见的。你唯一的依据是插件开发者提供的开发文档,组件名、页面名、js 接口规范都得从文档里查。这也是前面强调 doc/README.md 要认真写的原因,对使用方来说那就是全部。
用插件提供的自定义组件,方式和用普通自定义组件差不多,只是在 json 里声明时要用 plugin:// 协议,指明插件的引用名和组件名:
{ |
出于对插件的保护,这类组件在使用上有两条限制。默认情况下,页面里的 this.selectComponent 拿不到插件组件的实例对象;wx.createSelectorQuery 这些接口的 >>> 选择器也选不进插件内部。
这两条基本堵死了「从外面改插件内部行为」的路。想跟插件交互只能走它暴露的属性和事件,或者第 3.4 节说的 export。刚接手的时候我试过用选择器绕,白折腾。
用插件提供的页面,跳转时 url 用 plugin:// 前缀,形如 plugin://PLUGIN_NAME/PLUGIN_PAGE:
<navigator url="plugin://myPlugin/hello-page"> |
插件内部跳自己的页面用的是另一种形式,plugin-private://PLUGIN_APPID/PATH/TO/PAGE。要跳到别的插件时也用这种写法。这两个协议别搞混,一个给使用方用,一个给插件内部用。
用插件提供的 js 接口,靠 requirePlugin。假设插件提供了一个 hello 方法和一个 world 变量:
var myPluginInterface = requirePlugin('myPlugin'); |
基础库 2.14.0 起也可以直接用插件的 AppID 获取接口,写成 requirePlugin('wxidxxxxxxxxxxxxxxxx')。这种写法在插件之间互相调用时更有用,因为你未必知道对方在 app.json 里被起了什么引用名。
3.4 使用方怎么把东西交给插件
前面说过插件和小程序之间数据是隔离的,但有时候插件确实需要宿主的东西,比如用户 token、当前城市。这时候在声明使用插件的地方加一个 export 字段指定一个文件:
"myPlugin": { |

这个文件里导出的内容,插件侧可以用对应的全局函数读到。写这篇时用的是 requireMiniProgram(),具体名称请以你查到的官方文档为准。使用插件的小程序像下面这样导出:

这条通道是单向的,宿主主动给什么插件才能拿到什么。所以插件文档里要写清楚需要宿主导出哪些字段,不然对接的时候来回问会很累。
3.5 把一块区域交给使用方渲染
反过来,插件里也可以把一部分区域留给使用的小程序自己渲染。做法是用抽象节点,插件里定义一个占位的节点名,使用方指定用哪个组件去实现它。
给插件名为 plugin-index 的页面中的抽象节点 mp-view 指定小程序的自定义组件 components/comp-from-miniprogram 作为实现:
{ |
<!-- miniprogram/page/index.wxml --> |
这个能力在做「主体流程通用、局部样式各家不同」的插件时特别值钱。比如外卖插件里的商品卡片,每家餐厅的品牌风格不一样,就可以把卡片这一块交出去,插件只管下单流程。
四、插件有哪些限制
这一节是我觉得最该在立项前看的部分。很多需求做到一半才发现根本做不了,就是因为没提前查这些限制。
4.1 API 限制
有三条前提。插件的请求域名列表与小程序相互独立,也就是说插件要访问的接口域名得单独配,宿主配了不算。一部分 API 不允许插件调用,这些函数压根不存在于 wx 对象下,所以你连 typeof wx.login === 'function' 都判不出来,直接 undefined。还有些接口虽然插件里不能直接用,但可以通过功能页达到目的,见第五节。
下面是当时插件中不能使用的 API 清单:
| API | 说明 |
|---|---|
| wx.login | 登录 |
| wx.getUserInfo | 获取用户信息 |
| wx.chooseAddress | 获取用户收货地址 |
| wx.requestPayment | 【发起微信支付】 |
| wx.addCard | 添加卡券 |
| wx.openCard | 打开卡券 |
| wx.saveFile | 保存文件 |
| wx.getSavedFileList | 获取已保存的文件列表 |
| wx.getSavedFileInfo | 获取已保存的文件信息 |
| wx.removeSavedFile | 删除已保存的文件信息 |
| wx.openDocument | 打开文件 |
| wx.getStorageInfo | 获取本地缓存的相关信息 |
| wx.getStorageInfoSync | 获取本地缓存的相关信息 |
| wx.clearStorage | 清理本地数据缓存 |
| wx.clearStorageSync | 清理本地数据缓存 |
| wx.setNavigationBarTitle | 设置当前页面标题 |
| wx.showNavigationBarLoading | 显示导航条加载动画 |
| wx.hideNavigationBarLoading | 隐藏导航条加载动画 |
| wx.navigateTo | 新窗口打开页面 |
| wx.redirectTo | 原窗口打开页面 |
| wx.switchTab | 切换到 tabbar 页面 |
| wx.navigateBack | 退回上一个页面 |
| wx.stopPullDownRefresh | 停止下拉刷新动画 |
这张表看下来,规律其实很清楚。凡是涉及用户身份、支付、卡券、文件系统、导航栏和路由的,插件一律碰不到。微信的用意是把这些能力留在宿主小程序手里,插件只是嵌进去的一块内容,不该拥有跟宿主同等的权限。
对开发的实际影响是,插件里做不了独立登录态,也改不了导航栏标题,甚至连页面跳转都得靠宿主或者插件自己的页面协议。设计插件的交互流程时要顺着这个约束来,别照搬小程序的做法。
反过来说,没在这张表里的能力就是可用的。比如 canvas 相关的接口都不在禁用清单上,插件里画分享海报是能做的,具体写法可以看小程序绘制海报总结。不确定的接口,最稳的办法是在插件项目里打一行 typeof wx.xxx 试试,比翻文档快。
4.2 组件限制
有几个组件在插件页面里不能用:
- 开放能力
open-type为contact(打开客服会话)、getPhoneNumber(获取用户手机号)、getUserInfo(获取用户信息)的 button open-dataweb-view
另外有几个组件对基础库版本有要求,navigator 需要 2.1.0 以上,live-player 和 live-pusher 需要 2.3.0 以上。原文这几条被排版挤成了一行,实际是两组不同性质的限制,一组是完全不能用,一组是有版本门槛,这里拆开了。
web-view 不能用这条杀伤力最大。很多团队习惯把复杂页面做成 H5 内嵌,这条路在插件里是断的,所有交互都得用原生方式重写。小程序和 H5 之间怎么跳转、怎么传参,我在小程序跳转 H5 页面总结里写过,那套方案在插件里用不了,规划时要注意。
live-player 和 live-pusher 有版本门槛这一点,做音视频类插件的要留心,用户手机上的基础库版本不是你能控制的,低版本得有降级提示。这两个组件的用法我在小程序直播总结里详细写过。
4.3 插件之间怎么互相调用
插件不能直接引用其他插件。但如果宿主小程序同时引用了多个插件,这些插件之间是可以互相调用的。
一个插件调另一个插件的方法,写法和调自己的差不多。访问对方的自定义组件和页面用 plugin-private://APPID 这种形式,暂时不能用 plugin://。
js 接口用 requirePlugin,但这里有个时机问题必须注意:不能在文件一开头就调 requirePlugin,因为被依赖的那个插件可能还没初始化完。正确做法是推到更晚的时机,比如接口被实际调用时,或者组件 attached 的时候。
这个坑的表现是本地调试一切正常,换台手机或者调整一下插件声明顺序就报错说找不到接口。初始化顺序这种东西不写在代码里,靠运气对齐迟早要出事,直接改成懒加载最省心。
五、功能页,绕过限制的正门
5.1 功能页解决什么问题
功能页从小程序基础库版本 2.1.0 开始支持。
前面说了 wx.login、wx.requestPayment 这些接口插件里调不了,但需求不会因为限制就消失。微信给的解法是功能页,把这些敏感操作交给插件所有者的小程序去执行,插件只负责跳过去和接收结果。
当时功能页覆盖三件事:
- 获取用户信息,包括
openid和昵称等,相当于wx.login加wx.getUserInfo - 支付,相当于
wx.requestPayment - 获取收货地址,相当于
wx.chooseAddress
这三件正好是电商类插件最刚需的三件,第一节提到的「插件不能微信支付」,答案就在这里。不是不能支付,是不能直接调支付接口,得绕功能页。
用功能页要先激活功能页特性,配置对应的功能页函数,再用 functional-page-navigator 组件跳过去。
激活的做法是在插件所有者小程序的 app.json 里加一个 functionalPages 定义段:
{ |
这里要提醒一句,原文后面提审注意事项那段写的是 "functionalPages": true,跟这里的对象写法对不上。这是文档版本迭代留下的痕迹,两种写法在不同时期的文档里都出现过,实际以你当前查到的官方文档为准,别两个地方各抄一半。
5.2 跳转到功能页
功能页不能用 wx.navigateTo 跳,得用一个叫 functional-page-navigator 的组件。以获取用户信息为例,在插件里放这么一段:
<functional-page-navigator name="loginAndGetUserInfo" args="" version="develop" bind:success="loginSuccess"> |
用户点这个 navigator 的时候,会自动跳到插件所有者小程序的对应功能页。功能页提示用户登录或者做别的操作,结果以组件事件的方式返回,也就是代码里绑的 bind:success。
从基础库版本 2.4.0 开始,支持插件所有者小程序跳转到自己的功能页。低于 2.4.0 时,点这种 navigator 完全没反应,连报错都没有。调试时如果发现点了没动静,先看看基础库版本,别急着查代码。
还有一个提审前必检的点。version="develop" 仅用于调试,提审前需要做两件事:确保已发布设置了功能页特性的插件所有者小程序,以及把所有 functional-page-navigator 的属性改成 version="release"。漏改的话线上功能页跳不过去,而这个问题在开发环境是复现不出来的,我建议直接写进发布检查清单。
5.3 三个功能页分别能干什么
用户信息功能页用于帮插件获取用户信息,包括 openid 和昵称等,相当于 wx.login 加 wx.getUserInfo。
这里有个很实用的机制。自基础库版本 2.3.1 起,用户在功能页中授权之后,插件就可以直接调用 wx.login 和 wx.getUserInfo,不用每次都跳功能页。自基础库版本 2.6.3 起,还可以用 wx.getSetting 查询用户是否授权过。
这两条合起来,插件里的登录流程就有了标准写法:先 getSetting 查一下授权状态,授权过就直接调接口,没授权才引导用户点功能页。省掉的那次跳转对转化率影响不小。
支付功能页用于帮插件完成支付,相当于 wx.requestPayment。
这个要额外申请权限,申请位置在管理后台的「小程序插件 -> 基本设置 -> 支付能力」里。还有一条很硬的限制,无论申请有没有通过,主体为个人的小程序在使用插件时都用不了插件里的支付功能。
也就是说,你的插件哪怕拿到了支付能力,接入方是个人小程序照样付不了款。做面向长尾开发者的收费插件,这一条会直接影响商业模式,得提前想清楚。
收货地址功能页用于展示用户的收货地址列表,让用户从中选一个,自基础库版本 2.4.0 开始支持。
三个功能页的共同点是都要用户主动点击才能触发,没法在代码里静默调用。这是微信刻意的设计,涉及身份和钱的操作必须有明确的用户手势。设计交互时别想着「进页面自动拉起」,做不到。
六、这几年变了什么
这篇写于 2021 年 4 月,插件这块有几处需要说明。
小程序管理后台的菜单位置调整过。文中的「设置 - 第三方服务 - 插件管理」、「小程序插件 - 基本设置」这些路径,随着后台改版会挪,请以你打开时实际看到的界面为准,别照着截图找。
开放类目和申请条件也在变。文中那张类目表是当时的状态,官方本来就说会逐步开放更多类目,现在的清单请查官方文档。个人主体不能开通这条,据我了解一直没放开,但同样以后台实际能不能勾选为准。
用户信息相关的能力变化最大。微信这几年调整过获取用户头像昵称的方式,wx.getUserInfo 的行为跟 2021 年不一样了。文中用户信息功能页那段描述的是当时的机制,具体现在怎么拿,请查官方文档最新说明。这块我没有在最新版本上重新验证过,不敢写死。
基础库版本这块反而不用担心了。文中提到的 1.9.6、2.1.0、2.4.0、2.14.0 这些下限,现在的用户设备基本都远超了,当年要写的那些版本判断可以省掉不少。
最后说句实在的,插件这套机制这几年在微信生态里的存在感不算强,能查到的资料还是以官方文档为主。如果你正在评估要不要做插件,建议先照着第四节的限制清单过一遍需求,再决定要不要投入。
总结
插件这件事,技术难度不高,卡人的全是规则。
立项阶段先过三关:账号主体是不是企业、类目能不能对上、需求里有没有插件禁用的 API。这三关任何一关不过,后面写得再好也上不了线。个人号直接出局,一个小程序只能开一个插件,插件名和头像填了不能改,这几条都是不可逆的。
开发阶段记住 plugin.json 是对外门面,没列进 publicComponents 的组件使用方引不到。调试用开发版 ID,每次上传都可能变,而且引用了开发版插件的小程序不能发布,发版前一定要改回正式版本号。
使用方那边,主包和分包二选一,分包引入后同一个插件不能被多个分包引用,扩散风险要提前评估。插件和宿主之间数据是隔离的,宿主给东西走 export,插件让出渲染区域走抽象节点。
真正会让需求推倒重来的是第四节那批限制。web-view 不能用、支付不能直接调、导航栏改不了。支付和登录这两件事的正门是功能页,代价是必须由用户点击触发,并且个人主体的接入方用不了支付。