Skip to main content

Custom Tools

tip

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 CaseExample
Internal API queriesFetch deployment status from your CI/CD system
Domain-specific operationsCompile a design token file from Figma API data
Custom servicesTrigger a database backup, invalidate a CDN cache
Business logic validationCheck if a proposed change violates a business rule
Workflow automationCreate 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​

FieldRequiredTypeDescription
descriptionYesstringTells the LLM when and why to use this tool. Be specific.
commandYesstringThe shell command to execute. Supports {parameter} interpolation.
parametersNoobjectJSON Schema describing the tool's parameters.
permissionNostring"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​

tip

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:

  • enum for fixed sets of values.
  • pattern for format validation.
  • minimum/maximum for numeric ranges.

Use Environment Variables​

Keep Commands Simple​

Test Permissions​