Claude Code SDK for Dart

A powerful Dart SDK for interacting with Claude Code, providing seamless integration with AI-powered coding assistance through the Claude Code CLI.

Features

  • ๐Ÿš€ Easy Integration: Simple API for creating chat sessions with Claude
  • ๐Ÿ“ File Support: Send files along with text prompts for context-aware responses
  • ๐Ÿ’พ Bytes Support: Send in-memory data as temporary files (auto-cleanup on dispose)
  • ๐Ÿ“‹ Schema Support: Get structured responses using JSON schemas
  • ๐Ÿ”„ Session Management: Automatic conversation continuity with --resume flag
  • ๐Ÿ› ๏ธ Auto-Installation: Built-in methods to check and install Claude Code SDK
  • ๐Ÿงน Resource Management: Proper cleanup and disposal of chat sessions and temp files
  • ๐Ÿ” Secure: API key management with environment variable support
  • โšก Reliable: Simple Process.run based implementation (no streaming complexity)

Prerequisites

Before using this SDK, you need:

  1. Node.js and npm (for Claude Code CLI)

  2. Claude Code CLI

    • Install globally: npm install -g @anthropic-ai/claude-code
    • Or use the SDK's built-in installer (see below)
  3. Anthropic API Key

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  claude_code_sdk: ^1.0.0

Then run:

dart pub get

Quick Start

Basic Usage

import 'package:claude_code_sdk/claude_code_sdk.dart';

void main() async {
  // Initialize the SDK with your API key
  final claudeSDK = Claude('YOUR_API_KEY');
  
  // Create a new chat session
  final claudeChat = claudeSDK.createNewChat();
  
  try {
    // Send a simple text message
    final result = await claudeChat.sendMessage([
      ClaudeSdkContent.text('What is the capital of France?'),
    ]);
    
    print('Claude says: $result');
  } finally {
    // Always dispose of the chat when done
    await claudeChat.dispose();
  }
}

Sending Files with Messages

import 'dart:io';
import 'package:claude_code_sdk/claude_code_sdk.dart';

void main() async {
  final claudeSDK = Claude('YOUR_API_KEY');
  final claudeChat = claudeSDK.createNewChat();
  
  try {
    // Send a message with a file
    final result = await claudeChat.sendMessage([
      ClaudeSdkContent.text('Please analyze this HTML file and extract the user name'),
      ClaudeSdkContent.file(File('example.html')),
    ]);
    
    print('Analysis result: $result');
  } finally {
    await claudeChat.dispose();
  }
}

Sending Bytes as Temporary Files

You can send in-memory data (like images, documents, or any binary data) without creating permanent files:

import 'dart:typed_data';
import 'package:claude_code_sdk/claude_code_sdk.dart';

void main() async {
  final claudeSDK = Claude('YOUR_API_KEY');
  final claudeChat = claudeSDK.createNewChat();
  
  try {
    // Example 1: Send image bytes
    final imageBytes = await File('photo.jpg').readAsBytes();
    final result = await claudeChat.sendMessage([
      ClaudeSdkContent.text('What is in this image?'),
      ClaudeSdkContent.bytes(
        data: imageBytes,
        fileExtension: 'jpg',
      ),
    ]);
    
    // Example 2: Send text as bytes
    final textContent = 'Hello, this is dynamic content!';
    final textBytes = Uint8List.fromList(textContent.codeUnits);
    final result2 = await claudeChat.sendMessage([
      ClaudeSdkContent.text('Read this text:'),
      ClaudeSdkContent.bytes(
        data: textBytes,
        fileExtension: 'txt',
      ),
    ]);
    
    print('Result: $result2');
  } finally {
    // Temporary files are automatically deleted when disposed
    await claudeChat.dispose();
  }
}

Note: Temporary files created from bytes are automatically cleaned up when you call dispose() on the chat session.

Using Schemas for Structured Responses

import 'dart:io';
import 'package:claude_code_sdk/claude_code_sdk.dart';

