diff --git a/packages/plugins/@tachybase/plugin-print-template/.npmignore b/packages/plugins/@tachybase/plugin-print-template/.npmignore
new file mode 100644
index 000000000..65f5e8779
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/.npmignore
@@ -0,0 +1,2 @@
+/node_modules
+/src
diff --git a/packages/plugins/@tachybase/plugin-print-template/README.md b/packages/plugins/@tachybase/plugin-print-template/README.md
new file mode 100644
index 000000000..ef79607e2
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/README.md
@@ -0,0 +1 @@
+# @tachybase/plugin-print-template
diff --git a/packages/plugins/@tachybase/plugin-print-template/client.d.ts b/packages/plugins/@tachybase/plugin-print-template/client.d.ts
new file mode 100644
index 000000000..6c459cbac
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/client.d.ts
@@ -0,0 +1,2 @@
+export * from './dist/client';
+export { default } from './dist/client';
diff --git a/packages/plugins/@tachybase/plugin-print-template/client.js b/packages/plugins/@tachybase/plugin-print-template/client.js
new file mode 100644
index 000000000..b6e3be70e
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/client.js
@@ -0,0 +1 @@
+module.exports = require('./dist/client/index.js');
diff --git a/packages/plugins/@tachybase/plugin-print-template/package.json b/packages/plugins/@tachybase/plugin-print-template/package.json
new file mode 100644
index 000000000..1c79f8687
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "@tachybase/plugin-print-template",
+  "version": "0.21.73",
+  "main": "dist/server/index.js",
+  "dependencies": {
+    "docxtemplater": "^3.48.0",
+    "pizzip": "^3.1.7",
+    "xml2js": "^0.6.2"
+  },
+  "peerDependencies": {
+    "@tachybase/client": "workspace:*",
+    "@tachybase/server": "workspace:*",
+    "@tachybase/test": "workspace:*"
+  }
+}
diff --git a/packages/plugins/@tachybase/plugin-print-template/server.d.ts b/packages/plugins/@tachybase/plugin-print-template/server.d.ts
new file mode 100644
index 000000000..c41081ddc
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/server.d.ts
@@ -0,0 +1,2 @@
+export * from './dist/server';
+export { default } from './dist/server';
diff --git a/packages/plugins/@tachybase/plugin-print-template/server.js b/packages/plugins/@tachybase/plugin-print-template/server.js
new file mode 100644
index 000000000..972842039
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/server.js
@@ -0,0 +1 @@
+module.exports = require('./dist/server/index.js');
diff --git a/packages/plugins/@tachybase/plugin-print-template/src/client/index.tsx b/packages/plugins/@tachybase/plugin-print-template/src/client/index.tsx
new file mode 100644
index 000000000..5ebd1cebf
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/src/client/index.tsx
@@ -0,0 +1,21 @@
+import { Plugin } from '@tachybase/client';
+
+export class PluginPrintTemplateClient extends Plugin {
+  async afterAdd() {
+    // await this.app.pm.add()
+  }
+
+  async beforeLoad() {}
+
+  // You can get and modify the app instance here
+  async load() {
+    console.log(this.app);
+    // this.app.addComponents({})
+    // this.app.addScopes({})
+    // this.app.addProvider()
+    // this.app.addProviders()
+    // this.app.router.add()
+  }
+}
+
+export default PluginPrintTemplateClient;
diff --git a/packages/plugins/@tachybase/plugin-print-template/src/index.ts b/packages/plugins/@tachybase/plugin-print-template/src/index.ts
new file mode 100644
index 000000000..7e74612df
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/src/index.ts
@@ -0,0 +1,2 @@
+export * from './server';
+export { default } from './server';
diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/actions/printTemplates.ts b/packages/plugins/@tachybase/plugin-print-template/src/server/actions/printTemplates.ts
new file mode 100644
index 000000000..35dffec90
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/src/server/actions/printTemplates.ts
@@ -0,0 +1,93 @@
+import fs from 'fs';
+import path from 'path';
+import { PassThrough, Readable } from 'stream';
+import { Context } from '@tachybase/actions';
+import { Model } from '@tachybase/database';
+
+import Docxtemplater from 'docxtemplater';
+import PizZip from 'pizzip';
+
+import { getTemplateParams } from './template-params'; // 导入 getTemplateParams
+
+export const generate = async (ctx: Context) => {
+  const id = ctx.action.params.id;
+  const repo = ctx.db.getRepository('templateManage');
+  const template: Model = await repo.findOne({
+    filter: {
+      id,
+    },
+    appends: ['template'],
+  });
+  console.log(template.get());
+
+  const rawTemplate = template.get();
+  const templatePath = path.join(process.env.PWD, rawTemplate.template[0].get().url);
+
+  const data = rawTemplate.testData;
+
+  const outputPath = path.join(__dirname, './output.docx');
+
+  try {
+    // 获取模板中的占位符
+    const tags = (await getTemplateParams(templatePath)) as any;
+    console.log('Template tags:', tags);
+
+    // 验证数据是否包含所有占位符
+    const missingTags = tags.filter((tag) => !data.prototype.hasOwnProperty.call(tag.replace(/[{}]/g, '')));
+    if (missingTags.length > 0) {
+      // throw new Error(`Missing data for tags: ${missingTags.join(',')}`);
+      console.log('Missing data for tags');
+    }
+
+    // 生成定制的 docx 文件
+    const buffer = generateDocxFromTemplate(templatePath, data);
+
+    // 创建一个可读流
+    const readableStream = new Readable({
+      read(size) {
+        // 将Buffer数据push到可读流中
+        this.push(buffer);
+        // 标记流的末尾
+        this.push(null);
+      },
+    });
+
+    ctx.withoutDataWrapping = true;
+    // 设置响应类型为二进制流
+    ctx.set('Content-Type', 'application/octet-stream');
+    // 将可读流设置为响应体
+    ctx.body = readableStream;
+
+    // // 确保生成的 docx 文件存在
+    // if (!fs.existsSync(outputPath)) {
+    //     throw new Error('Generated DOCX file not found');
+    // }
+    // ctx.body = 'Word and PDF files generated';
+  } catch (error) {
+    console.error('Error:', error);
+    ctx.status = 500;
+    ctx.body = 'Error generating files: ' + error.message;
+  }
+};
+
+function generateDocxFromTemplate(templatePath, data): Buffer {
+  try {
+    const content = fs.readFileSync(templatePath, 'binary');
+    const zip = new PizZip(content);
+    const doc = new Docxtemplater(zip);
+
+    doc.setData(data);
+
+    try {
+      doc.render();
+    } catch (error) {
+      console.error('Error rendering template:', error);
+      throw error;
+    }
+
+    return doc.getZip().generate({ type: 'nodebuffer' });
+  } catch (error) {
+    console.error('Error generating DOCX from template:', error);
+    throw error; // Ensure errors are propagated correctly
+  }
+}
diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/actions/template-params.ts b/packages/plugins/@tachybase/plugin-print-template/src/server/actions/template-params.ts
new file mode 100644
index 000000000..f0fddd1a3
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/src/server/actions/template-params.ts
@@ -0,0 +1,54 @@
+import fs from 'fs';
+
+import PizZip from 'pizzip';
+import { parseStringPromise } from 'xml2js';
+
+export async function getTemplateParams(templatePath) {
+  try {
+    const content = fs.readFileSync(templatePath, 'binary');
+    const zip = new PizZip(content);
+
+    const getXmlContent = (fileName) => {
+      const file = zip.file(fileName);
+      return file ? file.asText() : '';
+    };
+
+    const docXml = getXmlContent('word/document.xml');
+
+    const parseXml = async (xml) => {
+      try {
+        const result = await parseStringPromise(xml, { explicitArray: false, ignoreAttrs: true });
+        const body = result['w:document']['w:body'];
+        return Array.isArray(body['w:p']) ? body['w:p'] : [body['w:p']];
+      } catch (error) {
+        console.error('Error parsing XML:', error);
+        throw error;
+      }
+    };
+
+    const xmlContent = await parseXml(docXml);
+
+    const extractTags = (nodes) => {
+      const tags = new Set();
+      nodes.forEach((node) => {
+        if (node['w:r'] && Array.isArray(node['w:r'])) {
+          node['w:r'].forEach((r) => {
+            if (r['w:t'] && r['w:t']['#']) {
+              const matches = r['w:t']['#'].match(/\{[\w-]+\.}/g);
+              if (matches) {
+                matches.forEach((tag) => tags.add(tag));
+              }
+            }
+          });
+        }
+      });
+      return tags;
+    };
+
+    const tags = extractTags(xmlContent);
+    return Array.from(tags); // Ensure the return value is an array
+  } catch (error) {
+    console.error('Error in getTemplateParams:', error);
+    throw error;
+  }
+}
diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/collections/.gitkeep b/packages/plugins/@tachybase/plugin-print-template/src/server/collections/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/index.ts b/packages/plugins/@tachybase/plugin-print-template/src/server/index.ts
new file mode 100644
index 000000000..b68aea57f
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/src/server/index.ts
@@ -0,0 +1 @@
+export { default } from './plugin';
diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/plugin.ts b/packages/plugins/@tachybase/plugin-print-template/src/server/plugin.ts
new file mode 100644
index 000000000..aae04ceb2
--- /dev/null
+++ b/packages/plugins/@tachybase/plugin-print-template/src/server/plugin.ts
@@ -0,0 +1,29 @@
+import { Plugin } from '@tachybase/server';
+
+import { generate } from './actions/printTemplates';
+
+export class PluginPrintTemplateServer extends Plugin {
+  async afterAdd() {}
+
+  async beforeLoad() {}
+
+  async load() {
+    // localhost:3000/api/printTemplates:generate
+    this.app.resourcer.define({
+      name: 'printTemplates',
+      actions: {
+        generate,
+      },
+    });
+  }
+
+  async install() {}
+
+  async afterEnable() {}
+
+  async afterDisable() {}
+
+  async remove() {}
+}
+
+export default PluginPrintTemplateServer;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e638fdb80..1a56e905f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3483,6 +3483,27 @@ importers:
         specifier: ^6.11.2
         version: 6.21.0(react-dom@18.3.1)(react@18.3.1)
 
