trpc-navigation-plugin
v0.3.4
Published
TypeScript Language Service Plugin that fixes broken 'go to definition' for tRPC when using declaration emit
Downloads
2,097
Maintainers
Readme
tRPC Navigation Plugin
A TypeScript Language Service Plugin that fixes broken "go to definition" navigation for tRPC procedures when using TypeScript's declaration emit.
🚀 View Demo Repository - See the plugin in action with a T3 Turbo monorepo
Problem
When using tRPC with TypeScript's declaration emit (declaration: true), there's a TypeScript bug that completely breaks "go to definition" functionality. When you try to Cmd+Click on a tRPC procedure call like api.users.getUser.useQuery(), TypeScript only takes you to the root router definition - it can't navigate any deeper into nested routers or the actual procedure implementations.
This is caused by how TypeScript handles the complex type inference in tRPC's router chains when generating .d.ts files.
Solution
This plugin bypasses TypeScript's broken type-based navigation by using a configured router location. When you Cmd+Click on a tRPC procedure, the plugin intercepts the navigation request and takes you directly to the procedure implementation in your source code.
Features
- Fixes Broken Navigation: Restores "go to definition" functionality that TypeScript's declaration emit breaks
- Direct Source Navigation: Takes you to the actual implementation code, not type definitions
- Simple Configuration: Just specify your router location - no complex setup
- Intelligent Client Detection: Automatically detects any tRPC client variable (api, trpc, client, etc.)
- useUtils Support: Full navigation support for
useUtils()variables - Cross-Package Support: Works seamlessly in monorepos
- Fast Performance: Direct navigation without complex type resolution
When You Need This Plugin
You need this plugin if:
- Your project uses tRPC
- You have
declaration: truein your tsconfig.json (for generating .d.ts files) - "Go to definition" on tRPC procedures doesn't work or takes you to the wrong place
You DON'T need this plugin if:
- You're not using TypeScript's declaration emit
- Your "go to definition" already works correctly
Installation
⚠️ IMPORTANT: VS Code & Editor Setup
You MUST use the workspace TypeScript version, not the built-in version!
In VS Code, Cursor, or other VS Code-based editors:
- Open any TypeScript file in your project
- Click the TypeScript version number or
{}in the bottom status bar- Select "Select TypeScript Version..."
- Choose "Use Workspace Version" (NOT "Use VS Code's Version")
Without this step, the plugin will not be detected or loaded!
Setup Steps
Install the plugin:
npm install --save-dev trpc-navigation-plugin # or yarn add -D trpc-navigation-plugin # or bun add -D trpc-navigation-pluginConfigure in your
tsconfig.jsonwith your router location:{ "compilerOptions": { "plugins": [ { "name": "trpc-navigation-plugin", "router": { "filePath": "./src/server/api/root.ts", "variableName": "appRouter" } } ] } }Switch to workspace TypeScript (see important note above)
Restart the TypeScript server:
Cmd+Shift+P→ "TypeScript: Restart TS Server"
Configuration
The plugin requires you to specify where your tRPC router is defined:
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "./src/server/api/root.ts", // Path to your router file
"variableName": "appRouter" // Name of your router variable
}
}
]
}
}Configuration Options
router.filePath(required): Path to the file containing your main tRPC router- Can be relative (resolved from project root) or absolute
- Example:
"./src/server/api/root.ts"
router.variableName(required): Name of your router variable in that file- Example:
"appRouter","router","mainRouter"
- Example:
patterns(optional): Customize pattern detectionprocedureTypes: Procedure types to detect (default:['query', 'mutation', 'subscription'])routerFunctions: Router function names (default:['router', 'createTRPCRouter', 'createRouter', 't.router'])clientInitializers: Client initialization patterns (default:['createTRPC', 'initTRPC', 'createTRPCClient'])utilsMethod: Name of the utils method (default:'useUtils')
fileExtensions(optional): File extensions to process- Default:
['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'] - Add custom extensions if needed
- Default:
Monorepo Setup
In monorepos, add the plugin to each package that uses tRPC:
// packages/api/tsconfig.json - where router is defined
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "./src/router/index.ts",
"variableName": "appRouter"
}
}
]
}
}
// packages/web/tsconfig.json - uses tRPC through @my/api package
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "../api/src/router/index.ts",
"variableName": "appRouter"
}
}
]
}
}Optional: Enable Verbose Logging
For debugging, you can enable verbose logging:
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "./src/server/api/root.ts",
"variableName": "appRouter"
},
"verbose": true // Enable detailed logging
}
]
}
}Advanced Configuration
Customize detection patterns for non-standard tRPC setups:
{
"compilerOptions": {
"plugins": [
{
"name": "trpc-navigation-plugin",
"router": {
"filePath": "./src/server/api/root.ts",
"variableName": "appRouter"
},
"patterns": {
"procedureTypes": ["query", "mutation", "subscription", "action"],
"routerFunctions": ["router", "createTRPCRouter", "myCustomRouter"],
"clientInitializers": ["createTRPC", "initTRPC", "setupTRPC"],
"utilsMethod": "useUtils" // or "useContext" for older tRPC versions
},
"fileExtensions": [".ts", ".tsx", ".mts", ".cts"]
}
]
}
}Automatic tRPC Client Detection
The plugin intelligently detects any tRPC client variable, regardless of naming:
// All of these work automatically:
export const api = createTRPCReact<AppRouter>();
export const trpc = createTRPCNext<AppRouter>();
export const client = createTRPCProxyClient<AppRouter>();
export const myCustomName = initTRPC.create();
// In your components:
api.users.getUser.useQuery(); // ✓ Works
trpc.users.getUser.useQuery(); // ✓ Works
client.users.getUser.query(); // ✓ Works
myCustomName.users.getUser.query(); // ✓ Works
// useUtils variables also work:
const utils = api.useUtils();
const apiCtx = trpc.useUtils();
utils.users.getUser.fetch(); // ✓ Works
apiCtx.users.getUser.invalidate(); // ✓ WorksHow It Works
- Router Configuration: You specify where your tRPC router is defined
- Client Detection: The plugin detects when you click on a tRPC client call
- Direct Navigation: Uses the configured router location to navigate directly to procedures
- Path Resolution: Follows the navigation path through nested routers to find the target
- Contextual Navigation: Click on different parts for different results:
api.billing.claims- clicking "billing" goes to billing routerapi.billing.claims- clicking "claims" goes to claims procedure/router
Troubleshooting
If navigation isn't working:
- Verify TypeScript Version: Make sure you're using the workspace TypeScript version! (See installation section)
- Check Configuration: Ensure your router config points to the correct file and variable name
- Verify Router Export: Make sure your router is exported or declared in the specified file
- Check TS Server Logs: Look for
[TRPC-Nav]entries in the TypeScript Server logs - Enable Verbose Logging: Add
"verbose": trueto see detailed debug information - Restart TS Server: After configuration changes:
Cmd+Shift+P→ "TypeScript: Restart TS Server"
Common issues:
- Plugin not loading: You're using VS Code's built-in TypeScript instead of workspace version
- Wrong file path: Paths are resolved from the project root (where tsconfig.json is)
- Wrong variable name: The variable name must match exactly (case-sensitive)
- Router not found: Make sure the router is a top-level export or variable declaration
Technical Details
- Uses TypeScript's Language Service API for navigation interception
- Directly navigates to configured router location without complex type resolution
- Works around TypeScript's navigation bug without modifying your build process
- Compatible with tRPC v10+ and v11 that use the standard router pattern
- Supports monorepo setups with relative or absolute paths
- Does not interfere with TypeScript's type checking or declaration emit
