Tạo một mod
Mod là một plugin của Claude Code có một file entry, gọi là hooks module: một file JavaScript hoặc TypeScript chứa các hàm mà Claude Code gọi khi có event xảy ra. Có hai cách để tạo mod:
- Nhờ Claude viết: mô tả thứ bạn muốn trong một session Claude Code
- Tự viết: làm theo hướng dẫn để hiểu code của một mod hoạt động thế nào. Bạn không cần Node.js, bundler hay bước build nào, vì Claude Code nạp trực tiếp file
.jsvà.ts.
Nếu bạn chưa chắc mod có phải công cụ phù hợp không, hãy đọc phần so sánh ở trang tổng quan trước.
Nhờ Claude viết mod
Phần tiêu đề “Nhờ Claude viết mod”Mô tả mod bạn muốn trong một session Claude Code tương tác, và Claude sẽ viết nó. Claude làm việc dựa trên một skill tích hợp sẵn tên là plugin-authoring, skill này cho Claude biết nên ghi mod vào đâu, phiên bản Claude Code của bạn có những event và method nào, và mod được nạp ra sao. Claude có thể tự nạp skill khi bạn yêu cầu viết mod, hoặc bạn tự nạp bằng cách chạy /plugin-authoring tại prompt của Claude Code.
Mod sẽ chạy ngay khi bạn duyệt nó, trừ ở những session không nạp được mod do Claude viết.
Bước 1: Mô tả mod
Phần tiêu đề “Bước 1: Mô tả mod”Yêu cầu mod bằng lời của bạn, ví dụ make a mod that shows the current git branch above the prompt. Claude ghi mod vào một thư mục riêng bên trong thư mục mods của session, tức là ~/.claude/dev-mods/ cộng với ID của session. Đường dẫn đầy đủ của một mod sẽ có dạng ~/.claude/dev-mods/3f2a9c1e-5b7d-4e8a-9c21-6d0f4b8a7e13/git-branch/.
Bước 2: Duyệt mod
Phần tiêu đề “Bước 2: Duyệt mod”Khi Claude lưu file đầu tiên, Claude Code hỏi có bật hot reloading cho session này không. Hot reloading sẽ chạy các mod Claude viết trong session này và tự cập nhật theo mỗi thay đổi sau đó.
Chọn một trong hai câu trả lời:
- Enable for this session: các mod trong thư mục mods của session được nạp khi turn kết thúc, và được nạp lại khi kết thúc mỗi turn có thay đổi chúng. Lựa chọn này giữ nguyên trong suốt session, kể cả khi bạn resume session.
- Not now: tạm thời không nạp gì. Các file vẫn nằm ở nơi Claude đã ghi, và các mod sẽ được nạp vào lần tiếp theo session đó khởi động. Để một mod không bao giờ được nạp, hãy xóa thư mục của nó.
Bước 3: Kiểm tra mod đã được nạp
Phần tiêu đề “Bước 3: Kiểm tra mod đã được nạp”Chạy /plugin tại prompt của Claude Code và nhấn Tab cho đến khi tab Installed được chọn. Tab này liệt kê mod, và bạn có thể tắt nó ở đó.
Bước 4: Dùng thử mod
Phần tiêu đề “Bước 4: Dùng thử mod”Dùng thứ bạn đã yêu cầu. Với prompt ví dụ ở trên, tên branch hiện tại sẽ xuất hiện phía trên ô prompt. Nếu mod chưa làm đúng ý, hãy bảo Claude cần sửa gì. Mod được nạp lại vào cuối mỗi turn có thay đổi file của nó, nên bạn có thể thử thay đổi ngay khi Claude làm xong.
Dùng mod trong các session khác
Phần tiêu đề “Dùng mod trong các session khác”Mod do Claude viết chỉ được nạp trong session đã tạo ra nó, và Claude Code sẽ xóa thư mục mods của session đó khi nó cũ hơn cleanupPeriodDays. Để giữ mod lại, hãy copy thư mục của nó ra khỏi thư mục mods tới một chỗ của riêng bạn, ví dụ ~/mods/git-branch. Sau đó chọn cách nạp:
- Trong một session bạn khởi động: trong shell, chạy
claude --plugin-dir ~/mods/git-branch - Cho người khác dùng: thêm nó vào một marketplace để họ có thể cài
Những session không nạp được mod do Claude viết
Phần tiêu đề “Những session không nạp được mod do Claude viết”Mod do Claude viết chỉ được nạp sau khi bạn duyệt, trong một workspace được tin cậy (trusted) và cho phép mods chạy. Trong các session sau, nó không được nạp:
- Không có ai để duyệt: session không thể hiện prompt cho bạn, như khi chạy
claude -phoặc ở modedontAsk - Workspace chưa được tin cậy: bạn chưa chấp nhận trust prompt cho thư mục đó
- Mods bị tắt: bạn khởi động với
--safe-modehoặc--bare, bạn đặtdisableAllHooks, hoặc managed settings của tổ chức chặn nó
Tự viết một mod
Phần tiêu đề “Tự viết một mod”Trong phần hướng dẫn này, bạn sẽ xây dựng một mod tên first-mod: đếm số tool call Claude thực hiện, hiển thị con số đó cạnh spinner trong lúc Claude làm việc, và thêm command /tally để in con số ra. Sau đó bạn đọc các file khai báo type mà Claude Code ghi cạnh mod của bạn và chạy claude plugin validate. Hai thứ này cho bạn biết phiên bản của bạn có những event và method nào, và Claude Code đọc được gì từ code của bạn.
Video dưới đây cho thấy mod hoàn chỉnh. Spinner đếm số tool call, /tally in ra con số, và một chỉnh sửa trong code có hiệu lực ngay trong lúc session đang chạy:
Bạn sẽ viết ba file:
first-mod/├── .claude-plugin/│ └── plugin.json└── hooks/ ├── hooks.json └── register.jsplugin.json: manifest của pluginhooks.json: trỏ tới file code của bạnregister.js: code của bạn, gọi là hooks module
Bước 1: Tạo thư mục plugin
Phần tiêu đề “Bước 1: Tạo thư mục plugin”Tạo hai thư mục chứa các file:
mkdir -p first-mod/.claude-plugin first-mod/hooksNew-Item -ItemType Directory -Force first-mod\.claude-plugin, first-mod\hooksBước 2: Viết manifest
Phần tiêu đề “Bước 2: Viết manifest”Mod là một plugin, và mod cần có manifest. Manifest của mod này không có field nào đặc biệt. Lưu nội dung sau thành first-mod/.claude-plugin/plugin.json:
{ "name": "first-mod", "version": "0.1.0", "description": "Counts Claude's tool calls, shows the count beside the spinner, and adds a /tally command", "author": { "name": "Your Name" }}Bước 3: Cho Claude Code biết code của bạn nằm ở đâu
Phần tiêu đề “Bước 3: Cho Claude Code biết code của bạn nằm ở đâu”Khi nạp một plugin, Claude Code đọc file hooks/hooks.json của plugin. Key modules trong file đó chỉ ra đường dẫn tới code của bạn, và chính việc có key này khiến plugin trở thành một mod. Ghi một đường dẫn, tương đối so với hooks.json. Ở đây nó trỏ tới register.js, file bạn sẽ viết ở bước tiếp theo.
Lưu nội dung sau thành first-mod/hooks/hooks.json:
{ "description": "The first-mod hooks module", "modules": ["./register.js"]}Bước 4: Viết code
Phần tiêu đề “Bước 4: Viết code”File này là code của mod, gọi là hooks module. Khi mod được nạp, Claude Code gọi hàm register mà file export ra và truyền vào một hàm tên là on. Mỗi lần gọi on sẽ đăng ký một event handler, gọi là hook, cho event được nêu tên.
Lưu nội dung sau thành first-mod/hooks/register.js:
// Biến đếm, dùng chung cho các hook bên dướilet calls = 0
// Claude Code gọi hàm này một lần khi mod được nạpexport function register(on) { // Chạy khi session bắt đầu, trước prompt đầu tiên của bạn on('session.start', async ($, e, next) => { // Thêm command /tally await $.command.register({ name: 'tally', description: 'Show how many tool calls Claude has made', }) // Để session bắt đầu như bình thường return next(e) })
// Chạy mỗi khi Claude sắp dùng một tool on('tool.call', async ($, e, next) => { calls += 1 // Yêu cầu Claude Code vẽ lại giao diện để hiện con số mới $.ui.invalidate('ui.render') // Để tool chạy như bình thường return next(e) })
// Chạy khi bạn gõ /tally, và chỉ khi đó, nhờ matcher on('command.run', { command: 'tally' }, async () => { // Đoạn text sẽ in ra transcript return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' } })
// Chạy mỗi khi Claude Code vẽ spinner on('ui.render', { component: 'Spinner' }, async ($, e, next) => { // Giữ nguyên spinner của Claude Code, thêm con số vào sau chữ của nó return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } }) })}File này lưu con số trong biến calls và đăng ký bốn hook:
session.startchạy khi session bắt đầu, trước prompt đầu tiên của bạn, và chạy lại mỗi khi mod được reload. Nó thêm command/tallyvào Claude Code.tool.callchạy mỗi khi Claude sắp dùng một tool. Nó cộngcallsthêm một và yêu cầu Claude Code vẽ lại giao diện.command.runchạy khi bạn gõ/tally. Nó trả về đoạn text cần in.ui.renderchạy mỗi khi Claude Code vẽ spinner. Nó thêm con số vào sau chữ của spinner.
Phần Mod ví dụ hoạt động thế nào giải thích ba tham số mà mỗi hook nhận và giá trị mỗi hook trả về.
Bước 5: Nạp mod
Phần tiêu đề “Bước 5: Nạp mod”Khởi động Claude Code với cờ --plugin-dir, cờ này nạp một thư mục plugin cho một session mà không cần cài đặt:
claude --plugin-dir ./first-modBước 6: Dùng thử mod
Phần tiêu đề “Bước 6: Dùng thử mod”Nhờ Claude làm việc gì đó cần vài tool call, ví dụ list the files here and read the README. Trong lúc Claude làm việc, chữ của spinner sẽ có thêm một con số tăng dần phía sau, kiểu Thinking · tool calls: 2…. Khi Claude làm xong, gõ /tally rồi nhấn Enter. Transcript sẽ hiện first-mod: Claude has made 2 tool calls since this mod loaded, với con số của riêng bạn. Claude Code tự thêm tên plugin vào trước đoạn text của command.
Để kiểm tra command mà không cần session tương tác, hãy chạy nó ở chế độ non-interactive:
claude -p "/tally" --plugin-dir ./first-modfirst-mod: Claude has made 0 tool calls since this mod loadedNếu /tally không có trong danh sách command, nghĩa là module chưa được nạp. Xem Tìm hiểu vì sao mod không làm gì.
Bước 7: Sửa code trong lúc session đang chạy
Phần tiêu đề “Bước 7: Sửa code trong lúc session đang chạy”Để session mở. Trong register.js, đổi ' · tool calls: ' thành ' · tools used: ' trong hook ui.render rồi lưu lại. Dòng được tô sáng là dòng thay đổi:
// Chạy mỗi khi Claude Code vẽ spinner on('ui.render', { component: 'Spinner' }, async ($, e, next) => { // Giữ nguyên spinner của Claude Code, thêm con số vào sau chữ của nó return next({ ...e, props: { ...e.props, suffix: ' · tools used: ' + calls + '…' } }) })Một dòng trong transcript báo first-mod đã reload và liệt kê các hook của nó, và spinner lần sau sẽ dùng text mới, kiểu Thinking · tools used: 1….
Mod ví dụ hoạt động thế nào
Phần tiêu đề “Mod ví dụ hoạt động thế nào”Mỗi hàm bạn truyền cho on là một hook, tức là một event handler. Claude Code truyền cho mọi hook cùng ba tham số:
- Mods API, đặt tên là
$: mọi method mà mod có thể gọi để tác động ra bên ngoài, được nhóm theo namespace như$.uivà$.command - Event, đặt tên là
e: input của event dưới dạng dữ liệu thuần, ví dụ tên và tham số của một tool call - Handler kế tiếp, đặt tên là
next: một hàm chuyển event tiếp cho các mod khác rồi tới hành vi mặc định của Claude Code, và trả về kết quả
Các hook trong first-mod xử lý event theo những cách sau:
- Quan sát: hook
session.startđăng ký command, còn hooktool.callđếm call và yêu cầu vẽ lại. Cả hai đều trả vềnext(e), nên session bắt đầu và tool chạy như bình thường. - Trả lời: hook
command.runtrả về kết quả của riêng nó và không bao giờ gọinext. Tham số thứ hai củaon,{ command: 'tally' }, là một bộ lọc, gọi là matcher, nên hook chỉ chạy cho/tally. - Viết lại: hook
ui.rendergọinextvới một bản copy củaecósuffixchứa con số, nên Claude Code vẽ spinner như thường lệ, kèm text của bạn sau chữ
Claude Code theo dõi thư mục được nạp bằng --plugin-dir và hot-reload hooks module khi có file trong đó thay đổi. Mỗi lần reload, register chạy lại, nên calls trở về 0 và /tally bắt đầu đếm lại từ đầu. Để giữ một giá trị qua các lần reload, xem Lưu state.
Tiếp tục phát triển mod
Phần tiêu đề “Tiếp tục phát triển mod”Khi một mod đã được nạp, bạn có thể nhờ Claude sửa nó, kiểm tra code của bạn với type definitions của phiên bản đang dùng, liệt kê các event và lời gọi mà Claude Code tìm thấy trong mod, và viết test cho nó.
Sửa mod bằng Claude
Phần tiêu đề “Sửa mod bằng Claude”Để sửa một mod bạn đã có, khởi động session với --plugin-dir trỏ tới thư mục của mod, để những gì Claude viết được nạp ngay trong cùng session:
claude --plugin-dir ./first-modSau đó yêu cầu thay đổi, ví dụ add a /tally-reset command to this mod that sets the tally back to zero. Claude sẽ sửa hooks module, chạy claude plugin validate và sửa những lỗi nó báo. Thư mục bạn nạp bằng --plugin-dir là một protected path, nên ở mode default và acceptEdits bạn sẽ được hỏi để duyệt từng chỉnh sửa Claude làm trên mod. Bảng protected paths cho biết kết quả với các permission mode khác.
Các file Claude lưu trong turn sẽ được reload khi turn kết thúc, nên bạn có thể thử /tally-reset ngay khi Claude làm xong.
Lấy type definitions cho phiên bản của bạn
Phần tiêu đề “Lấy type definitions cho phiên bản của bạn”Mỗi lần Claude Code nạp hoặc reload một mod từ thư mục bạn truyền vào --plugin-dir, hoặc một mod Claude đã viết cho bạn, nó ghi các file khai báo TypeScript, đuôi .d.ts, vào .claude-plugin/types/ bên trong thư mục của mod. Các file này mô tả chính xác các event, method của mods API và element có trong phiên bản Claude Code bạn đang chạy, nhờ đó editor có thể gợi ý (autocomplete) và kiểm tra type cho hook của bạn. Để xem các khai báo online, đọc mods/types/claude-code.d.ts trong repository Claude Code, dòng đầu tiên của file ghi phiên bản đã sinh ra nó. Thư mục chứa các file sau:
| Đường dẫn | Khai báo gì |
|---|---|
claude-code/index.d.ts | Mọi event cùng input và result của nó, mọi namespace và method của mods API, và các element mà mỗi surface vẽ được |
claude-code-tools/index.d.ts | Input và result của các tool tích hợp sẵn, để khi kiểm tra e.tool === 'Bash' thì type của e được thu hẹp lại |
claude-code-mcp/index.d.ts | Input của các MCP tool đang kết nối vào lần cuối bạn lưu một file trong mod |
index.d.ts trong thư mục mang tên một plugin | Những gì plugin đó thêm vào mods API. Có một thư mục cho mỗi plugin mà plugin.json của bạn liệt kê trong dependencies. |
tsconfig.json | Các compiler option phù hợp với hooks module |
Nếu mod của bạn chưa có tsconfig.json riêng, Claude Code sẽ thêm một file ở thư mục gốc của mod, kế thừa (extend) file được sinh ra, nhờ đó editor và lệnh tsc -p ./first-mod kiểm tra type cho mod mà không cần thiết lập gì thêm.
Event và method có thể thay đổi giữa các bản phát hành, nên khi có mâu thuẫn, hãy tin các file này hơn bất kỳ trang tài liệu nào, kể cả trang này.
claude-code/index.d.ts là tài liệu tham chiếu đầy đủ nhất cho bản build của bạn, có chú thích và ví dụ cho mọi method của mods API. Để tra cứu, tìm theo tên trong file, ví dụ 'tool.call'.
Kiểm tra Claude Code đọc được gì từ mod
Phần tiêu đề “Kiểm tra Claude Code đọc được gì từ mod”Để xem mod của bạn theo cách Claude Code nhìn thấy nó, mà không cần chạy code hay khởi động session, hãy dùng claude plugin validate. Lệnh này kiểm tra manifest và chạy cùng phép phân tích tĩnh (static analysis) trên mã nguồn hooks module mà Claude Code chạy khi nạp mod. Trong shell, chạy nó trên thư mục của mod:
claude plugin validate ./first-modVới first-mod, output có các dòng sau.
❯ ./register.js hooks: session.start, tool.call, command.run{command=tally}, ui.render{component=Spinner} ❯ ./register.js calls: $.command.register, $.ui.invalidate
✔ Validation passedDòng hooks: liệt kê các event mà module của bạn hook vào, mỗi event kèm bộ lọc trong ngoặc nhọn. Dòng calls: liệt kê mọi method của mods API mà nó gọi. Module nào đọc hoặc đặt biến môi trường sẽ có thêm dòng env reads: và env writes:, còn module dùng $.state có thêm state reads: và state writes:.
Nếu một event bạn định xử lý không có trong dòng đầu tiên, Claude Code cũng sẽ không gọi hook đó. Nguyên nhân thường gặp là viết sai tên event, khi đó lệnh báo lỗi kiểu "tool.calls" is not an event.
Tuân thủ các quy tắc sau để static analysis tìm được mọi hook và mọi lời gọi:
- Viết đầy đủ mỗi lời gọi mods API:
$, namespace, rồi method, như$.store.get('notes'). Bạn có thể truyền$cho một hàm khai báo ở top level của cùng file, và với một hàm tênloadNotes, dòngcalls:sẽ ghi$.store.get (via loadNotes). Truyền$cho một method, một hàm định nghĩa bên trong hook, hoặc một hàm import từ file khác của bạn sẽ khiến validate thất bại. Các hàmreadvàupdatemà$.statedùng là những import duy nhất được nhận$. Không gán$hay một namespace của nó cho biến, không destructure, không truy cập bằng tên tính toán động.const ui = $.uisẽ lỗi với$.ui is used as a value. - Viết tên event trong mỗi lời gọi
ondưới dạng string literal, như'tool.call'. Một biến, hoặc một vòng lặp qua danh sách tên, sẽ lỗi vớithe event name passed to on() is not a string literal. - Bên trong
register, không khai báo thêm biến hay tham số nào tênon. Validate sẽ lỗi với"on" is declared again (shadowed). - Chỉ import từ các file bên trong thư mục plugin, bằng đường dẫn tương đối. Import “trần” (bare import) duy nhất được phép là
claude-code, dùng cho type và một vài helper. - Dùng khai báo
importở đầu file, nhưimport { name } from './file.js'. Mộtimport()động sẽ lỗi vớia dynamic import(); a hooks module imports its own files with an import declaration. - Viết mọi file dưới dạng ES module, dùng
importchứ không dùngrequire. Phần tham chiếu liệt kê các đuôi file Claude Code nạp được.
Kiểm thử mod
Phần tiêu đề “Kiểm thử mod”Bạn có thể viết test tự động cho mod và chạy chúng từ shell bằng claude plugin test, không cần session, đăng nhập hay mạng. Một test sẽ kích hoạt (fire) các event mà hook của bạn xử lý và kiểm tra hook đã làm gì.
Test dưới đây kích hoạt hai tool call, chạy /tally, và kiểm tra rằng câu trả lời đếm đủ cả hai. Lưu thành first-mod/tests/first-mod.test.ts:
import { expect, test } from 'claude-code/testing'
test('/tally reports the tool calls the mod has seen', async ($, on) => { // Trả lời mỗi tool call thay cho Claude Code, nên không tool nào thực sự chạy on('tool.call', () => ({ result: 'ok' }))
// Kích hoạt hai tool call, hook tool.call của mod sẽ đếm chúng await $.tool.call({ tool: 'Bash', command: 'ls' }) await $.tool.call({ tool: 'Read', file_path: 'README.md' })
// Chạy /tally và kiểm tra đoạn text mà hook của nó trả về const answer = await $.command.run({ command: 'tally', args: '' }) expect(answer.text).toBe('Claude has made 2 tool calls since this mod loaded')})Trong shell, chạy test từ thư mục first-mod:
claude plugin testOutput ghi tên từng test và kết quả pass hay fail, kèm thời gian chạy sẽ khác nhau mỗi lần:
tests/first-mod.test.ts:(pass) /tally reports the tool calls the mod has seen [22.87ms]
1 pass 0 failRan 1 test across 1 file. [0.19s]Trang Kiểm thử mod hướng dẫn stub một lời gọi model hoặc store, và kiểm thử timer cũng như phần vẽ giao diện.
Chia sẻ mod
Phần tiêu đề “Chia sẻ mod”Mod là một plugin, nên bạn quản lý phiên bản trong manifest, và người khác cài, cập nhật nó bằng các lệnh /plugin. Cách chia sẻ phụ thuộc vào đối tượng:
- Vài người: gửi họ thư mục plugin hoặc một file
.zipcủa nó. Xem Share a plugin without a marketplace - Team của bạn: đưa nó vào marketplace riêng của bạn, ví dụ một repository private có mỗi thư mục cho một plugin. Để thêm marketplace đó cho mọi người làm việc trong một repository, hãy đăng ký nó trong settings của repository
- Cả tổ chức: administrator có thể cài mod của tổ chức thông qua managed settings
- Bất kỳ ai: đặt repository marketplace của bạn ở chế độ public, hoặc gửi plugin vào directory của Anthropic
Trước khi chia sẻ, hãy kiểm tra name của plugin: claude plugin validate sẽ báo lỗi với tên trông giống tên của chính Anthropic, ví dụ tên bắt đầu bằng claude-. Event và method có thể thay đổi giữa các bản phát hành, nên README là nơi để ghi rõ bạn đã test với phiên bản Claude Code nào.
Hãy tiếp tục phát triển trên thư mục gốc với --plugin-dir, đừng sửa bản đã cài. Claude Code cache plugin đã cài theo version, nên chỉnh sửa của bạn sẽ không tới được bản đã cài cho đến khi bạn tăng version và cài lại.
Đọc tiếp
Phần tiêu đề “Đọc tiếp”- Vẽ giao diện bằng mod: mở pane, vẽ phía trên prompt, thêm nút bấm và ô nhập liệu
- Phản ứng với event: hook vào tool call, prompt và turn
- Dùng mods API: thêm command và tool, gọi model, chạy tác vụ theo timer
- Kiểm thử mod: stub những gì Claude Code sẽ trả lời, kiểm thử timer và phần vẽ
- Xử lý sự cố mod: các lý do khiến mod không làm gì, và debug log
- Đọc mã nguồn các mod tích hợp sẵn: các plugin hoàn chỉnh, mỗi cái có hooks module và test
Bài tiếp theo: Vẽ giao diện bằng mod - Vẽ pane, band phía trên prompt, nút bấm, ô nhập liệu và lưu state.