+  packages/plugins/@tachybase/plugin-print-template:
+    dependencies:
+      '@tachybase/client':
+        specifier: workspace:*
+        version: link:../../../core/client
+      '@tachybase/server':
+        specifier: workspace:*
+        version: link:../../../core/server
+      '@tachybase/test':
+        specifier: workspace:*
+        version: link:../../../core/test
+      docxtemplater:
+        specifier: ^3.48.0
+        version: 3.48.0
+      pizzip:
+        specifier: ^3.1.7
+        version: 3.1.7
+      xml2js:
+        specifier: ^0.6.2
+        version: 0.6.2
+
   packages/plugins/@tachybase/plugin-saml:
     dependencies:
       '@tachybase/actions':
@@ -13928,7 +13949,6 @@ packages:
   /@xmldom/xmldom@0.8.10:
     resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
     engines: {node: '>=10.0.0'}
-    dev: true
 
   /@xtuc/ieee754@1.2.0:
     resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@@ -17432,6 +17452,13 @@ packages:
     dependencies:
       esutils: 2.0.3
 
+  /docxtemplater@3.48.0:
+    resolution: {integrity: sha512-eM/sSUIVZND/GeY7FCC8kL2QV9oEQZ/RNxfY9cC07JutpVMFC9EEBOk4ajs3mcN72gqzOUqrsdwXPmKTD9TyHA==}
+    engines: {node: '>=0.10'}
+    dependencies:
+      '@xmldom/xmldom': 0.8.10
+    dev: false
+
   /dom-accessibility-api@0.5.16:
     resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
 
