verify-article-structure-spec 完整规则(对应微信公众号草稿箱API HTML校验规范)
来源:微信开放社区《公众平台编辑器插件开发规范》,仓库 verify_article_structure.md 就是这份规范,是草稿接口上传前的预校验规则。 核心前提:所有样式只能写在元素 style="" 内联;禁止外部CSS、<style>、class/id 样式选择器
✅ 允许使用的 HTML 标签
推荐基础容器:<section>(公众号原生容器,优先用它代替div) 行内文本:<span>、<span leaf="">(专门行内叶子节点,只能放文本/行内元素,不能包块级标签) 语义标签:<p>、<strong>、<em>、<u>、<s>、<br>、<a>、<ul>、<ol>、<li> 媒体:<img>、<video>、<svg> 表格:<table>、<tr>、<td>、<th>
<div> 也可以用,但微信原生编辑器默认输出是 <section>;<pre> 不推荐(white-space:pre 会横向溢出)
❌ 直接禁止的标签(上传草稿会被直接删除)
<script>、<iframe>、<noscript>、<form>、<input>、<button>、<canvas>、<object>、<embed>、<style>、<link>、<meta>、<head>、<body>、<html>
✅ 允许的 CSS 属性(写在 style 内联)
文字:color、font-size、font-weight、font-style、text-align、letter-spacing、line-height、text-decoration 背景:background-color、background-image、background-repeat、background-size、background-position 边框:border / border-width / border-color / border-style、border-radius 盒模型:padding、margin、width、height、overflow、overflow-x、overflow-y 布局:display(inline/inline-block/block/flex ✅;grid ❌)、vertical-align 阴影:box-shadow 其他:opacity、transform、transform-origin、user-select
注意:display:flex 可用,但 display:grid 是黑名单
❌ 禁止 / 不推荐 CSS 属性 & 值
-
position:absolute / relative / fixed / sticky 不推荐,容易破坏阅读顺序、dark mode 识别异常
-
-
-
-
@keyframes、@media、CSS动画:禁止(只能内联静态transform,不能css动画)
-
text-align:start / end:禁止,跨终端对齐不一致,只能用 left/center/right
-
caret-color:transparent:禁止,编辑器光标看不见
-
!important:强烈不建议,会干扰深色模式自动转换
-
font-family:不建议写,公众号自带统一字体栈,自定义字体会iOS/安卓渲染不一致
📐 结构校验规则(这个仓库重点检测项)
1. DOM嵌套层数限制
连续同标签、无新增样式、只包含单个子节点的嵌套链路,不能超过10层,超过会被编辑器自动精简删除。
图片/svg/media标签不受该层数限制。
2. span[leaf] 强制规则
<span leaf=""> 是行内叶子节点,内部不能包裹块级元素(section/div/p),只能放文本、strong、img这类行内内容。
3. width 宽度检测(非常高频报错)
-
❌ 硬写固定px宽度,会检测「大屏居中失效、移动端水平溢出」
-
-
豁免:给节点加
data-ignore-width,跳过该节点及其子树的宽度溢出检测(横向滚动卡片常用)
4. height 高度检测
-
❌ 文字容器
height:0:移动端直接空白看不见
-
豁免:svg内部、纯图片容器的 height:0 不违规
5. line-height 行高检测
-
多行文字:line-height 不能小于font-size → 文字重叠,直接报违规
-
✅ 例外:容器没有文字,只放图片,line-height:0 用来消除图片间隙是允许的;单行文字也不受限制
🌑 Dark Mode 深色模式规则(仓库会检测对比度)
-
文字和背景对比度不足会告警;可加
data-ignore-dm="low-contrast" 豁免对比度检查
-
data-no-dark:当前节点不参与深色模式颜色转换(仅本节点)
-
data-ignore-dm="text-bg-gradient":豁免文字背景渐变警告>
背景图上的文字,深色模式不会自动反转文字颜色,需要自己处理对比度
🧩 SVG 特殊规则
-
SVG 本身允许;SVG内部
animate 动画,如果监听touchstart,必须同时写click:begin="touchstart; click",否则判定不兼容
-
SVG不参与Dark Mode自动变色,建议
fill="currentColor" 适配深浅色
🔗 链接与图片
-
<a href="https://xxx"> 支持,但是仅支持公众号允许的域名,草稿API上传后微信还会二次校验链接
-
img src 必须是 https mmbiz.qpic 资源;外部图会被微信后台过滤
校验通过 ≠ 100% 草稿一定成功
-
这个工具做本地预校验;调用草稿接口提交时,微信后台还会再做一轮HTML清洗过滤
-
部分样式本地puppeteer渲染没问题,但公众号手机端渲染差异
-
校验无报错后,必须登录公众号后台打开草稿预览+手机预览
快速CLI命令
git clone [https://github.com/wechatjs/verify-article-structure-spec.git](https://github.com/wechatjs/verify-article-structure-spec.git)
cd verify-article-structure-spec/cli
npm install
# 检测HTML,输出可读报告
npm run check ./article.html
# JSON结构化输出,适合接入自动化流水线
npm run check ./article.html --json
# 自动清理冗余嵌套标签
npm run dedupe ./article.html --out=clean.html
白名单/黑名单JSON(可直接接入SpringBoot校验接口)
适配 verify-article-structure-spec 规则,用于后端预校验公众号草稿HTML
{
"allowTags": [
"section", "span", "p", "strong", "em", "u", "s", "br", "a",
"ul", "ol", "li", "img", "video", "svg", "table", "tr", "td", "th", "div"
],
"denyTags": [
"script", "iframe", "noscript", "form", "input", "button",
"canvas", "object", "embed", "style", "link", "meta", "head", "body", "html"
],
"allowCssProps": [
"color", "font-size", "font-weight", "font-style", "text-align",
"letter-spacing", "line-height", "text-decoration",
"background-color", "background-image", "background-repeat", "background-size", "background-position",
"border", "border-width", "border-color", "border-style", "border-radius",
"padding", "margin", "width", "height", "overflow", "overflow-x", "overflow-y",
"display", "vertical-align", "box-shadow", "opacity", "transform", "transform-origin", "user-select"
],
"denyCssProps": [
"position", "float", "z-index", "var", "@keyframes", "@media",
"caret-color", "font-family"
],
"denyCssValues": {
"display": ["grid"],
"text-align": ["start", "end"]
},
"specialAttrAllow": [
"leaf", "data-ignore-width", "data-ignore-dm", "data-no-dark", "href", "src"
],
"maxNestingDepth": 10,
"rulesNote": {
"important": "不推荐使用!important",
"position": "absolute/relative/fixed/sticky虽能解析,但强烈不建议,深色模式异常风险高",
"lineHeight": "多行文字line-height不能小于font-size,防止文字重叠",
"imgSrc": "必须为mmbiz.qpic.cn的https地址,外部图片微信后台会过滤"
}
}
极简JS核心校验函数(复刻核心规则,无需Puppeteer,可嵌入SpringBoot通过NodeJS调用,或前端预校验)
/**
* 公众号草稿HTML简易校验(复刻 verify-article-structure-spec 核心规则)
* @param {string} html 待检测html片段(只传文章主体,不带<html><body>)
* @returns {Object} {pass:boolean, errors:Array, warnings:Array}
*/
function checkWechatArticleHtml(html) {
const allowTags = new Set(["section","span","p","strong","em","u","s","br","a","ul","ol","li","img","video","svg","table","tr","td","th","div"]);
const denyTags = new Set(["script","iframe","noscript","form","input","button","canvas","object","embed","style","link","meta","head","body","html"]);
const allowCssProps = new Set(["color","font-size","font-weight","font-style","text-align","letter-spacing","line-height","text-decoration","background-color","background-image","background-repeat","background-size","background-position","border","border-width","border-color","border-style","border-radius","padding","margin","width","height","overflow","overflow-x","overflow-y","display","vertical-align","box-shadow","opacity","transform","transform-origin","user-select"]);
const denyCssProps = new Set(["position","float","z-index","var","caret-color","font-family"]);
const errors = [];
const warnings = [];
// 1. 检测禁止标签
for(const tag of denyTags){
const reg = new RegExp(`<${tag}[ >]`, 'i');
if(reg.test(html)){
errors.push(`禁止标签 <${tag}>,公众号草稿接口会直接删除`);
}
}
// 2. 提取内联style属性,校验css属性
const styleReg = /style="([^"]+)"/g;
let styleMatch;
while((styleMatch = styleReg.exec(html)) !== null){
const styleStr = styleMatch[1];
const decls = styleStr.split(';').filter(s=>s.trim());
for(const decl of decls){
const [prop, val] = decl.split(':').map(x=>x.trim());
if(!prop) continue;
const propRaw = prop.replace(/!important/i,'').trim();
if(denyCssProps.has(propRaw)){
errors.push(`禁止CSS属性:${propRaw}`);
}
if(propRaw === 'display' && val.toLowerCase() === 'grid'){
errors.push(`display:grid 不支持`);
}
if(propRaw === 'text-align' && ['start','end'].includes(val.toLowerCase())){
errors.push(`text-align:${val} 不支持,仅允许left/center/right`);
}
if(decl.includes('!important')){
warnings.push(`不建议使用 !important,深色模式适配异常风险`);
}
}
}
// 3. 简易嵌套深度检查(简化版,完整深度检测建议用dom解析库如cheerio)
const tagStack = [];
const tagReg = /<(\/)?([a-zA-Z0-9]+)[ >]/g;
let tagMatch;
while((tagMatch = tagReg.exec(html)) !== null){
const isClose = !!tagMatch[1];
const tagName = tagMatch[2].toLowerCase();
if(isClose){
const last = tagStack[tagStack.length-1];
if(last === tagName) tagStack.pop();
}else{
tagStack.push(tagName);
if(tagStack.length > 10){
errors.push(`DOM嵌套层级超过10层,最大允许10层,当前${tagStack.length}`);
}
}
}
// 4. span leaf 简单校验:<span leaf> 内部不能包含块级标签
const spanLeafReg = /<span leaf="?"[^>]*>([\s\S]*?)<\/span>/gi;
let spanMatch;
const blockTags = new Set(['section','div','p','ul','ol','table']);
while((spanMatch = spanLeafReg.exec(html))!==null){
const inner = spanMatch[1];
for(const bt of blockTags){
if(new RegExp(`<${bt}[ >]`,"i").test(inner)){
errors.push(`<span leaf> 内部禁止包含块级标签 <${bt}>`);
}
}
}
return {
pass: errors.length === 0,
errors,
warnings
}
}
// 使用示例
/*
const html = `<section><p style="color:#333;display:flex;">测试</p></section>`;
const ret = checkWechatArticleHtml(html);
console.log(ret);
*/
注意:JS这个是轻量简化版;如果要和仓库校验完全一致,后端建议引入 cheerio 做DOM解析,替代正则解析DOM(正则处理嵌套HTML有边界缺陷)。
公众号草稿API可用最小HTML模板,直接提交草稿content
<section>
<p style="font-size:16px;color:#333;line-height:1.7;text-align:left;">
正文第一段文字测试,<strong>加粗文字</strong>,<em>斜体文字</em>,<u>下划线</u>,<s>删除线</s>。
</p>
<p style="font-size:16px;color:#333;line-height:1.7;">
<a href="[https://www.example.com](https://www.example.com)" style="color:#0066cc;text-decoration:underline;">测试超链接</a>
</p>
<ul style="padding-left:20px;">
<li style="font-size:16px;line-height:1.7;color:#333;">列表项1</li>
<li style="font-size:16px;line-height:1.7;color:#333;">列表项2</li>
</ul>
<p>
<img src="[https://mmbiz.qpic.cn/xxx/xxx.jpg](https://mmbiz.qpic.cn/xxx/xxx.jpg)" style="width:100%;border-radius:8px;display:block;">
</p>
</section>
要点:
-
-
-
-
无禁止标签、无grid、position等高危样式。