[{"data":1,"prerenderedAt":276},["ShallowReactive",2],{"blog-\u002Fblog\u002Ftauri-plugins-peer-dont-pin":3},{"id":4,"title":5,"body":6,"date":263,"description":264,"extension":265,"image":266,"meta":267,"navigation":268,"path":269,"seo":270,"stem":271,"tags":272,"__hash__":275},"blog\u002Fblog\u002Ftauri-plugins-peer-dont-pin.md","Tauri Plugins: Peer, Don't Pin",{"type":7,"value":8,"toc":257},"minimark",[9,13,29,34,44,61,64,68,78,88,91,95,102,106,109,112,165,168,201,204,235,241,244,247,253],[10,11,12],"p",{},"For application code, pinning NPM dependencies to exact versions is a sensible default: what you install is what ships.",[10,14,15,16,23,24,28],{},"Libraries are different. A package you publish to NPM runs inside someone else's dependency tree, and exact pins in a published package become version conflicts the consumer has to resolve. Publishing a ",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Ftauri.app",[21],"nofollow","Tauri"," plugin recently was a useful reminder that the rules for applications don't automatically transfer to the packages we publish — and that ",[25,26,27],"code",{},"@tauri-apps\u002Fapi"," in particular needs special handling.",[30,31,33],"h2",{"id":32},"the-dependency-trap","The dependency trap",[10,35,36,37,39,40,43],{},"A Tauri plugin is an NPM package that extends a host Tauri app. The plugin calls into Tauri's JavaScript API via ",[25,38,27],{},", and so does the host app. There's one runtime, one webview, one ",[25,41,42],{},"invoke"," function.",[10,45,46,47,49,50,53,54,56,57,60],{},"If the plugin declares ",[25,48,27],{}," as a production dependency with an exact pin and the host app uses a different version, NPM installs a second, nested copy inside the plugin's ",[25,51,52],{},"node_modules",". While ",[25,55,27],{}," is a thin wrapper over ",[25,58,59],{},"window.__TAURI_INTERNALS__"," and two copies usually behave identically at runtime, duplication becomes a problem when a future version introduces a breaking change.",[10,62,63],{},"The bug isn't in the plugin. It's in the dependency declaration.",[30,65,67],{"id":66},"why-a-peer-dependency","Why a peer dependency",[10,69,70,71,73,74,77],{},"The fix is to declare ",[25,72,27],{}," as a ",[25,75,76],{},"peerDependency",". That tells NPM \"the host app provides this — don't install your own.\"",[10,79,80,81,83,84,87],{},"Tauri's own first-party plugins keep ",[25,82,27],{}," in ",[25,85,86],{},"dependencies"," with a caret range, which lets NPM dedupe against the host app's copy. That works in the common case, but leans on the resolver to do the right thing.",[10,89,90],{},"Peer-declaring it is stricter: NPM warns or errors if the host hasn't installed a compatible version, surfacing conflicts at install time rather than letting them ship as silent duplicates. For third-party plugins, where we don't control how the host app is structured, that extra guarantee is worth having.",[30,92,94],{"id":93},"why-a-wide-caret-range","Why a wide caret range",[10,96,97,98,101],{},"A permissive caret range — ",[25,99,100],{},"^2.9.0"," — says \"we work with 2.9.0 and anything compatible with it.\" The minimum reflects the oldest version we've tested; the caret accepts every minor and patch release above it. If we later discover a specific incompatibility, we raise the floor. The ceiling stays open.",[30,103,105],{"id":104},"the-rule","The rule",[10,107,108],{},"So, for our Tauri plugins:",[10,110,111],{},"✅ Good — peer dependency with a caret range:",[113,114,119],"pre",{"className":115,"code":116,"language":117,"meta":118,"style":118},"language-json shiki shiki-themes github-dark","{\n   \"peerDependencies\": {\n      \"@tauri-apps\u002Fapi\": \"^2.9.0\"\n   }\n}\n","json","",[25,120,121,130,140,153,159],{"__ignoreMap":118},[122,123,126],"span",{"class":124,"line":125},"line",1,[122,127,129],{"class":128},"s95oV","{\n",[122,131,133,137],{"class":124,"line":132},2,[122,134,136],{"class":135},"sDLfK","   \"peerDependencies\"",[122,138,139],{"class":128},": {\n",[122,141,143,146,149],{"class":124,"line":142},3,[122,144,145],{"class":135},"      \"@tauri-apps\u002Fapi\"",[122,147,148],{"class":128},": ",[122,150,152],{"class":151},"sU2Wk","\"^2.9.0\"\n",[122,154,156],{"class":124,"line":155},4,[122,157,158],{"class":128},"   }\n",[122,160,162],{"class":124,"line":161},5,[122,163,164],{"class":128},"}\n",[10,166,167],{},"❌ Bad — production dependency (nested duplicate risk):",[113,169,171],{"className":115,"code":170,"language":117,"meta":118,"style":118},"{\n   \"dependencies\": {\n      \"@tauri-apps\u002Fapi\": \"2.9.1\"\n   }\n}\n",[25,172,173,177,184,193,197],{"__ignoreMap":118},[122,174,175],{"class":124,"line":125},[122,176,129],{"class":128},[122,178,179,182],{"class":124,"line":132},[122,180,181],{"class":135},"   \"dependencies\"",[122,183,139],{"class":128},[122,185,186,188,190],{"class":124,"line":142},[122,187,145],{"class":135},[122,189,148],{"class":128},[122,191,192],{"class":151},"\"2.9.1\"\n",[122,194,195],{"class":124,"line":155},[122,196,158],{"class":128},[122,198,199],{"class":124,"line":161},[122,200,164],{"class":128},[10,202,203],{},"❌ Bad — exact peer dependency (breaks consumers on any different version):",[113,205,207],{"className":115,"code":206,"language":117,"meta":118,"style":118},"{\n   \"peerDependencies\": {\n      \"@tauri-apps\u002Fapi\": \"2.9.1\"\n   }\n}\n",[25,208,209,213,219,227,231],{"__ignoreMap":118},[122,210,211],{"class":124,"line":125},[122,212,129],{"class":128},[122,214,215,217],{"class":124,"line":132},[122,216,136],{"class":135},[122,218,139],{"class":128},[122,220,221,223,225],{"class":124,"line":142},[122,222,145],{"class":135},[122,224,148],{"class":128},[122,226,192],{"class":151},[122,228,229],{"class":124,"line":155},[122,230,158],{"class":128},[122,232,233],{"class":124,"line":161},[122,234,164],{"class":128},[10,236,237,238,240],{},"None of this is special to Tauri. Peer dependencies for host-provided runtimes are the standard pattern across NPM — React, Vue, Svelte, RxJS, and every mature plugin ecosystem work the same way. The only Tauri-specific thing here is remembering to apply the pattern to ",[25,239,27],{},". Any other dependencies can be managed according to your preferred pinning style.",[10,242,243],{},"Library code isn't application code. When a package plugs into a shared runtime, the right question isn't \"how tightly can we pin this?\" but \"who should own this version?\" For host-provided runtimes, the answer is almost always the host.",[245,246],"hr",{},[10,248,249],{},[250,251,252],"em",{},"Tauri is a trademark of the Tauri Programme within The Commons Conservancy.",[254,255,256],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":118,"searchDepth":132,"depth":132,"links":258},[259,260,261,262],{"id":32,"depth":132,"text":33},{"id":66,"depth":132,"text":67},{"id":93,"depth":132,"text":94},{"id":104,"depth":132,"text":105},"2026-04-17","Publishing a Tauri plugin means letting go of the exact-pinning habits that work for application code. Here's how @tauri-apps\u002Fapi should be declared, and why.","md",null,{},true,"\u002Fblog\u002Ftauri-plugins-peer-dont-pin",{"title":5,"description":264},"blog\u002Ftauri-plugins-peer-dont-pin",[273,274],"tauri","software engineering","-9jfwxyyT2B3b6vUXOpJEEFwQuai86vdp5JZsq-ao5Y",1776453428629]