@@ -22634,7 +22661,7 @@ packages:
     dependencies:
       debug: 3.2.7
       iconv-lite: 0.4.24
-      sax: 1.3.0
+      sax: 1.4.1
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -23330,6 +23357,10 @@ packages:
   /pako@1.0.11:
     resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
 
+  /pako@2.1.0:
+    resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
+    dev: false
+
   /param-case@3.0.4:
     resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
     dependencies:
@@ -23710,6 +23741,12 @@ packages:
     optionalDependencies:
       nice-napi: 1.0.2
 
+  /pizzip@3.1.7:
+    resolution: {integrity: sha512-VemVeAQtdIA74AN1Fsd5OmbMbEeS4YOwwlcudgzvmUrOIOPrk1idYC5Tw5FUFq/I0c26ziNOw9z//iPmGfp1jA==}
+    dependencies:
+      pako: 2.1.0
+    dev: false
+
   /pkg-dir@4.2.0:
     resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
     engines: {node: '>=8'}
@@ -26886,13 +26923,9 @@ packages:
       immutable: 4.3.5
       source-map-js: 1.2.0
 
-  /sax@1.3.0:
-    resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
-
   /sax@1.4.1:
     resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
     requiresBuild: true
-    optional: true
 
   /saxes@5.0.1:
     resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
@@ -30356,7 +30389,7 @@ packages:
     resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==}
     engines: {node: '>=4.0.0'}
     dependencies:
-      sax: 1.3.0
+      sax: 1.4.1
       xmlbuilder: 11.0.1
     dev: true
 
@@ -30364,7 +30397,7 @@ packages:
     resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
     engines: {node: '>=4.0.0'}
     dependencies:
-      sax: 1.3.0
+      sax: 1.4.1
       xmlbuilder: 11.0.1
     dev: true
 
@@ -30372,14 +30405,12 @@ packages:
     resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
     engines: {node: '>=4.0.0'}
     dependencies:
-      sax: 1.3.0
+      sax: 1.4.1
       xmlbuilder: 11.0.1
-    dev: true
 
   /xmlbuilder@11.0.1:
     resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
     engines: {node: '>=4.0'}
-    dev: true
 
   /xmlbuilder@15.1.1:
     resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}