在微信公众平台配置自定义菜单时,接口返回错误码 40016 表示提交的按钮数量不符合平台规定。这个错误通常与菜单结构中的 button 数组或 sub_button 数组长度有关。很多菜单请求在客户端看起来正常,但发送到微信服务器后被拦截,正是因为平台对一级菜单和二级菜单分别做数量校验,而不是只看按钮总数。
要准确理解 40016,需要先明确微信自定义菜单的层级设计。自定义菜单由一级菜单数组 button 组成,每个一级菜单可以包含二级菜单数组 sub_button。微信服务器校验请求体时,会分别检查这两个数组的长度,任何一层不满足要求都会返回 errcode 为 40016,错误信息通常写作 invalid button size,中文提示为不合法的按钮个数。
一、40016错误码的准确含义
微信公众平台接口返回的 errcode 为 40016 时,表示菜单按钮数量不合法。菜单创建接口的返回体通常包含 errcode 和 errmsg 两个字段。当 errmsg 显示为 invalid button size 或中文提示不合法的按钮个数时,就应该把排查范围锁定在 button 数组和 sub_button 数组的长度上。
这个错误码与权限、参数格式、网络波动等无关,它只关注按钮数量。之所以会出现 40016,是因为微信自定义菜单的数据结构不是一个简单的扁平按钮列表,而是分成两个层级。一级菜单数组 button 最多允许 3 个元素,每个一级菜单下的二级菜单数组 sub_button 最多允许 5 个元素。平台校验时会对这两个层级分别计数,任何一层超出限制都会直接拒绝创建请求。
还有一个容易被忽略的细节:如果某个一级菜单包含 sub_button 字段,那么即使这个一级菜单本身配置了 name,它也不会作为可点击按钮触发事件,它只承担容器角色。此时按钮计数的核心不是简单的总数相加减,而是分层限制。换句话说,即便你认为实际显示的按钮总数没有超过某个数值,只要某一层级的数组长度越界,依然会返回 40016。
二、按钮个数限制的具体规则
微信官方对自定义菜单的层级数量限制非常明确。button 数组长度必须为 1 到 3。也就是说,创建一个完全不包含 button 字段的菜单,或者 button 数组长度大于 3,都会触发 40016。同样地,sub_button 数组长度必须为 1 到 5。如果某个一级菜单配置了二级菜单,但 sub_button 是空数组或者包含超过 5 个对象,也会不符合要求。
以一个合规结构为例,最外层 button 包含 3 个一级菜单:第一个一级菜单没有子菜单,第二个一级菜单包含 3 个二级菜单,第三个一级菜单包含 5 个二级菜单。此时一级菜单个数为 3,二级菜单个数分别为 0、3、5,均未越界。后端接收的按钮节点总数为 11 个,但微信不会只按照总节点数来检查,而是会同时检查每一个 button 元素内部的 sub_button 数组长度。
如果第三个一级菜单下放置了 6 个二级菜单,即便 button 数组长度仍然为 3,接口依然会返回 40016。这就是很多配置看似一级菜单数量没有超过 3 个却报错的原因。因此排查时不能只盯着顶层菜单数量,还需要逐个检查二级菜单数组。
三、常见触发40016的场景与示例
第一种常见场景是一级菜单数组写成了 4 个。比如系统早期默认生成三个一级菜单,后来运营人员又新增了一个跳转入口,结果 button 数组长度变成 4。微信平台不允许超过 3 个一级菜单,提交后就会返回 40016。这类问题通常需要回到菜单配置逻辑中,去掉多余的一级菜单,或者把入口合并到已有菜单下。
第二种常见场景是二级菜单超过 5 个。开发者把一组功能全部挂在同一个一级菜单下,sub_button 添加了 6 个甚至更多。微信规定每个一级菜单最多只能包含 5 个二级菜单,超过后直接报错。下面这个 JSON 示例中,一级菜单数量为 2,但第二个一级菜单下配置了 6 个二级菜单,因此会触发 40016。
{
"button": [
{
"name": "一级菜单A",
"sub_button": []
},
{
"name": "一级菜单B",
"sub_button": [
{"type": "click", "name": "子菜单1", "key": "K1"},
{"type": "click", "name": "子菜单2", "key": "K2"},
{"type": "click", "name": "子菜单3", "key": "K3"},
{"type": "click", "name": "子菜单4", "key": "K4"},
{"type": "click", "name": "子菜单5", "key": "K5"},
{"type": "click", "name": "子菜单6", "key": "K6"}
]
}
]
}
第三种常见场景是 sub_button 为空数组。有些配置生成逻辑会先创建父级菜单,子菜单为空数组,后续再异步填充。但微信接口在创建菜单时会把空数组判定为不合法按钮个数。因此只要某个一级菜单包含 sub_button 字段,就必须保证该数组至少包含 1 个二级菜单对象。
此外,还有一种情况是菜单 JSON 在序列化或传输过程中产生了重复对象或空对象,导致字段丢失。这类问题虽然不一定直接由按钮个数引起,但同样可能让微信返回 40016。排查时最好打印实际提交的 JSON 内容,而不是依赖前端页面展示出来的按钮样式。
四、如何快速定位并修复40016
遇到 40016 后,第一步是打印请求体。可以把准备提交的菜单 JSON 序列化后写入日志,查看 button 数组长度和每个对象的 sub_button 长度。不要只依赖调试界面,因为界面展示的是渲染后的按钮,与实际提交的 JSON 结构可能存在差异。如果团队后端的菜单模型比较复杂,还可以在拼接菜单的代码中加入长度检测。
第二步是用脚本校验。下面是一段 Python 脚本,读取菜单 JSON 并输出各层级按钮数量。只要打印结果中显示一级菜单数量大于 3,或者某个一级菜单下的二级菜单数量大于 5,就能立即定位错误位置。
import json
def check_menu(menu_json: str):
data = json.loads(menu_json)
buttons = data.get("button", [])
print(f"一级菜单数量: {len(buttons)}")
if len(buttons) > 3:
print("错误: 一级菜单超过3个")
for index, btn in enumerate(buttons):
sub_buttons = btn.get("sub_button", [])
print(f"第{index + 1}个一级菜单的子菜单数量: {len(sub_buttons)}")
if len(sub_buttons) > 5:
print("错误: 该一级菜单下的二级菜单超过5个")
if "sub_button" in btn and len(sub_buttons) == 0:
print("错误: 二级菜单数组不能为空")
第三步是修正菜单结构。如果一级菜单超过 3 个,需要删减或合并;如果某个一级菜单下的二级菜单超过 5 个,可以把部分子菜单拆分到其他一级菜单下。若确实需要更多子菜单,可以考虑使用小程序、网页链接或客服消息来扩展功能,而不是尝试突破微信给自定义菜单设定的按钮个数限制。
修复后重新调用菜单创建接口。如果仍然返回 40016,再检查是否存在不可见字符、按钮对象是否为空、name 字段是否为空白等连带问题。只有确保 button 数组长度在 1 到 3 之间,且每个 sub_button 数组长度在 1 到 5 之间,才能通过按钮个数校验,让菜单配置一次提交成功。
微信公众号自定义菜单40016按钮个数限制修改时间:2026-08-25 12:18:29