void main() async {
  final claudeSDK = Claude('YOUR_API_KEY');
  final claudeChat = claudeSDK.createNewChat();
  
  try {
    // Define a schema with nullable properties
    final schema = SchemaObject(
      properties: {
        'userName': SchemaProperty.string(
          description: 'The name of the user found in the HTML',
          nullable: false, // Required field
        ),
        'userEmail': SchemaProperty.string(
          description: 'The email of the user if found',
          nullable: true, // Optional field
        ),
        'userRole': SchemaProperty.string(
          description: 'The role or title of the user',
          nullable: true, // Optional field
        ),
      },
      // No need to specify 'required' array - it's automatically derived from nullable properties
    );
    
    // Send message with schema
    final result = await claudeChat.sendMessageWithSchema(
      messages: [
        ClaudeSdkContent.text('Extract user information from this HTML file'),
        ClaudeSdkContent.file(File('profile.html')),
      ],
      schema: schema,
    );
    
    print('Model message: ${result.modelMessage}');
    print('Extracted data: ${result.data}');
    
    // Access specific fields
    final userName = result.data['userName'];
    print('User name: $userName');
  } finally {
    await claudeChat.dispose();
  }
}

Streaming Responses

import 'package:claude_code_sdk/claude_code_sdk.dart';

void main() async {
  final claudeSDK = Claude('YOUR_API_KEY');
  final claudeChat = claudeSDK.createNewChat(
    options: ClaudeChatOptions(
      streamJson: true,
    ),
  );
  
  try {
    // Stream the response
    await for (final chunk in claudeChat.streamResponse([
      ClaudeSdkContent.text('Write a detailed explanation of quantum computing'),
    ])) {
      print(chunk); // Print each chunk as it arrives
    }
  } finally {
    await claudeChat.dispose();
  }
}

Advanced Configuration

Chat Options

final claudeChat = claudeSDK.createNewChat(
  options: ClaudeChatOptions(
    systemPrompt: 'You are a helpful coding assistant',
    maxTurns: 5,
    allowedTools: ['Read', 'Write', 'Bash'],
    permissionMode: 'acceptEdits',
    cwd: '/path/to/project',
    model: 'claude-3.5-sonnet',
    outputJson: true,
    timeoutMs: 30000,
  ),
);

Checking and Installing Claude Code SDK

void main() async {
  final claudeSDK = Claude('YOUR_API_KEY');
  
  // Check if Claude Code SDK is installed
  final isInstalled = await claudeSDK.isClaudeCodeSDKInstalled();
  
  if (!isInstalled) {
    print('Claude Code SDK is not installed. Installing...');
    
    try {
      // Install the SDK globally
      await claudeSDK.installClaudeCodeSDK(global: true);
      print('Installation complete!');
    } catch (e) {
      print('Installation failed: $e');
    }
  }
  
  // Get SDK information
  final info = await claudeSDK.getSDKInfo();
  print('SDK Info: $info');
}

Schema Building

The SDK provides convenient factory methods for building schemas with nullable control:

final schema = SchemaObject(
  properties: {
    'name': SchemaProperty.string(
      description: 'User name',
      defaultValue: 'Anonymous',
      nullable: false, // Required field
    ),
    'age': SchemaProperty.number(
      description: 'User age',
      nullable: false, // Required field
    ),
    'email': SchemaProperty.string(
      description: 'User email',
      nullable: true, // Optional field (default)
    ),
    'isActive': SchemaProperty.boolean(
      description: 'Whether the user is active',
      defaultValue: true,
      nullable: false, // Required with default value
    ),
    'tags': SchemaProperty.array(
      items: SchemaProperty.string(),
      description: 'List of tags',
      nullable: true, // Optional array
    ),
    'metadata': SchemaProperty.object(
      properties: {
        'created': SchemaProperty.string(nullable: false),
        'updated': SchemaProperty.string(nullable: true),
      },
      description: 'Metadata object',
      nullable: true, // Optional nested object
    ),
  },
  // The 'required' array is automatically generated from nullable: false properties
  description: 'User information schema',
);

Nullable Property Behavior

  • nullable: false - The property is required and must be present in the response
  • nullable: true (default) - The property is optional and may be omitted or null
  • Properties with nullable: false are automatically added to the JSON schema's required array
  • You can still use the legacy required parameter on SchemaObject for backward compatibility

Error Handling

