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