{"meta":{"title":"User prompt submitted hook","intro":"The onUserPromptSubmitted hook is called when a user submits a message. Use it to:","product":"GitHub Copilot","breadcrumbs":[{"href":"/en/copilot","title":"GitHub Copilot"},{"href":"/en/copilot/how-tos","title":"How-tos"},{"href":"/en/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/en/copilot/how-tos/copilot-sdk/hooks","title":"Use hooks"},{"href":"/en/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted","title":"User Prompt Submitted"}],"documentType":"article"},"body":"# User prompt submitted hook\n\nThe onUserPromptSubmitted hook is called when a user submits a message. Use it to:\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n* Modify or enhance user prompts\n* Add context before processing\n* Filter or validate user input\n* Implement prompt templates\n\n## Hook signature\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\ntype UserPromptSubmittedHandler = (\n  input: UserPromptSubmittedHookInput,\n  invocation: HookInvocation\n) => Promise<UserPromptSubmittedHookOutput | null | undefined>;\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nUserPromptSubmittedHandler = Callable[\n    [UserPromptSubmittedHookInput, dict[str, str]],\n    Awaitable[UserPromptSubmittedHookOutput | None]\n]\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\ntype UserPromptSubmittedHandler func(\n    input UserPromptSubmittedHookInput,\n    invocation HookInvocation,\n) (*UserPromptSubmittedHookOutput, error)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\npublic delegate Task<UserPromptSubmittedHookOutput?> UserPromptSubmittedHandler(\n    UserPromptSubmittedHookInput input,\n    HookInvocation invocation);\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n```java\n@FunctionalInterface\npublic interface UserPromptSubmittedHandler {\n    CompletableFuture<UserPromptSubmittedHookOutput> handle(\n        UserPromptSubmittedHookInput input,\n        HookInvocation invocation);\n}\n```\n\n</div>\n\n</div>\n\n## Input\n\n| Field       | Type   | Description                                |\n| ----------- | ------ | ------------------------------------------ |\n| `timestamp` | number | Unix timestamp when the hook was triggered |\n| `cwd`       | string | Current working directory                  |\n| `prompt`    | string | The user's submitted prompt                |\n\n## Output\n\nReturn `null` or `undefined` to use the prompt unchanged. Otherwise, return an object with any of these fields:\n\n| Field               | Type    | Description                                       |\n| ------------------- | ------- | ------------------------------------------------- |\n| `modifiedPrompt`    | string  | Modified prompt to use instead of original        |\n| `additionalContext` | string  | Extra context added to the conversation           |\n| `suppressOutput`    | boolean | If true, suppress the assistant's response output |\n\n## Examples\n\n### Log all user prompts\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input, invocation) => {\n      console.log(`[${invocation.sessionId}] User: ${input.prompt}`);\n      return null; // Pass through unchanged\n    },\n  },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot.session import PermissionHandler\n\nasync def on_user_prompt_submitted(input_data, invocation):\n    print(f\"[{invocation['session_id']}] User: {input_data['prompt']}\")\n    return None\n\nsession = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={\"on_user_prompt_submitted\": on_user_prompt_submitted})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\nsession, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{\n    Hooks: &copilot.SessionHooks{\n        OnUserPromptSubmitted: func(input copilot.UserPromptSubmittedHookInput, inv copilot.HookInvocation) (*copilot.UserPromptSubmittedHookOutput, error) {\n            fmt.Printf(\"[%s] User: %s\\n\", inv.SessionID, input.Prompt)\n            return nil, nil\n        },\n    },\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    Hooks = new SessionHooks\n    {\n        OnUserPromptSubmitted = (input, invocation) =>\n        {\n            Console.WriteLine($\"[{invocation.SessionId}] User: {input.Prompt}\");\n            return Task.FromResult<UserPromptSubmittedHookOutput?>(null);\n        },\n    },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.*;\nimport com.github.copilot.rpc.*;\nimport java.util.concurrent.CompletableFuture;\n\nvar hooks = new SessionHooks()\n    .setOnUserPromptSubmitted((input, invocation) -> {\n        System.out.println(\"[\" + invocation.getSessionId() + \"] User: \" + input.prompt());\n        return CompletableFuture.completedFuture(null);\n    });\n\nvar session = client.createSession(\n    new SessionConfig()\n        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n        .setHooks(hooks)\n).get();\n```\n\n</div>\n\n</div>\n\n### Add project context\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      const projectInfo = await getProjectInfo();\n      \n      return {\n        additionalContext: `\nProject: ${projectInfo.name}\nLanguage: ${projectInfo.language}\nFramework: ${projectInfo.framework}\n        `.trim(),\n      };\n    },\n  },\n});\n```\n\n### Expand shorthand commands\n\n```typescript\nconst SHORTCUTS: Record<string, string> = {\n  \"/fix\": \"Please fix the errors in the code\",\n  \"/explain\": \"Please explain this code in detail\",\n  \"/test\": \"Please write unit tests for this code\",\n  \"/refactor\": \"Please refactor this code to improve readability and maintainability\",\n};\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      for (const [shortcut, expansion] of Object.entries(SHORTCUTS)) {\n        if (input.prompt.startsWith(shortcut)) {\n          const rest = input.prompt.slice(shortcut.length).trim();\n          return {\n            modifiedPrompt: `${expansion}${rest ? `: ${rest}` : \"\"}`,\n          };\n        }\n      }\n      return null;\n    },\n  },\n});\n```\n\n### Content filtering\n\n```typescript\nconst BLOCKED_PATTERNS = [\n  /password\\s*[:=]/i,\n  /api[_-]?key\\s*[:=]/i,\n  /secret\\s*[:=]/i,\n];\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      for (const pattern of BLOCKED_PATTERNS) {\n        if (pattern.test(input.prompt)) {\n          // Replace the prompt with a warning message\n          return {\n            modifiedPrompt: \"[Content blocked: Please don't include sensitive credentials in your prompts. Use environment variables instead.]\",\n            suppressOutput: true,\n          };\n        }\n      }\n      return null;\n    },\n  },\n});\n```\n\n### Enforce prompt length limits\n\n```typescript\nconst MAX_PROMPT_LENGTH = 10000;\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      if (input.prompt.length > MAX_PROMPT_LENGTH) {\n        // Truncate the prompt and add context\n        return {\n          modifiedPrompt: input.prompt.substring(0, MAX_PROMPT_LENGTH),\n          additionalContext: `Note: The original prompt was ${input.prompt.length} characters and was truncated to ${MAX_PROMPT_LENGTH} characters.`,\n        };\n      }\n      return null;\n    },\n  },\n});\n```\n\n### Add user preferences\n\n```typescript\ninterface UserPreferences {\n  codeStyle: \"concise\" | \"verbose\";\n  preferredLanguage: string;\n  experienceLevel: \"beginner\" | \"intermediate\" | \"expert\";\n}\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      const prefs: UserPreferences = await loadUserPreferences();\n      \n      const contextParts = [];\n      \n      if (prefs.codeStyle === \"concise\") {\n        contextParts.push(\"User prefers concise code with minimal comments.\");\n      } else {\n        contextParts.push(\"User prefers verbose code with detailed comments.\");\n      }\n      \n      if (prefs.experienceLevel === \"beginner\") {\n        contextParts.push(\"Explain concepts in simple terms.\");\n      }\n      \n      return {\n        additionalContext: contextParts.join(\" \"),\n      };\n    },\n  },\n});\n```\n\n### Usage threshold notices\n\n```typescript\nconst promptTimestamps: number[] = [];\nconst NOTICE_THRESHOLD = 10; // prompts\nconst RATE_WINDOW = 60000; // 1 minute\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      const now = Date.now();\n      \n      // Remove timestamps outside the window\n      while (promptTimestamps.length > 0 && promptTimestamps[0] < now - RATE_WINDOW) {\n        promptTimestamps.shift();\n      }\n\n      promptTimestamps.push(now);\n      if (promptTimestamps.length >= NOTICE_THRESHOLD) {\n        // This is advisory context for the model, not an enforced rate limit.\n        // Enforce hard limits before calling session.send().\n        return {\n          additionalContext: `The user has sent ${promptTimestamps.length} prompts in the last minute. Suggest waiting before sending more.`,\n        };\n      }\n\n      return null;\n    },\n  },\n});\n```\n\n### Prompt templates\n\n```typescript\nconst TEMPLATES: Record<string, (args: string) => string> = {\n  \"bug:\": (desc) => `I found a bug: ${desc}\n\nPlease help me:\n1. Understand why this is happening\n2. Suggest a fix\n3. Explain how to prevent similar bugs`,\n\n  \"feature:\": (desc) => `I want to implement this feature: ${desc}\n\nPlease:\n1. Outline the implementation approach\n2. Identify potential challenges\n3. Provide sample code`,\n};\n\nconst session = await client.createSession({\n  hooks: {\n    onUserPromptSubmitted: async (input) => {\n      for (const [prefix, template] of Object.entries(TEMPLATES)) {\n        if (input.prompt.toLowerCase().startsWith(prefix)) {\n          const args = input.prompt.slice(prefix.length).trim();\n          return {\n            modifiedPrompt: template(args),\n          };\n        }\n      }\n      return null;\n    },\n  },\n});\n```\n\n## Best practices\n\n1. **Preserve user intent** - When modifying prompts, ensure the core intent remains clear.\n\n2. **Be transparent about modifications** - If you significantly change a prompt, consider logging or notifying the user.\n\n3. **Use `additionalContext` over `modifiedPrompt`** - Adding context is less intrusive than rewriting the prompt.\n\n4. **Use `additionalContext` for advisory guidance**: This hook cannot reject a prompt or enforce policy. Enforce hard limits before calling `session.send()`.\n\n5. **Keep processing fast** - This hook runs on every user message. Avoid slow operations.\n\n## See also\n\n* [Use hooks](/en/copilot/how-tos/copilot-sdk/hooks)\n* [Session lifecycle hooks](/en/copilot/how-tos/copilot-sdk/hooks/session-lifecycle)\n* [Pre-tool use hook](/en/copilot/how-tos/copilot-sdk/hooks/pre-tool-use)"}