The SDK provides specific exception types for different error scenarios:

import 'package:claude_code_sdk/claude_code_sdk.dart';

void main() async {
  final claudeSDK = Claude('YOUR_API_KEY');
  final claudeChat = claudeSDK.createNewChat();
  
  try {
    final result = await claudeChat.sendMessage([
      ClaudeSdkContent.text('Hello, Claude!'),
    ]);
    print(result);
  } on CLINotFoundException {
    print('Claude Code CLI is not installed. Please install it first.');
  } on ProcessException catch (e) {
    print('Process error: ${e.message}');
    if (e.exitCode != null) {
      print('Exit code: ${e.exitCode}');
    }
  } on JSONDecodeException catch (e) {
    print('Failed to parse response: ${e.message}');
  } on ClaudeSDKException catch (e) {
    print('SDK error: ${e.message}');
  } finally {
    await claudeChat.dispose();
  }
}

Implementation Details

This SDK uses a simple and reliable approach:

  • Process.run: Each message is sent as a separate process call (no streaming complexity)
  • Session Management: Uses Claude CLI's --resume flag for conversation continuity
  • JSON Output: Always uses --output-format json for consistent parsing
  • Automatic Fallback: Tries claude command first, falls back to claude-code if needed

Resource Management

Important: Always Dispose Chat Sessions

Always dispose of chat sessions when done to ensure proper cleanup:

// Method 1: Using try-finally
final chat = claudeSDK.createNewChat();
try {
  // Use the chat
  await chat.sendMessage([...]);
} finally {
  await chat.dispose();
}

// Method 2: Dispose all sessions at once
await claudeSDK.dispose(); // Disposes all active sessions

API Reference

Claude Class

  • Claude(String apiKey) - Creates a new SDK instance
  • createNewChat({ClaudeChatOptions? options}) - Creates a new chat session
  • isClaudeCodeSDKInstalled() - Checks if Claude Code CLI is installed
  • installClaudeCodeSDK({bool global = true}) - Installs the Claude Code SDK
  • getSDKInfo() - Gets information about installed SDKs
  • dispose() - Disposes all active chat sessions

ClaudeChat Class

  • sendMessage(List<ClaudeSdkContent> contents) - Sends a message and returns the response
  • sendMessageWithSchema({messages, schema}) - Sends a message with a schema for structured response
  • get sessionId - Gets the current session ID (null until first message)
  • resetConversation() - Resets the conversation, starting a new session
  • dispose() - Disposes the chat session and cleans up resources (including temp files)

ClaudeSdkContent

  • ClaudeSdkContent.text(String text) - Creates text content
  • ClaudeSdkContent.file(File file) - Creates file content
  • ClaudeSdkContent.bytes({data, fileExtension}) - Creates content from bytes (temporary file)

Environment Variables

You can also set your API key as an environment variable:

export ANTHROPIC_API_KEY="your-api-key-here"

Then use it in your code:

final apiKey = Platform.environment['ANTHROPIC_API_KEY'] ?? '';
final claudeSDK = Claude(apiKey);

Troubleshooting

Claude Code CLI not found

If you get a CLINotFoundException, make sure Claude Code is installed:

npm install -g @anthropic-ai/claude-code

Or use the SDK's built-in installer:

await claudeSDK.installClaudeCodeSDK();

Permission Errors

On Unix-like systems, you might need to use sudo for global npm installations:

sudo npm install -g @anthropic-ai/claude-code

Process Cleanup

Always dispose of chat sessions to prevent resource leaks:

await claudeChat.dispose();
// or
await claudeSDK.dispose(); // Disposes all sessions

Examples

Check the example/ directory for more comprehensive examples:

  • example/basic_usage.dart - Simple text messaging
  • example/file_analysis.dart - Analyzing files with Claude
  • example/schema_example.dart - Using schemas for structured responses
  • example/streaming_example.dart - Streaming responses
  • example/installation_check.dart - Checking and installing dependencies

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

For issues and questions:

Acknowledgments

  • Built on top of the official Claude Code CLI by Anthropic
  • Inspired by the Python and TypeScript SDKs

Libraries

claude_code_sdk
Claude Code SDK for Dart