Custom Tools
Custom tools let you extend opencode with domain-specific operations while keeping permissions manageable.
When to Use Custom Tools
Custom tools are useful when the built-in tools are insufficient:
| Use Case | Example |
|---|---|
| Internal API queries | Fetch deployment status from your CI/CD system |
| Domain-specific operations | Compile a design token file from Figma API data |
| Custom services | Trigger a database backup, invalidate a CDN cache |
| Business logic validation | Check if a proposed change violates a business rule |
| Workflow automation | Create a Jira ticket, send a Slack notification |
Defining Custom Tools
Custom tools are defined in the opencode.json configuration file under the custom_tools key.
Basic Structure
{
"custom_tools": {
"get-deployment-status": {
"description": "Get the current deployment status from Vercel",
"command": "vercel status --json",
"permission": "allow"
},
"invalidate-cdn-cache": {
"description": "Purge the CDN cache for a given path",
"command": "curl -X POST https://api.fastly.com/service/xxx/purge/{path}",
"parameters": {
"path": {
"type": "string",
"description": "The URL path to purge (e.g. /api/v1/*)"
}
},
"permission": "ask"
}
}
}
Schema
| Field | Required | Type | Description |
|---|---|---|---|
description | Yes | string | Tells the LLM when and why to use this tool. Be specific. |
command | Yes | string | The shell command to execute. Supports {parameter} interpolation. |
parameters | No | object | JSON Schema describing the tool's parameters. |
permission | No | string | "allow", "ask", or "deny". Defaults to "ask". |
Parameters
When parameters is defined, the LLM must provide values for each parameter before invoking the tool. Parameters use JSON Schema syntax:
{
"parameters": {
"service": {
"type": "string",
"description": "The service name to restart (e.g. api, worker, web)",
"enum": ["api", "worker", "web", "cron"]
},
"environment": {
"type": "string",
"description": "Target environment",
"enum": ["staging", "production"],
"default": "staging"
},
"force": {
"type": "boolean",
"description": "Skip confirmation prompts",
"default": false
}
}
}
The command template references parameters with {name} syntax:
{
"command": "deployctl restart --service {service} --env {environment}"
}
If force is true, the command becomes:
{
"command": "deployctl restart --service {service} --env {environment} --force"
}
Advanced Examples
Database Query Tool
{
"custom_tools": {
"run-database-query": {
"description": "Execute a read-only SQL query against the production database. Returns results as JSON. Never use for writes.",
"command": "psql $DATABASE_URL -c {query} --json",
"parameters": {
"query": {
"type": "string",
"description": "The SQL query to execute. Must be a SELECT statement."
}
},
"permission": "ask"
}
}
}
Jira Ticket Lookup
{
"custom_tools": {
"get-jira-ticket": {
"description": "Fetch details of a Jira ticket by its key (e.g. PROJ-1234). Returns summary, status, assignee, and description.",
"command": "curl -s -u $JIRA_AUTH 'https://jira.company.com/rest/api/2/issue/{ticket_key}' | jq '{key: .key, summary: .fields.summary, status: .fields.status.name, assignee: .fields.assignee.displayName, description: .fields.description}'",
"parameters": {
"ticket_key": {
"type": "string",
"description": "The Jira ticket key (e.g. PROJ-1234)",
"pattern": "^[A-Z]+-\\d+$"
}
},
"permission": "allow"
}
}
}
Slack Notification
{
"custom_tools": {
"send-slack-message": {
"description": "Send a message to a Slack channel. Use this to notify the team about deployments, failures, or important updates.",
"command": "curl -s -X POST -H 'Content-Type: application/json' -d '{\"channel\": \"{channel}\", \"text\": \"{message}\"}' $SLACK_WEBHOOK_URL",
"parameters": {
"channel": {
"type": "string",
"description": "The Slack channel name (with or without # prefix)",
"pattern": "^#?[a-z0-9_-]+$"
},
"message": {
"type": "string",
"description": "The message text to send"
}
},
"permission": "ask"
}
}
}
Permission Pairing
Safe Default
Default custom tools to "ask" and only elevate to "allow" when you trust the command and its parameters.
Allow with Caution
Deny for Safety
Combining with Agent Permissions
Custom tools respect agent-level permission overrides. You can restrict which agents can use which custom tools:
{
"agent": {
"deploy-agent": {
"description": "Handles deployments",
"mode": "subagent",
"permission": {
"custom_tools": {
"get-deployment-status": "allow",
"invalidate-cdn-cache": "allow",
"send-slack-message": "allow",
"run-database-query": "deny"
}
}
}
}
}
Best Practices
Be Specific in Descriptions
Validate Parameters
Use JSON Schema constraints to prevent invalid inputs:
enumfor fixed sets of values.patternfor format validation.minimum/maximumfor numeric ranges.