
Projects
- 39 installs
- 23 repo stars
- Updated August 5, 2026
- launchdarkly/ai-tooling
Helps with ai & agent building tasks.
About
projects is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- projects
- AI & Agent Building
- AI-coding skill
Projects by the numbers
- 39 all-time installs (skills.sh)
- +4 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #8,302 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/launchdarkly/ai-tooling --skill projectsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 39 |
|---|---|
| repo stars | ★ 23 |
| Last updated | August 5, 2026 |
| Repository | launchdarkly/ai-tooling ↗ |
What it does
Helps with ai & agent building tasks.
Files
LaunchDarkly Projects Setup
You're using a skill that will guide you through setting up LaunchDarkly project management in a codebase. Your job is to explore the codebase to understand the stack and patterns, assess what approach makes sense, choose the right implementation path from the references, execute the setup, and verify it works.
Prerequisites
Choose one:
- LaunchDarkly API access token with
projects:writepermission - LaunchDarkly MCP server configured in your environment
Core Principles
1. Understand First: Explore the codebase to understand the stack and patterns. 2. Choose the Right Fit: Select an approach that matches your architecture. 3. Follow Conventions: Respect existing code style and structure. 4. Verify Integration: Confirm the setup works: the agent performs checks and reports results.
API Key Detection
Before prompting the user for an API key, try to detect it automatically:
1. Check environment variables: Look for LAUNCHDARKLY_API_KEY, LAUNCHDARKLY_API_TOKEN, or LD_API_KEY 2. Check MCP config: If using Claude, read ~/.claude/config.json for mcpServers.launchdarkly.env.LAUNCHDARKLY_API_KEY 3. Prompt user: Only if detection fails, ask the user for their API key
See Quick Start for API usage patterns.
What Are Projects?
Projects are LaunchDarkly's top-level organizational containers that hold:
- All your configs
- Feature flags and segments
- Multiple environments (Production and Test created by default)
Think of projects as separate applications, services, or teams that need their own isolated set of configurations.
Project Setup Workflow
Step 1: Explore the Codebase
Before implementing anything, understand the existing architecture:
1. Identify the tech stack:
- What language(s)? (Python, Node.js, Go, Java, etc.)
- What framework(s)? (FastAPI, Express, Spring Boot, etc.)
- Is there an existing LaunchDarkly integration?
2. Check environment management:
- How are environment variables stored? (.env files, secrets manager, config files)
- Where is configuration loaded? (startup scripts, config modules)
- Are there existing LaunchDarkly SDK keys?
3. Look for patterns:
- Are there existing API clients or service modules?
- How is external API integration typically done?
- Is there a CLI, scripts directory, or admin tooling?
4. Understand the use case:
- Is this a new project being set up?
- Adding to an existing LaunchDarkly integration?
- Part of a multi-service architecture?
- Need for project cloning across regions/teams?
Step 2: Assess the Situation
Based on your exploration, determine the right approach:
| Scenario | Recommended Path |
|---|---|
| New project, no LaunchDarkly integration | Quick Setup - Create project and save SDK keys |
| Existing LaunchDarkly usage | Add to Existing - Create new project or use existing |
| Multiple services/microservices | Multi-Project - Create projects per service |
| Multi-region or multi-tenant | Project Cloning - Clone template project |
| Infrastructure-as-Code (IaC) setup | Automated Setup - Script-based creation |
| Need project management tooling | CLI/Admin Tools - Build project management utilities |
Step 3: Choose Your Implementation Path
Select the reference guide that matches your stack and use case:
By Language/Stack:
- Python Implementation - For Python applications (FastAPI, Django, Flask)
- Node.js/TypeScript Implementation - For Node.js/Express/NestJS applications
- Go Implementation - For Go services
- Multi-Language Setup - For polyglot architectures
By Use Case:
- Quick Start - Create first project and get SDK keys
- Environment Configuration - Save SDK keys to .env, secrets, or config
- Project Cloning - Clone projects for regions/teams
- IaC/Automation - Terraform, scripts, CI/CD integration
- Admin Tooling - Build CLI or admin utilities
Step 4: Implement the Integration
Follow the chosen reference guide to implement project management. Key considerations:
1. API Authentication:
- Store API token securely
- Follow existing secrets management patterns
- Never commit tokens to version control
2. Project Naming:
- Use consistent, descriptive names
- Follow existing naming conventions
- Project keys: lowercase, hyphens, start with letter
3. SDK Key Management:
- Extract and store SDK keys for each environment
- Use the same pattern as other secrets in your codebase
- Consider separate keys for test/staging/production
4. Error Handling:
- Handle existing projects gracefully (409 conflict)
- Provide clear error messages
- Don't fail silently
Step 5: Verify the Setup
After creating the project, verify it works:
1. Fetch to confirm it exists. Prefer the MCP get-project tool over raw curl — it returns a typed object you can inspect directly. If you must call the REST API:
curl -X GET "https://app.launchdarkly.com/api/v2/projects/{projectKey}?expand=environments" \
-H "Authorization: {api_token}"Do not pipe the response straight into a `.environments.items[]`-style `jq` filter. The shape of environments varies by expand parameter — sometimes it's {items: [...]}, sometimes a bare array — and a hand-rolled filter will fail with Cannot index array with string "items". Run jq -e . first to inspect the actual shape, or use jq '.environments | if type == "object" then .items else . end' to handle both.
2. Test SDK integration: Run a quick verification to ensure the SDK key works:
import ldclient
from ldclient.config import Config
ldclient.set_config(Config("{sdk_key}"))
# SDK initializes successfully
# Always flush events before closing — trailing events are at risk of being
# lost otherwise, in short-lived scripts and long-running services alike.
ldclient.get().flush()
ldclient.get().close()3. Report results:
- ✓ Project exists and has environments
- ✓ SDK keys are present and valid
- ✓ SDK can initialize (or flag any issues)
Project Key Best Practices
Project keys must follow these rules:
✓ Good examples:
- "support-ai"
- "chat-bot-v2"
- "internal-tools"
✗ Bad examples:
- "Support_AI" # No uppercase or underscores
- "123-project" # Must start with letter
- "my.project" # No dots allowedNaming Recommendations:
- Keep keys short but descriptive
- Use team/service/purpose as naming scheme
- Be consistent across your organization
Common Organization Patterns
By Team
platform-ai → Platform Team Agent
customer-ai → Customer Success Team Agent
internal-ai → Internal Tools Team AgentBy Application/Service
mobile-ai → Mobile App configs
web-ai → Web App configs
api-ai → API Service configsBy Region/Deployment
ai-us → US Region
ai-eu → Europe Region
ai-apac → Asia-Pacific RegionEdge Cases
| Situation | Action |
|---|---|
| Project already exists | Check if it's the right one; use it or create with different key |
| Need multiple projects | Create separately for each service/region/team |
| Shared configs across services | Use same project, separate by SDK context |
| Token lacks permissions | Request projects:write or use MCP server |
| Project name conflict | Keys must be unique, names can be similar |
What NOT to Do
- Don't create projects without understanding the use case first
- Don't commit API tokens or SDK keys to version control
- Don't use production SDK keys in test/development environments
- Don't create duplicate projects unnecessarily
- Don't skip the exploration phase
Next Steps
After setting up projects:
1. Create configs - Use the configs-create skill 2. Set up SDK Integration - Use the sdk skill 3. Configure Targeting - Use the configs-targeting skill
Related Skills
configs-create- Create configs in projectssdk- Integrate SDK in your applicationconfigs-targeting- Configure config targetingconfigs-variations- Manage config variations
References
- Python Implementation
- Node.js Implementation
- Go Implementation
- Quick Start Guide
- Environment Configuration
- Project Cloning
- IaC/Automation
- Admin Tooling
LaunchDarkly Config Projects Skill
An Agent Skill for setting up LaunchDarkly project management in a codebase. Guides exploration of the stack, assessment of the right approach, and integration that fits the architecture.
Overview
This skill teaches agents how to:
- Explore the codebase to understand the tech stack and patterns
- Assess what project setup approach makes sense
- Choose the right implementation path (by language, use case, or tooling)
- Create projects and save SDK keys via API or MCP
- Verify the setup via API fetch and SDK integration test
Installation (Local)
For now, install by placing this skill directory where your agent client loads skills.
Examples:
- Generic: copy
skills/agentcontrol/projects/into your client's skills path
Prerequisites
Choose one:
- LaunchDarkly API access token with
projects:writepermission - LaunchDarkly MCP server configured in your environment
Usage
Once installed, the skill activates automatically when you ask about project setup:
Set up a LaunchDarkly project for our configsCreate a project for our customer support agentAdd LaunchDarkly project management to this codebaseStructure
projects/
├── SKILL.md
├── README.md
└── references/
├── quick-start.md
├── python-setup.md
├── nodejs-setup.md
├── go-setup.md
├── env-config.md
├── project-cloning.md
├── iac-automation.md
├── admin-tooling.md
└── multi-language-setup.mdRelated
- LaunchDarkly AgentControl: Create configs after setting up projects
- LaunchDarkly Docs
- Agent Skills Specification
License
Apache-2.0
Admin Tooling
Build CLI tools and admin utilities for project management at scale.
Use Cases
- Bulk operations: Create/manage many projects at once
- Auditing: Report on project usage and configuration
- Maintenance: Clean up unused projects
- Onboarding: Help teams set up their projects
- Automation: Integrate with CI/CD and IaC
Full-Featured CLI
Build a comprehensive CLI tool:
Python with Click
# cli/ldprojects.py
import click
import json
from tabulate import tabulate
from launchdarkly.projects import ProjectManager
pm = ProjectManager()
@click.group()
@click.option('--api-token', envvar='LAUNCHDARKLY_API_TOKEN', help='LaunchDarkly API token')
@click.pass_context
def cli(ctx, api_token):
"""LaunchDarkly project management CLI."""
ctx.obj = ProjectManager(api_token)
@cli.command()
@click.argument('name')
@click.argument('key')
@click.option('--tags', '-t', multiple=True, help='Project tags')
@click.option('--save-env/--no-save-env', default=True, help='Save SDK keys to .env')
@click.pass_obj
def create(pm, name, key, tags, save_env):
"""Create a new project."""
try:
project = pm.create_project(name, key, list(tags))
click.echo(f"✓ Created: {project['name']} ({project['key']})")
if save_env:
from launchdarkly.env_config import save_sdk_key_to_env
save_sdk_key_to_env(key, "production")
click.echo(f"✓ Saved SDK keys to .env")
except Exception as e:
click.echo(f"✗ Error: {e}", err=True)
raise click.Abort()
@cli.command()
@click.option('--format', '-f', type=click.Choice(['table', 'json', 'csv']), default='table')
@click.option('--filter-tag', help='Filter by tag')
@click.pass_obj
def list(pm, format, filter_tag):
"""List all projects."""
try:
projects = pm.list_projects()
if filter_tag:
projects = [p for p in projects if filter_tag in p.get('tags', [])]
if format == 'json':
click.echo(json.dumps(projects, indent=2))
elif format == 'csv':
click.echo("key,name,tags")
for p in projects:
tags = ','.join(p.get('tags', []))
click.echo(f"{p['key']},{p['name']},{tags}")
else: # table
rows = [[p['key'], p['name'], ', '.join(p.get('tags', []))] for p in projects]
click.echo(tabulate(rows, headers=['Key', 'Name', 'Tags']))
except Exception as e:
click.echo(f"✗ Error: {e}", err=True)
raise click.Abort()
@cli.command()
@click.argument('project_key')
@click.option('--env', default='production', help='Environment')
@click.option('--show-key/--mask-key', default=False, help='Show full key')
@click.pass_obj
def get_key(pm, project_key, env, show_key):
"""Get SDK key for a project."""
try:
sdk_key = pm.get_sdk_key(project_key, env)
if sdk_key:
if show_key:
click.echo(sdk_key)
else:
click.echo(f"{sdk_key[:10]}...{sdk_key[-4:]}")
else:
click.echo(f"✗ Environment '{env}' not found", err=True)
raise click.Abort()
except Exception as e:
click.echo(f"✗ Error: {e}", err=True)
raise click.Abort()
@cli.command()
@click.argument('source_key')
@click.argument('new_key')
@click.argument('new_name')
@click.option('--tags', '-t', multiple=True, help='Additional tags')
@click.pass_obj
def clone(pm, source_key, new_key, new_name, tags):
"""Clone an existing project."""
try:
from launchdarkly.cloning import clone_project
project = clone_project(source_key, new_name, new_key, list(tags))
click.echo(f"✓ Cloned {source_key} → {new_key}")
except Exception as e:
click.echo(f"✗ Error: {e}", err=True)
raise click.Abort()
@cli.command()
@click.argument('project_key')
@click.option('--environments', '-e', multiple=True, default=['production', 'test'])
@click.option('--output', '-o', type=click.File('w'), default='-')
@click.pass_obj
def export_keys(pm, project_key, environments, output):
"""Export SDK keys for all environments."""
try:
keys = {}
for env in environments:
sdk_key = pm.get_sdk_key(project_key, env)
if sdk_key:
keys[env] = sdk_key
output.write(json.dumps(keys, indent=2))
output.write('\n')
click.echo(f"✓ Exported keys for {project_key}", err=True)
except Exception as e:
click.echo(f"✗ Error: {e}", err=True)
raise click.Abort()
@cli.command()
@click.argument('csv_file', type=click.File('r'))
@click.option('--dry-run/--execute', default=True)
@click.pass_obj
def bulk_create(pm, csv_file, dry_run):
"""Create multiple projects from CSV file."""
import csv
reader = csv.DictReader(csv_file)
for row in reader:
key = row['key']
name = row['name']
tags = row.get('tags', '').split(',') if row.get('tags') else []
if dry_run:
click.echo(f"Would create: {key} ({name})")
else:
try:
project = pm.create_project(name, key, tags)
click.echo(f"✓ Created: {key}")
except Exception as e:
click.echo(f"✗ Failed to create {key}: {e}", err=True)
@cli.command()
@click.pass_obj
def audit(pm):
"""Audit all projects and show statistics."""
try:
projects = pm.list_projects()
# Gather statistics
total = len(projects)
tags_count = {}
for p in projects:
for tag in p.get('tags', []):
tags_count[tag] = tags_count.get(tag, 0) + 1
click.echo(f"\n📊 Project Audit Report\n")
click.echo(f"Total projects: {total}")
click.echo(f"\nTag distribution:")
for tag, count in sorted(tags_count.items(), key=lambda x: x[1], reverse=True):
click.echo(f" {tag}: {count}")
except Exception as e:
click.echo(f"✗ Error: {e}", err=True)
raise click.Abort()
if __name__ == '__main__':
cli()Install dependencies:
pip install click tabulateUsage:
# Create project
python cli/ldprojects.py create "My Agent" my-ai -t ai-configs -t production
# List projects
python cli/ldprojects.py list
python cli/ldprojects.py list --format json
python cli/ldprojects.py list --filter-tag ai-configs
# Get SDK key
python cli/ldprojects.py get-key my-ai
python cli/ldprojects.py get-key my-ai --env test --show-key
# Clone project
python cli/ldprojects.py clone template-ai new-ai "New Agent Project"
# Export keys
python cli/ldprojects.py export-keys my-ai -o keys.json
# Bulk create
python cli/ldprojects.py bulk-create projects.csv --execute
# Audit
python cli/ldprojects.py auditWeb Admin Dashboard
Build a simple web dashboard:
Flask Dashboard
# admin/app.py
from flask import Flask, render_template, request, redirect, jsonify
from launchdarkly.projects import ProjectManager
app = Flask(__name__)
pm = ProjectManager()
@app.route('/')
def index():
projects = pm.list_projects()
return render_template('index.html', projects=projects)
@app.route('/projects', methods=['POST'])
def create_project():
data = request.json
project = pm.create_project(
name=data['name'],
key=data['key'],
tags=data.get('tags', [])
)
return jsonify(project)
@app.route('/projects/<key>')
def project_detail(key):
project = pm.get_project(key)
return render_template('project.html', project=project)
@app.route('/projects/<key>/keys/<env>')
def get_sdk_key(key, env):
sdk_key = pm.get_sdk_key(key, env)
return jsonify({'sdkKey': sdk_key})
if __name__ == '__main__':
app.run(debug=True, port=5000)templates/index.html:
<!DOCTYPE html>
<html>
<head>
<title>LaunchDarkly Projects Admin</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.project { border: 1px solid #ddd; padding: 15px; margin: 10px 0; }
.tags { color: #666; font-size: 0.9em; }
</style>
</head>
<body>
<h1>LaunchDarkly Projects</h1>
<h2>Create New Project</h2>
<form id="createForm">
<input type="text" name="name" placeholder="Project Name" required>
<input type="text" name="key" placeholder="project-key" required>
<input type="text" name="tags" placeholder="tag1,tag2">
<button type="submit">Create</button>
</form>
<h2>Existing Projects</h2>
{% for project in projects %}
<div class="project">
<h3><a href="/projects/{{ project.key }}">{{ project.name }}</a></h3>
<p><strong>Key:</strong> {{ project.key }}</p>
<p class="tags"><strong>Tags:</strong> {{ project.tags|join(', ') }}</p>
</div>
{% endfor %}
<script>
document.getElementById('createForm').onsubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = {
name: formData.get('name'),
key: formData.get('key'),
tags: formData.get('tags').split(',').filter(t => t.trim())
};
const resp = await fetch('/projects', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
if (resp.ok) {
location.reload();
} else {
alert('Failed to create project');
}
};
</script>
</body>
</html>Run the dashboard:
pip install flask
python admin/app.py
# Visit http://localhost:5000Monitoring & Alerting
Monitor project creation and usage:
# monitoring/project_monitor.py
import time
from datetime import datetime
from launchdarkly.projects import ProjectManager
class ProjectMonitor:
"""Monitor LaunchDarkly projects for changes."""
def __init__(self):
self.pm = ProjectManager()
self.known_projects = set()
def check_for_new_projects(self):
"""Check for newly created projects."""
projects = self.pm.list_projects()
current_keys = {p['key'] for p in projects}
new_projects = current_keys - self.known_projects
if new_projects:
self.on_new_projects(new_projects, projects)
self.known_projects = current_keys
def on_new_projects(self, new_keys, all_projects):
"""Handle new projects."""
for key in new_keys:
project = next(p for p in all_projects if p['key'] == key)
print(f"[{datetime.now()}] New project detected: {project['name']} ({key})")
# Send alert, log to DB, etc.
def run(self, interval=60):
"""Run monitor continuously."""
print(f"Starting project monitor (interval: {interval}s)")
while True:
try:
self.check_for_new_projects()
except Exception as e:
print(f"Error: {e}")
time.sleep(interval)
if __name__ == '__main__':
monitor = ProjectMonitor()
monitor.run()Backup & Recovery
Backup project configurations:
# backup/project_backup.py
import json
from datetime import datetime
from launchdarkly.projects import ProjectManager
def backup_all_projects(output_file=None):
"""Backup all projects to JSON file."""
pm = ProjectManager()
projects = pm.list_projects()
if not output_file:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = f"projects_backup_{timestamp}.json"
with open(output_file, 'w') as f:
json.dump(projects, f, indent=2)
print(f"✓ Backed up {len(projects)} projects to {output_file}")
return output_file
def restore_projects(backup_file):
"""Restore projects from backup (creates if missing)."""
pm = ProjectManager()
with open(backup_file, 'r') as f:
projects = json.load(f)
for project in projects:
try:
pm.create_project(
name=project['name'],
key=project['key'],
tags=project.get('tags', [])
)
print(f"✓ Restored: {project['key']}")
except Exception as e:
print(f"✗ Failed to restore {project['key']}: {e}")
# Usage
backup_all_projects()
# restore_projects('projects_backup_20260205_120000.json')Integration with Terraform
Export projects to Terraform format:
# terraform/export_terraform.py
def export_to_terraform(project_keys=None):
"""Export projects as Terraform configuration."""
pm = ProjectManager()
projects = pm.list_projects()
if project_keys:
projects = [p for p in projects if p['key'] in project_keys]
tf_config = []
for project in projects:
resource_name = project['key'].replace('-', '_')
tags = ', '.join(f'"{tag}"' for tag in project.get('tags', []))
tf_config.append(f'''
resource "launchdarkly_project" "{resource_name}" {{
key = "{project['key']}"
name = "{project['name']}"
tags = [{tags}]
}}
''')
output = '\n'.join(tf_config)
with open('projects.tf', 'w') as f:
f.write(output)
print(f"✓ Exported {len(projects)} projects to projects.tf")
# Usage
export_to_terraform()Slack Integration
Send notifications to Slack:
# integrations/slack_notifier.py
import requests
def notify_slack(webhook_url, message):
"""Send notification to Slack."""
requests.post(webhook_url, json={'text': message})
def notify_project_created(project, webhook_url):
"""Notify Slack when project is created."""
message = f"🎉 New LaunchDarkly project created: *{project['name']}* (`{project['key']}`)"
notify_slack(webhook_url, message)
# Usage in CLI
@cli.command()
@click.argument('name')
@click.argument('key')
@click.option('--slack-webhook', envvar='SLACK_WEBHOOK_URL')
@click.pass_obj
def create_with_notification(pm, name, key, slack_webhook):
"""Create project and notify Slack."""
project = pm.create_project(name, key, [])
click.echo(f"✓ Created: {project['name']}")
if slack_webhook:
notify_project_created(project, slack_webhook)
click.echo("✓ Notified Slack")Next Steps
- Set up IaC automation
- Configure project cloning
- Manage SDK keys
Environment Configuration
Patterns for saving SDK keys to your codebase's configuration system.
Overview
After creating a project, you need to save the SDK keys so your application can use them. The approach depends on your existing configuration pattern.
Common Patterns
1. .env Files
Most common pattern for local development and simple deployments.
Python
def save_sdk_key_to_env(
project_key: str,
environment: str = "production",
env_file: str = ".env",
var_name: str = "LAUNCHDARKLY_SDK_KEY"
):
"""Save SDK key to .env file."""
# Get the SDK key
pm = ProjectManager()
sdk_key = pm.get_sdk_key(project_key, environment)
if not sdk_key:
raise ValueError(f"Could not get SDK key for {project_key}/{environment}")
# Read existing .env content
env_content = {}
if os.path.exists(env_file):
with open(env_file, "r") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
env_content[key] = value
# Update or add the SDK key
env_content[var_name] = sdk_key
# Write back to .env
with open(env_file, "w") as f:
for key, value in env_content.items():
f.write(f"{key}={value}\n")
print(f"✓ Saved {var_name} to {env_file}")Node.js/TypeScript
import * as fs from 'fs';
import * as path from 'path';
async function saveSdkKeyToEnv(
projectKey: string,
environment: string = 'production',
envFile: string = '.env',
varName: string = 'LAUNCHDARKLY_SDK_KEY'
): Promise<void> {
const pm = new ProjectManager();
const sdkKey = await pm.getSdkKey(projectKey, environment);
if (!sdkKey) {
throw new Error(`Could not get SDK key for ${projectKey}/${environment}`);
}
// Read existing .env content
const envContent: Record<string, string> = {};
if (fs.existsSync(envFile)) {
const content = fs.readFileSync(envFile, 'utf-8');
content.split('\n').forEach((line) => {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {
const [key, ...valueParts] = trimmed.split('=');
envContent[key] = valueParts.join('=');
}
});
}
// Update or add the SDK key
envContent[varName] = sdkKey;
// Write back to .env
const lines = Object.entries(envContent).map(([key, value]) => `${key}=${value}`);
fs.writeFileSync(envFile, lines.join('\n') + '\n');
console.log(`✓ Saved ${varName} to ${envFile}`);
}Usage
# Python
python -c "from launchdarkly.projects import save_sdk_key_to_env; save_sdk_key_to_env('my-project')"
# Node.js
node -e "require('./src/launchdarkly/env-config').saveSdkKeyToEnv('my-project')"2. Multiple Environments
Save keys for multiple environments:
# Save both production and test keys
save_sdk_key_to_env("my-project", "production", var_name="LD_SDK_KEY_PROD")
save_sdk_key_to_env("my-project", "test", var_name="LD_SDK_KEY_TEST").env result:
LD_SDK_KEY_PROD=sdk-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
LD_SDK_KEY_TEST=sdk-yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy3. Secrets Manager Integration
For cloud deployments, integrate with secrets managers.
AWS Secrets Manager
import boto3
import json
def save_to_aws_secrets(project_key: str, environment: str, secret_name: str):
"""Save SDK key to AWS Secrets Manager."""
pm = ProjectManager()
sdk_key = pm.get_sdk_key(project_key, environment)
client = boto3.client('secretsmanager')
try:
# Get existing secret
response = client.get_secret_value(SecretId=secret_name)
secrets = json.loads(response['SecretString'])
except client.exceptions.ResourceNotFoundException:
secrets = {}
# Update with new key
secrets['LAUNCHDARKLY_SDK_KEY'] = sdk_key
# Save back
client.put_secret_value(
SecretId=secret_name,
SecretString=json.dumps(secrets)
)
print(f"✓ Saved SDK key to AWS Secrets Manager: {secret_name}")
# Usage
save_to_aws_secrets("my-project", "production", "myapp/production")GCP Secret Manager
from google.cloud import secretmanager
def save_to_gcp_secrets(project_key: str, environment: str, secret_id: str, gcp_project: str):
"""Save SDK key to GCP Secret Manager."""
pm = ProjectManager()
sdk_key = pm.get_sdk_key(project_key, environment)
client = secretmanager.SecretManagerServiceClient()
parent = f"projects/{gcp_project}/secrets/{secret_id}"
# Add new version
response = client.add_secret_version(
request={
"parent": parent,
"payload": {"data": sdk_key.encode("UTF-8")},
}
)
print(f"✓ Saved SDK key to GCP Secret Manager: {response.name}")Azure Key Vault
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
def save_to_azure_keyvault(project_key: str, environment: str, vault_url: str, secret_name: str):
"""Save SDK key to Azure Key Vault."""
pm = ProjectManager()
sdk_key = pm.get_sdk_key(project_key, environment)
credential = DefaultAzureCredential()
client = SecretClient(vault_url=vault_url, credential=credential)
client.set_secret(secret_name, sdk_key)
print(f"✓ Saved SDK key to Azure Key Vault: {secret_name}")4. Kubernetes Secrets
For Kubernetes deployments:
import base64
import yaml
def create_k8s_secret(project_key: str, environment: str, namespace: str = "default"):
"""Generate Kubernetes secret manifest."""
pm = ProjectManager()
sdk_key = pm.get_sdk_key(project_key, environment)
# Encode SDK key
encoded_key = base64.b64encode(sdk_key.encode()).decode()
secret = {
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"name": "launchdarkly-sdk-key",
"namespace": namespace
},
"type": "Opaque",
"data": {
"sdk-key": encoded_key
}
}
# Write to file
with open("k8s-secret.yaml", "w") as f:
yaml.dump(secret, f)
print("✓ Created k8s-secret.yaml")
print("Apply with: kubectl apply -f k8s-secret.yaml")5. Configuration Files
For applications using config files (YAML, JSON, TOML):
YAML Config
import yaml
def save_to_yaml_config(project_key: str, environment: str, config_file: str = "config.yaml"):
"""Save SDK key to YAML config file."""
pm = ProjectManager()
sdk_key = pm.get_sdk_key(project_key, environment)
# Read existing config
config = {}
if os.path.exists(config_file):
with open(config_file, "r") as f:
config = yaml.safe_load(f) or {}
# Update LaunchDarkly section
if "launchdarkly" not in config:
config["launchdarkly"] = {}
config["launchdarkly"]["sdk_key"] = sdk_key
config["launchdarkly"]["project_key"] = project_key
config["launchdarkly"]["environment"] = environment
# Write back
with open(config_file, "w") as f:
yaml.dump(config, f, default_flow_style=False)
print(f"✓ Saved SDK key to {config_file}")JSON Config
import * as fs from 'fs';
async function saveToJsonConfig(
projectKey: string,
environment: string,
configFile: string = 'config.json'
): Promise<void> {
const pm = new ProjectManager();
const sdkKey = await pm.getSdkKey(projectKey, environment);
// Read existing config
let config: any = {};
if (fs.existsSync(configFile)) {
config = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
}
// Update LaunchDarkly section
config.launchdarkly = {
sdkKey,
projectKey,
environment,
};
// Write back
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
console.log(`✓ Saved SDK key to ${configFile}`);
}Security Best Practices
1. Never Commit SDK Keys
Add to .gitignore:
# Environment files
.env
.env.local
.env.production
.env.test
# Config files with secrets
config/secrets.yaml
config/production.json2. Use Different Keys Per Environment
# Development
save_sdk_key_to_env("my-project", "test", ".env.development")
# Production (deploy separately)
save_sdk_key_to_env("my-project", "production", ".env.production")3. Rotate Keys Regularly
def rotate_sdk_key(project_key: str, environment: str):
"""
Note: This requires creating a new SDK key via API.
The LaunchDarkly API doesn't support key rotation directly.
You would need to create a new environment or reset the key in the UI.
"""
print("⚠️ SDK key rotation must be done via LaunchDarkly UI")
print(f" Go to: Project Settings → Environments → {environment} → Reset SDK Key")4. Least Privilege Access
- API tokens for project creation:
projects:write - Application SDK keys: read-only by default
- Separate keys for test vs production
Verification
After saving SDK keys, verify they work:
def verify_sdk_key(sdk_key: str):
"""Verify SDK key works by testing connection."""
import ldclient
from ldclient.config import Config
config = Config(sdk_key)
client = ldclient.get()
if client.is_initialized():
print("✓ SDK key is valid and working")
return True
else:
print("✗ SDK key failed to initialize")
return FalseNext Steps
- Integrate SDK in your application
- Set up project cloning
- Build automation scripts
Go Project Setup
Implementation patterns for Go applications using the LaunchDarkly API.
Prerequisites
go get github.com/joho/godotenvBasic Project Manager
Create a package for project operations:
// pkg/launchdarkly/projects.go
package launchdarkly
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
const BaseURL = "https://app.launchdarkly.com/api/v2"
type ProjectManager struct {
apiToken string
client *http.Client
}
type Project struct {
Name string `json:"name"`
Key string `json:"key"`
Tags []string `json:"tags,omitempty"`
Environments *Environments `json:"environments,omitempty"`
}
type Environments struct {
Items []Environment `json:"items"`
}
type Environment struct {
Name string `json:"name"`
Key string `json:"key"`
APIKey string `json:"apiKey"`
}
func NewProjectManager(apiToken string) *ProjectManager {
if apiToken == "" {
apiToken = os.Getenv("LAUNCHDARKLY_API_TOKEN")
}
return &ProjectManager{
apiToken: apiToken,
client: &http.Client{},
}
}
func (pm *ProjectManager) CreateProject(name, key string, tags []string) (*Project, error) {
payload := map[string]interface{}{
"name": name,
"key": key,
}
if tags != nil {
payload["tags"] = tags
}
body, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("marshal payload: %w", err)
}
req, err := http.NewRequest("POST", fmt.Sprintf("%s/projects", BaseURL), bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Authorization", pm.apiToken)
req.Header.Set("Content-Type", "application/json")
resp, err := pm.client.Do(req)
if err != nil {
return nil, fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusConflict {
// Project exists, fetch it
return pm.GetProject(key)
}
if resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, body)
}
var project Project
if err := json.NewDecoder(resp.Body).Decode(&project); err != nil {
return nil, fmt.Errorf("decode response: %w", err)
}
return &project, nil
}
func (pm *ProjectManager) GetProject(projectKey string) (*Project, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/projects/%s?expand=environments", BaseURL, projectKey), nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Authorization", pm.apiToken)
resp, err := pm.client.Do(req)
if err != nil {
return nil, fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("project not found: %d", resp.StatusCode)
}
var project Project
if err := json.NewDecoder(resp.Body).Decode(&project); err != nil {
return nil, fmt.Errorf("decode response: %w", err)
}
return &project, nil
}
func (pm *ProjectManager) GetSDKKey(projectKey, environment string) (string, error) {
project, err := pm.GetProject(projectKey)
if err != nil {
return "", err
}
if project.Environments == nil {
return "", fmt.Errorf("no environments found")
}
for _, env := range project.Environments.Items {
if env.Key == environment {
return env.APIKey, nil
}
}
return "", fmt.Errorf("environment '%s' not found", environment)
}
func (pm *ProjectManager) ListProjects() ([]Project, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/projects", BaseURL), nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Authorization", pm.apiToken)
resp, err := pm.client.Do(req)
if err != nil {
return nil, fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to list projects: %d", resp.StatusCode)
}
var result struct {
Items []Project `json:"items"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("decode response: %w", err)
}
return result.Items, nil
}Usage Examples
Create a Project
package main
import (
"fmt"
"log"
"yourmodule/pkg/launchdarkly"
)
func main() {
pm := launchdarkly.NewProjectManager("")
project, err := pm.CreateProject(
"Customer Agent Service",
"customer-ai",
[]string{"ai-configs", "production"},
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("✓ Created project: %s (%s)\n", project.Name, project.Key)
}Get SDK Key
func main() {
pm := launchdarkly.NewProjectManager("")
// Get production SDK key
sdkKey, err := pm.GetSDKKey("customer-ai", "production")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Production SDK Key: %s\n", sdkKey)
}List Projects
func main() {
pm := launchdarkly.NewProjectManager("")
projects, err := pm.ListProjects()
if err != nil {
log.Fatal(err)
}
fmt.Println("Projects:")
for _, project := range projects {
fmt.Printf(" - %s (%s)\n", project.Name, project.Key)
}
}HTTP Server Integration
Integrate into an HTTP server:
// cmd/server/main.go
package main
import (
"log"
"net/http"
"os"
"yourmodule/pkg/launchdarkly"
)
func main() {
// Initialize LaunchDarkly project
pm := launchdarkly.NewProjectManager(os.Getenv("LAUNCHDARKLY_API_TOKEN"))
project, err := pm.CreateProject(
"Go API Service",
"go-api-service",
[]string{"api", "ai-configs"},
)
if err != nil {
log.Fatalf("Failed to initialize LaunchDarkly: %v", err)
}
// Get SDK key
sdkKey, err := pm.GetSDKKey("go-api-service", "production")
if err != nil {
log.Fatalf("Failed to get SDK key: %v", err)
}
// Store SDK key for SDK initialization
os.Setenv("LAUNCHDARKLY_SDK_KEY", sdkKey)
log.Printf("✓ LaunchDarkly project ready: %s\n", project.Key)
// Start server
http.HandleFunc("/health", healthHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}CLI Tool
Create a CLI for project management:
// cmd/ldprojects/main.go
package main
import (
"flag"
"fmt"
"log"
"os"
"strings"
"yourmodule/pkg/launchdarkly"
)
func main() {
createCmd := flag.NewFlagSet("create", flag.ExitOnError)
createName := createCmd.String("name", "", "Project name")
createKey := createCmd.String("key", "", "Project key")
createTags := createCmd.String("tags", "", "Comma-separated tags")
listCmd := flag.NewFlagSet("list", flag.ExitOnError)
getKeyCmd := flag.NewFlagSet("get-key", flag.ExitOnError)
getKeyProject := getKeyCmd.String("project", "", "Project key")
getKeyEnv := getKeyCmd.String("env", "production", "Environment")
if len(os.Args) < 2 {
fmt.Println("Usage: ldprojects [create|list|get-key] [options]")
os.Exit(1)
}
pm := launchdarkly.NewProjectManager("")
switch os.Args[1] {
case "create":
createCmd.Parse(os.Args[2:])
if *createName == "" || *createKey == "" {
log.Fatal("name and key are required")
}
var tags []string
if *createTags != "" {
tags = strings.Split(*createTags, ",")
}
project, err := pm.CreateProject(*createName, *createKey, tags)
if err != nil {
log.Fatal(err)
}
fmt.Printf("✓ Created: %s (%s)\n", project.Name, project.Key)
case "list":
listCmd.Parse(os.Args[2:])
projects, err := pm.ListProjects()
if err != nil {
log.Fatal(err)
}
fmt.Println("Projects:")
for _, project := range projects {
fmt.Printf(" - %s (%s)\n", project.Name, project.Key)
}
case "get-key":
getKeyCmd.Parse(os.Args[2:])
if *getKeyProject == "" {
log.Fatal("project is required")
}
sdkKey, err := pm.GetSDKKey(*getKeyProject, *getKeyEnv)
if err != nil {
log.Fatal(err)
}
fmt.Println(sdkKey)
default:
fmt.Println("Unknown command:", os.Args[1])
os.Exit(1)
}
}Usage:
go run cmd/ldprojects/main.go create -name "My Agent" -key my-ai -tags ai-configs,production
go run cmd/ldprojects/main.go list
go run cmd/ldprojects/main.go get-key -project my-ai -env productionError Handling
Add comprehensive error handling:
type LaunchDarklyError struct {
StatusCode int
Message string
}
func (e *LaunchDarklyError) Error() string {
return fmt.Sprintf("LaunchDarkly API error (%d): %s", e.StatusCode, e.Message)
}
func (pm *ProjectManager) CreateProject(name, key string, tags []string) (*Project, error) {
// ... request setup ...
resp, err := pm.client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusCreated:
var project Project
if err := json.NewDecoder(resp.Body).Decode(&project); err != nil {
return nil, fmt.Errorf("decode response: %w", err)
}
return &project, nil
case http.StatusConflict:
return pm.GetProject(key)
case http.StatusUnauthorized:
return nil, &LaunchDarklyError{resp.StatusCode, "Invalid API token"}
case http.StatusForbidden:
return nil, &LaunchDarklyError{resp.StatusCode, "Insufficient permissions (need projects:write)"}
default:
body, _ := io.ReadAll(resp.Body)
return nil, &LaunchDarklyError{resp.StatusCode, string(body)}
}
}Testing
Mock the HTTP client for testing:
// pkg/launchdarkly/projects_test.go
package launchdarkly
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestCreateProject(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("Expected POST, got %s", r.Method)
}
w.WriteHeader(http.StatusCreated)
w.Write([]byte(`{"name":"Test","key":"test","tags":[]}`))
}))
defer server.Close()
pm := &ProjectManager{
apiToken: "test-token",
client: server.Client(),
}
// Override BaseURL for test
oldBaseURL := BaseURL
BaseURL = server.URL
defer func() { BaseURL = oldBaseURL }()
project, err := pm.CreateProject("Test", "test", nil)
if err != nil {
t.Fatalf("CreateProject failed: %v", err)
}
if project.Key != "test" {
t.Errorf("Expected key 'test', got '%s'", project.Key)
}
}Next Steps
- Save SDK keys to .env
- Set up project cloning
- Build automation tools
Infrastructure as Code (IaC) Automation
Automate project management using IaC tools and CI/CD pipelines.
Terraform
LaunchDarkly Terraform Provider
Install and configure the LaunchDarkly Terraform provider:
# terraform/main.tf
terraform {
required_providers {
launchdarkly = {
source = "launchdarkly/launchdarkly"
version = "~> 2.0"
}
}
}
provider "launchdarkly" {
access_token = var.launchdarkly_access_token
}Define Projects
# terraform/projects.tf
variable "launchdarkly_access_token" {
description = "LaunchDarkly API access token"
type = string
sensitive = true
}
resource "launchdarkly_project" "customer_ai" {
key = "customer-ai"
name = "Customer Agent Service"
tags = ["ai-configs", "production", "terraform"]
}
resource "launchdarkly_project" "platform_ai" {
key = "platform-ai"
name = "Platform Agent Service"
tags = ["ai-configs", "production", "terraform"]
}
# Output SDK keys
output "customer_ai_sdk_key_production" {
value = launchdarkly_project.customer_ai.environments[0].api_key
sensitive = true
}
output "customer_ai_sdk_key_test" {
value = launchdarkly_project.customer_ai.environments[1].api_key
sensitive = true
}Custom Environments
resource "launchdarkly_project" "my_project" {
key = "my-project"
name = "My Project"
environments = [
{
key = "production"
name = "Production"
color = "FF0000"
},
{
key = "staging"
name = "Staging"
color = "FFA500"
},
{
key = "development"
name = "Development"
color = "00FF00"
}
]
}Apply Terraform
# Initialize
terraform init
# Plan changes
terraform plan -var="launchdarkly_access_token=$LAUNCHDARKLY_API_TOKEN"
# Apply
terraform apply -var="launchdarkly_access_token=$LAUNCHDARKLY_API_TOKEN"
# Get SDK key
terraform output -raw customer_ai_sdk_key_productionSave SDK Keys to Files
# Save SDK keys to local files (for development only)
resource "local_file" "sdk_key_production" {
content = launchdarkly_project.customer_ai.environments[0].api_key
filename = "${path.module}/.env.production"
# Don't commit these files!
provisioner "local-exec" {
command = "echo '.env.production' >> .gitignore"
}
}GitHub Actions
Automate project creation in CI/CD:
Create Project on Deploy
# .github/workflows/setup-launchdarkly.yml
name: Setup LaunchDarkly Project
on:
workflow_dispatch:
inputs:
project_key:
description: 'Project key'
required: true
project_name:
description: 'Project name'
required: true
jobs:
setup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install requests python-dotenv
- name: Create LaunchDarkly Project
env:
LAUNCHDARKLY_API_TOKEN: ${{ secrets.LAUNCHDARKLY_API_TOKEN }}
run: |
python scripts/create_project.py \
--name "${{ github.event.inputs.project_name }}" \
--key "${{ github.event.inputs.project_key }}" \
--tags "github-actions,automated"
- name: Save SDK Keys to Secrets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PROJECT_KEY: ${{ github.event.inputs.project_key }}
run: |
SDK_KEY=$(python scripts/get_sdk_key.py $PROJECT_KEY production)
gh secret set LAUNCHDARKLY_SDK_KEY --body "$SDK_KEY"Automated Project Creation on New Service
# .github/workflows/new-service.yml
name: New Service Setup
on:
create:
branches:
- 'service/*'
jobs:
setup-project:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Extract service name
id: service
run: |
BRANCH_NAME="${{ github.ref }}"
SERVICE_NAME="${BRANCH_NAME#refs/heads/service/}"
echo "name=$SERVICE_NAME" >> $GITHUB_OUTPUT
echo "key=${SERVICE_NAME//_/-}" >> $GITHUB_OUTPUT
- name: Create LaunchDarkly Project
env:
LAUNCHDARKLY_API_TOKEN: ${{ secrets.LAUNCHDARKLY_API_TOKEN }}
run: |
python scripts/create_project.py \
--name "${{ steps.service.outputs.name }} Service" \
--key "${{ steps.service.outputs.key }}-service" \
--tags "service,automated"GitLab CI
# .gitlab-ci.yml
stages:
- setup
- deploy
setup-launchdarkly:
stage: setup
image: python:3.11
script:
- pip install requests
- |
python -c "
from launchdarkly.projects import ProjectManager
pm = ProjectManager('$LAUNCHDARKLY_API_TOKEN')
project = pm.create_project(
name='$CI_PROJECT_NAME',
key='$CI_PROJECT_NAME',
tags=['gitlab-ci', '$CI_ENVIRONMENT_NAME']
)
sdk_key = pm.get_sdk_key('$CI_PROJECT_NAME', 'production')
print(f'SDK_KEY={sdk_key}')
" > .env.production
artifacts:
paths:
- .env.production
expire_in: 1 day
only:
- mainCircleCI
# .circleci/config.yml
version: 2.1
jobs:
setup-launchdarkly:
docker:
- image: cimg/python:3.11
steps:
- checkout
- run:
name: Install dependencies
command: pip install requests
- run:
name: Create LaunchDarkly project
command: |
python scripts/create_project.py \
--name "$CIRCLE_PROJECT_REPONAME" \
--key "$CIRCLE_PROJECT_REPONAME" \
--tags "circleci,automated"
- run:
name: Save SDK key
command: |
SDK_KEY=$(python scripts/get_sdk_key.py $CIRCLE_PROJECT_REPONAME production)
echo "export LAUNCHDARKLY_SDK_KEY='$SDK_KEY'" >> $BASH_ENV
workflows:
setup:
jobs:
- setup-launchdarklyAnsible
Manage projects with Ansible:
# playbooks/setup-launchdarkly.yml
---
- name: Setup LaunchDarkly Projects
hosts: localhost
vars:
launchdarkly_api_token: "{{ lookup('env', 'LAUNCHDARKLY_API_TOKEN') }}"
projects:
- name: "Customer Agent Service"
key: "customer-ai"
tags: ["ai-configs", "production"]
- name: "Platform Agent Service"
key: "platform-ai"
tags: ["ai-configs", "production"]
tasks:
- name: Create LaunchDarkly projects
uri:
url: "https://app.launchdarkly.com/api/v2/projects"
method: POST
headers:
Authorization: "{{ launchdarkly_api_token }}"
Content-Type: "application/json"
body_format: json
body:
name: "{{ item.name }}"
key: "{{ item.key }}"
tags: "{{ item.tags }}"
status_code: [201, 409]
loop: "{{ projects }}"
register: project_results
- name: Get SDK keys
uri:
url: "https://app.launchdarkly.com/api/v2/projects/{{ item.key }}?expand=environments"
method: GET
headers:
Authorization: "{{ launchdarkly_api_token }}"
loop: "{{ projects }}"
register: sdk_keys
- name: Save SDK keys to .env
copy:
content: |
LAUNCHDARKLY_SDK_KEY={{ item.json.environments.items[0].apiKey }}
dest: ".env.{{ item.item.key }}"
loop: "{{ sdk_keys.results }}"
no_log: trueRun playbook:
ansible-playbook playbooks/setup-launchdarkly.ymlPulumi
Infrastructure as code with Pulumi:
Python
# __main__.py
import pulumi
import pulumi_launchdarkly as launchdarkly
# Create projects
customer_ai = launchdarkly.Project(
"customer-ai",
key="customer-ai",
name="Customer Agent Service",
tags=["ai-configs", "production", "pulumi"]
)
platform_ai = launchdarkly.Project(
"platform-ai",
key="platform-ai",
name="Platform Agent Service",
tags=["ai-configs", "production", "pulumi"]
)
# Export SDK keys
pulumi.export("customer_ai_sdk_key_prod", customer_ai.environments[0]["api_key"])
pulumi.export("customer_ai_sdk_key_test", customer_ai.environments[1]["api_key"])TypeScript
// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as launchdarkly from "@pulumi/launchdarkly";
// Create projects
const customerAi = new launchdarkly.Project("customer-ai", {
key: "customer-ai",
name: "Customer Agent Service",
tags: ["ai-configs", "production", "pulumi"],
});
const platformAi = new launchdarkly.Project("platform-ai", {
key: "platform-ai",
name: "Platform Agent Service",
tags: ["ai-configs", "production", "pulumi"],
});
// Export SDK keys
export const customerAiSdkKeyProd = customerAi.environments[0].apiKey;
export const customerAiSdkKeyTest = customerAi.environments[1].apiKey;Deploy:
pulumi up
pulumi stack output customerAiSdkKeyProdDocker Compose
Initialize projects in Docker setup:
# docker-compose.yml
version: '3.8'
services:
setup-launchdarkly:
image: python:3.11-slim
environment:
- LAUNCHDARKLY_API_TOKEN=${LAUNCHDARKLY_API_TOKEN}
volumes:
- ./scripts:/scripts
- ./.env.production:/output/.env
command: >
sh -c "
pip install requests &&
python /scripts/create_project.py --name 'My Service' --key my-service &&
python /scripts/save_sdk_key.py my-service production > /output/.env
"
app:
build: .
depends_on:
- setup-launchdarkly
env_file:
- .env.productionKubernetes Operator
Custom operator to manage projects:
# k8s/launchdarkly-project.yaml
apiVersion: launchdarkly.com/v1
kind: Project
metadata:
name: customer-ai
spec:
key: customer-ai
name: Customer Agent Service
tags:
- ai-configs
- production
- kubernetes
secretName: launchdarkly-sdk-keysOperator implementation (Python):
# operator/controller.py
import kopf
from launchdarkly.projects import ProjectManager
from kubernetes import client, config
@kopf.on.create('launchdarkly.com', 'v1', 'projects')
def create_project(spec, name, namespace, **kwargs):
"""Handle Project creation."""
pm = ProjectManager()
# Create project
project = pm.create_project(
name=spec['name'],
key=spec['key'],
tags=spec.get('tags', [])
)
# Get SDK keys
sdk_key_prod = pm.get_sdk_key(spec['key'], 'production')
sdk_key_test = pm.get_sdk_key(spec['key'], 'test')
# Create Kubernetes Secret
config.load_incluster_config()
v1 = client.CoreV1Api()
secret = client.V1Secret(
metadata=client.V1ObjectMeta(
name=spec.get('secretName', f"{name}-sdk-keys"),
namespace=namespace
),
string_data={
'sdk-key-production': sdk_key_prod,
'sdk-key-test': sdk_key_test
}
)
v1.create_namespaced_secret(namespace, secret)
return {'message': f'Created project {spec["key"]}'}Make/Taskfile
Simple automation with Make:
# Makefile
.PHONY: create-project list-projects get-key
create-project:
@python scripts/create_project.py \
--name "$(NAME)" \
--key "$(KEY)" \
--tags "$(TAGS)"
list-projects:
@python scripts/list_projects.py
get-key:
@python scripts/get_sdk_key.py $(PROJECT) $(ENV)
setup-env:
@python scripts/save_sdk_key.py $(PROJECT) production > .env.production
@echo "✓ Saved SDK key to .env.production"Usage:
make create-project NAME="My Agent" KEY=my-ai TAGS=ai-configs,production
make list-projects
make get-key PROJECT=my-ai ENV=production
make setup-env PROJECT=my-aiNext Steps
- Build admin tooling
- Configure project cloning
- Manage environment configuration
Multi-Language Setup
Guidance for polyglot architectures with multiple languages/frameworks.
Overview
In a microservices or polyglot architecture, you may have services in different languages that need to share LaunchDarkly projects or maintain separate projects per service.
Architecture Patterns
Pattern 1: Shared Project, Multiple Services
One project, different services consume from different environments or contexts.
When to use:
- All services are part of the same application
- Want centralized config management
- Services share the same configs
Project: "my-app"
├── Service A (Python) → Uses "my-app" production SDK key
├── Service B (Node.js) → Uses "my-app" production SDK key
└── Service C (Go) → Uses "my-app" production SDK keyPattern 2: Project Per Service
Each service has its own project.
When to use:
- Services are independent
- Different teams own different services
- Need isolation between services
Project: "service-a" → Service A (Python)
Project: "service-b" → Service B (Node.js)
Project: "service-c" → Service C (Go)Pattern 3: Hybrid
Shared projects for related services, separate for others.
Project: "frontend-services"
├── Web App (React)
└── Mobile App (React Native)
Project: "backend-services"
├── API Gateway (Node.js)
└── Auth Service (Go)
Project: "ml-services"
├── Recommendation Engine (Python)
└── Model Serving (Python)Centralized Project Management
Create a central service for project management that other services can use:
Project Management API
# project-manager-service/app.py
from flask import Flask, jsonify, request
from launchdarkly.projects import ProjectManager
app = Flask(__name__)
pm = ProjectManager()
@app.route('/projects', methods=['POST'])
def create_project():
"""Central API to create projects for any service."""
data = request.json
project = pm.create_project(
name=data['name'],
key=data['key'],
tags=data.get('tags', [])
)
return jsonify(project)
@app.route('/projects/<key>/keys/<env>')
def get_sdk_key(key, env):
"""Get SDK key for any service."""
sdk_key = pm.get_sdk_key(key, env)
return jsonify({'sdkKey': sdk_key})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)Service Consumption
Each service calls the central API:
Python Service:
import requests
def get_launchdarkly_sdk_key():
resp = requests.get(
'http://project-manager:8080/projects/my-service/keys/production'
)
return resp.json()['sdkKey']Node.js Service:
async function getLaunchDarklySdkKey(): Promise<string> {
const resp = await fetch(
'http://project-manager:8080/projects/my-service/keys/production'
);
const data = await resp.json();
return data.sdkKey;
}Go Service:
func getLaunchDarklySdkKey() (string, error) {
resp, err := http.Get("http://project-manager:8080/projects/my-service/keys/production")
if err != nil {
return "", err
}
defer resp.Body.Close()
var result struct {
SdkKey string `json:"sdkKey"`
}
json.NewDecoder(resp.Body).Decode(&result)
return result.SdkKey, nil
}Shared Configuration Repository
Maintain a central config repo that all services reference:
config-repo/
├── launchdarkly/
│ ├── projects.yaml # Project definitions
│ ├── sdk-keys/
│ │ ├── production/
│ │ │ ├── service-a.key
│ │ │ ├── service-b.key
│ │ │ └── service-c.key
│ │ └── test/
│ │ ├── service-a.key
│ │ ├── service-b.key
│ │ └── service-c.key
│ └── scripts/
│ ├── create_projects.py
│ └── sync_keys.shprojects.yaml:
projects:
- name: Service A
key: service-a
tags: [python, backend]
services:
- name: service-a
language: python
- name: Service B
key: service-b
tags: [nodejs, api]
services:
- name: service-b
language: nodejs
- name: Service C
key: service-c
tags: [go, gateway]
services:
- name: service-c
language: goSync script:
#!/bin/bash
# scripts/sync_keys.sh
for env in production test; do
for service in service-a service-b service-c; do
sdk_key=$(python scripts/get_sdk_key.py $service $env)
echo "$sdk_key" > "sdk-keys/$env/$service.key"
echo "✓ Synced $service/$env"
done
doneService Templates
Create templates for each language:
Python Template
# templates/python/launchdarkly_setup.py
import os
from launchdarkly.projects import ProjectManager
def setup_project(service_name: str):
"""Setup LaunchDarkly project for Python service."""
pm = ProjectManager()
project = pm.create_project(
name=f"{service_name} Service",
key=service_name,
tags=["python", "service"]
)
sdk_key = pm.get_sdk_key(service_name, "production")
# Save to .env
with open(".env", "w") as f:
f.write(f"LAUNCHDARKLY_SDK_KEY={sdk_key}\n")
print(f"✓ Setup complete for {service_name}")Node.js Template
// templates/nodejs/launchdarkly-setup.ts
import { ProjectManager } from './launchdarkly/projects';
import * as fs from 'fs';
async function setupProject(serviceName: string): Promise<void> {
const pm = new ProjectManager();
const project = await pm.createProject({
name: `${serviceName} Service`,
key: serviceName,
tags: ['nodejs', 'service'],
});
const sdkKey = await pm.getSdkKey(serviceName, 'production');
// Save to .env
fs.writeFileSync('.env', `LAUNCHDARKLY_SDK_KEY=${sdkKey}\n`);
console.log(`✓ Setup complete for ${serviceName}`);
}Go Template
// templates/go/launchdarkly_setup.go
package main
import (
"fmt"
"os"
"yourmodule/pkg/launchdarkly"
)
func setupProject(serviceName string) error {
pm := launchdarkly.NewProjectManager("")
project, err := pm.CreateProject(
fmt.Sprintf("%s Service", serviceName),
serviceName,
[]string{"go", "service"},
)
if err != nil {
return err
}
sdkKey, err := pm.GetSDKKey(serviceName, "production")
if err != nil {
return err
}
// Save to .env
f, err := os.Create(".env")
if err != nil {
return err
}
defer f.Close()
fmt.Fprintf(f, "LAUNCHDARKLY_SDK_KEY=%s\n", sdkKey)
fmt.Printf("✓ Setup complete for %s\n", serviceName)
return nil
}Monorepo Setup
For monorepos with multiple services:
monorepo/
├── services/
│ ├── api/ (Node.js)
│ ├── worker/ (Python)
│ └── gateway/ (Go)
├── packages/
│ └── launchdarkly-setup/
│ ├── python/
│ │ └── setup.py
│ ├── nodejs/
│ │ └── setup.ts
│ └── go/
│ └── setup.go
└── scripts/
└── setup-all-projects.shsetup-all-projects.sh:
#!/bin/bash
echo "Setting up LaunchDarkly projects for all services..."
cd services/api
npm run setup-launchdarkly
cd ../worker
python scripts/setup_launchdarkly.py
cd ../gateway
go run scripts/setup_launchdarkly.go
echo "✓ All services configured"Environment Variable Convention
Standardize environment variable names across languages:
# Common convention
LAUNCHDARKLY_SDK_KEY=<production-key>
LAUNCHDARKLY_SDK_KEY_TEST=<test-key>
LAUNCHDARKLY_PROJECT_KEY=<project-key>
LAUNCHDARKLY_ENVIRONMENT=<production|test>Loading in each language:
Python:
import os
sdk_key = os.environ['LAUNCHDARKLY_SDK_KEY']Node.js:
const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY;Go:
sdkKey := os.Getenv("LAUNCHDARKLY_SDK_KEY")Ruby:
sdk_key = ENV['LAUNCHDARKLY_SDK_KEY']Java:
String sdkKey = System.getenv("LAUNCHDARKLY_SDK_KEY");Container/K8s ConfigMap
Share SDK keys via Kubernetes ConfigMap:
# k8s/launchdarkly-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: launchdarkly-config
data:
project-key: "my-app"
environment: "production"
---
apiVersion: v1
kind: Secret
metadata:
name: launchdarkly-secrets
type: Opaque
stringData:
sdk-key: "sdk-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"Mount in all services:
apiVersion: apps/v1
kind: Deployment
metadata:
name: service-a
spec:
template:
spec:
containers:
- name: app
env:
- name: LAUNCHDARKLY_SDK_KEY
valueFrom:
secretKeyRef:
name: launchdarkly-secrets
key: sdk-key
- name: LAUNCHDARKLY_PROJECT_KEY
valueFrom:
configMapKeyRef:
name: launchdarkly-config
key: project-keyService Mesh Integration
For service mesh (Istio, Linkerd), use a sidecar pattern:
# Sidecar that manages LaunchDarkly SDK keys
apiVersion: v1
kind: Pod
metadata:
name: my-service
spec:
containers:
# Main application
- name: app
image: my-service:latest
env:
- name: LAUNCHDARKLY_SDK_KEY
value: /var/run/secrets/launchdarkly/sdk-key
volumeMounts:
- name: ld-keys
mountPath: /var/run/secrets/launchdarkly
# Sidecar that fetches/refreshes keys
- name: ld-key-sync
image: ld-key-sync:latest
env:
- name: LAUNCHDARKLY_API_TOKEN
valueFrom:
secretKeyRef:
name: ld-api-token
key: token
- name: PROJECT_KEY
value: my-service
volumeMounts:
- name: ld-keys
mountPath: /var/run/secrets/launchdarkly
volumes:
- name: ld-keys
emptyDir: {}Best Practices
1. Naming Conventions
Project Key Format: {service-name}-{optional-suffix}
Examples:
- api-gateway
- user-service
- recommendation-engine2. Tagging Strategy
tags = [
"language:python", # or nodejs, go, etc.
"team:platform", # owning team
"environment:prod", # deployment env
"type:service" # or frontend, worker, etc.
]3. Documentation
Maintain a service registry:
# Service Registry
| Service | Language | Project Key | Team | Status |
|---------|----------|-------------|------|--------|
| API Gateway | Node.js | api-gateway | Platform | Active |
| User Service | Go | user-service | Identity | Active |
| ML Engine | Python | ml-engine | Data Science | Active |4. Automation
Automate project creation for new services:
# scripts/new-service.sh
#!/bin/bash
SERVICE_NAME=$1
LANGUAGE=$2
# Create project
python scripts/create_project.py \
--name "$SERVICE_NAME Service" \
--key "$SERVICE_NAME" \
--tags "language:$LANGUAGE,type:service"
# Generate template
cp -r "templates/$LANGUAGE" "services/$SERVICE_NAME"
# Setup SDK key
cd "services/$SERVICE_NAME"
bash setup.sh
echo "✓ New service $SERVICE_NAME created"Next Steps
- Setup environment configuration
- Configure project cloning
- Build admin tooling
Node.js/TypeScript Project Setup
Implementation patterns for Node.js and TypeScript applications.
Prerequisites
npm install axios dotenv
# or
yarn add axios dotenvTypeScript Project Manager
Create a typed module for project operations:
// src/launchdarkly/projects.ts
import axios, { AxiosInstance } from 'axios';
interface Project {
name: string;
key: string;
tags?: string[];
environments?: {
items: Environment[];
};
}
interface Environment {
name: string;
key: string;
apiKey: string;
}
interface CreateProjectParams {
name: string;
key: string;
tags?: string[];
}
export class ProjectManager {
private client: AxiosInstance;
constructor(apiToken?: string) {
const token = apiToken || process.env.LAUNCHDARKLY_API_TOKEN;
if (!token) {
throw new Error('LAUNCHDARKLY_API_TOKEN is required');
}
this.client = axios.create({
baseURL: 'https://app.launchdarkly.com/api/v2',
headers: {
Authorization: token,
'Content-Type': 'application/json',
},
});
}
async createProject(params: CreateProjectParams): Promise<Project> {
try {
const response = await this.client.post<Project>('/projects', params);
return response.data;
} catch (error: any) {
if (error.response?.status === 409) {
// Project exists, fetch and return it
console.log(`Project '${params.key}' already exists`);
return this.getProject(params.key);
}
throw error;
}
}
async getProject(projectKey: string): Promise<Project> {
const response = await this.client.get<Project>(`/projects/${projectKey}`, {
params: { expand: 'environments' },
});
return response.data;
}
async getSdkKey(projectKey: string, environment: string = 'production'): Promise<string | null> {
const project = await this.getProject(projectKey);
const envItems = project.environments?.items || [];
const env = envItems.find((e) => e.key === environment);
return env?.apiKey || null;
}
async listProjects(): Promise<Project[]> {
const response = await this.client.get<{ items: Project[] }>('/projects');
return response.data.items;
}
}JavaScript (CommonJS)
For Node.js without TypeScript:
// src/launchdarkly/projects.js
const axios = require('axios');
class ProjectManager {
constructor(apiToken) {
const token = apiToken || process.env.LAUNCHDARKLY_API_TOKEN;
if (!token) {
throw new Error('LAUNCHDARKLY_API_TOKEN is required');
}
this.client = axios.create({
baseURL: 'https://app.launchdarkly.com/api/v2',
headers: {
Authorization: token,
'Content-Type': 'application/json',
},
});
}
async createProject({ name, key, tags = [] }) {
try {
const response = await this.client.post('/projects', { name, key, tags });
return response.data;
} catch (error) {
if (error.response?.status === 409) {
console.log(`Project '${key}' already exists`);
return this.getProject(key);
}
throw error;
}
}
async getProject(projectKey) {
const response = await this.client.get(`/projects/${projectKey}`, {
params: { expand: 'environments' },
});
return response.data;
}
async getSdkKey(projectKey, environment = 'production') {
const project = await this.getProject(projectKey);
const envItems = project.environments?.items || [];
const env = envItems.find((e) => e.key === environment);
return env?.apiKey || null;
}
async listProjects() {
const response = await this.client.get('/projects');
return response.data.items;
}
}
module.exports = { ProjectManager };Express.js Integration
Integrate project setup into Express app:
// src/app.ts
import express from 'express';
import dotenv from 'dotenv';
import { ProjectManager } from './launchdarkly/projects';
dotenv.config();
const app = express();
const pm = new ProjectManager();
// Ensure project exists on startup
async function initializeLaunchDarkly() {
try {
const project = await pm.createProject({
name: 'Express API',
key: 'express-api',
tags: ['api', 'ai-configs'],
});
const sdkKey = await pm.getSdkKey('express-api', 'production');
console.log(`✓ LaunchDarkly project ready: ${project.key}`);
// Store SDK key for SDK initialization
process.env.LAUNCHDARKLY_SDK_KEY = sdkKey || '';
} catch (error) {
console.error('Failed to initialize LaunchDarkly:', error);
process.exit(1);
}
}
// Initialize before starting server
initializeLaunchDarkly().then(() => {
app.listen(3000, () => {
console.log('Server running on port 3000');
});
});NestJS Integration
For NestJS applications:
// src/launchdarkly/launchdarkly.module.ts
import { Module, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ProjectManager } from './projects';
@Module({
providers: [ProjectManager],
exports: [ProjectManager],
})
export class LaunchDarklyModule implements OnModuleInit {
constructor(
private readonly pm: ProjectManager,
private readonly config: ConfigService,
) {}
async onModuleInit() {
const projectKey = this.config.get('LAUNCHDARKLY_PROJECT_KEY', 'nestjs-app');
try {
const project = await this.pm.createProject({
name: 'NestJS Application',
key: projectKey,
tags: ['nestjs', 'ai-configs'],
});
console.log(`✓ LaunchDarkly project ready: ${project.key}`);
} catch (error) {
console.error('LaunchDarkly initialization failed:', error);
}
}
}
// src/launchdarkly/projects.ts (Injectable version)
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios, { AxiosInstance } from 'axios';
@Injectable()
export class ProjectManager {
private client: AxiosInstance;
constructor(private config: ConfigService) {
const apiToken = this.config.get('LAUNCHDARKLY_API_TOKEN');
this.client = axios.create({
baseURL: 'https://app.launchdarkly.com/api/v2',
headers: {
Authorization: apiToken,
'Content-Type': 'application/json',
},
});
}
// ... same methods as before
}CLI Tool
Create a CLI for project management:
// cli/projects.ts
#!/usr/bin/env node
import { Command } from 'commander';
import { ProjectManager } from '../src/launchdarkly/projects';
const program = new Command();
const pm = new ProjectManager();
program
.name('ld-projects')
.description('LaunchDarkly project management CLI');
program
.command('create <name> <key>')
.description('Create a new project')
.option('-t, --tags <tags...>', 'Project tags')
.action(async (name: string, key: string, options: { tags?: string[] }) => {
try {
const project = await pm.createProject({ name, key, tags: options.tags });
console.log(`✓ Created: ${project.name} (${project.key})`);
} catch (error: any) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('list')
.description('List all projects')
.action(async () => {
try {
const projects = await pm.listProjects();
projects.forEach((p) => {
console.log(`- ${p.name} (${p.key})`);
});
} catch (error: any) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('get-key <projectKey>')
.description('Get SDK key for a project')
.option('-e, --env <environment>', 'Environment', 'production')
.action(async (projectKey: string, options: { env: string }) => {
try {
const sdkKey = await pm.getSdkKey(projectKey, options.env);
if (sdkKey) {
console.log(sdkKey);
} else {
console.error(`Environment '${options.env}' not found`);
process.exit(1);
}
} catch (error: any) {
console.error('Error:', error.message);
process.exit(1);
}
});
program.parse();Usage:
npm run ld-projects create "My Agent" my-ai -t ai-configs production
npm run ld-projects list
npm run ld-projects get-key my-ai --env productionError Handling
Add comprehensive error handling:
export class LaunchDarklyError extends Error {
constructor(
message: string,
public statusCode?: number,
public response?: any
) {
super(message);
this.name = 'LaunchDarklyError';
}
}
export class ProjectManager {
async createProject(params: CreateProjectParams): Promise<Project> {
try {
const response = await this.client.post<Project>('/projects', params);
return response.data;
} catch (error: any) {
if (error.response) {
const status = error.response.status;
if (status === 409) {
return this.getProject(params.key);
}
if (status === 401) {
throw new LaunchDarklyError('Invalid API token', status);
}
if (status === 403) {
throw new LaunchDarklyError(
'Insufficient permissions (need projects:write)',
status
);
}
throw new LaunchDarklyError(
`API error: ${error.response.data.message || 'Unknown error'}`,
status,
error.response.data
);
}
throw new LaunchDarklyError(`Request failed: ${error.message}`);
}
}
}Testing
Mock with Jest:
// __tests__/projects.test.ts
import axios from 'axios';
import { ProjectManager } from '../src/launchdarkly/projects';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe('ProjectManager', () => {
let pm: ProjectManager;
beforeEach(() => {
mockedAxios.create.mockReturnValue(mockedAxios as any);
pm = new ProjectManager('test-token');
});
it('should create a project', async () => {
const mockProject = { name: 'Test', key: 'test', tags: [] };
mockedAxios.post.mockResolvedValue({ data: mockProject });
const project = await pm.createProject({ name: 'Test', key: 'test' });
expect(project.key).toBe('test');
expect(mockedAxios.post).toHaveBeenCalledWith('/projects', {
name: 'Test',
key: 'test',
});
});
it('should handle existing project', async () => {
const mockProject = { name: 'Test', key: 'test' };
mockedAxios.post.mockRejectedValue({
response: { status: 409 },
});
mockedAxios.get.mockResolvedValue({ data: mockProject });
const project = await pm.createProject({ name: 'Test', key: 'test' });
expect(project.key).toBe('test');
expect(mockedAxios.get).toHaveBeenCalledWith('/projects/test', {
params: { expand: 'environments' },
});
});
});Next Steps
- Save SDK keys to .env
- Set up project cloning
- Build admin tooling
Project Cloning
Patterns for cloning projects across regions, teams, or environments.
Use Cases
- Multi-region deployments: Clone project structure for US, EU, APAC regions
- Multi-tenant applications: Separate project per customer/tenant
- Team isolation: Clone template for different teams
- Environment parity: Ensure dev/staging/prod have identical structure
Basic Cloning Pattern
Python
def clone_project(source_key: str, new_name: str, new_key: str, tags: List[str] = None) -> Dict:
"""
Clone a project's structure (metadata only, not flags/configs).
Args:
source_key: The project to copy settings from
new_name: Name for the new project
new_key: Unique key for the new project
tags: Optional tags (defaults to source tags + 'cloned')
Returns:
The newly created project
"""
pm = ProjectManager()
# Get source project settings
source = pm.get_project(source_key)
if not source:
raise ValueError(f"Source project '{source_key}' not found")
# Prepare new project with same settings
source_tags = source.get("tags", [])
new_tags = tags or (source_tags + ["cloned"])
# Create new project
return pm.create_project(
name=new_name,
key=new_key,
tags=new_tags
)TypeScript
async function cloneProject(
sourceKey: string,
newName: string,
newKey: string,
tags?: string[]
): Promise<Project> {
const pm = new ProjectManager();
// Get source project settings
const source = await pm.getProject(sourceKey);
if (!source) {
throw new Error(`Source project '${sourceKey}' not found`);
}
// Prepare new project with same settings
const sourceTags = source.tags || [];
const newTags = tags || [...sourceTags, 'cloned'];
// Create new project
return pm.createProject({
name: newName,
key: newKey,
tags: newTags,
});
}Multi-Region Cloning
Clone a project for multiple regions:
def clone_for_regions(base_project: str, regions: List[str]):
"""
Clone a project for multiple regions.
Example:
clone_for_regions("ai-service", ["us", "eu", "apac"])
Creates: ai-service-us, ai-service-eu, ai-service-apac
"""
pm = ProjectManager()
base = pm.get_project(base_project)
if not base:
raise ValueError(f"Base project '{base_project}' not found")
created_projects = []
for region in regions:
new_key = f"{base_project}-{region}"
new_name = f"{base['name']} - {region.upper()}"
print(f"Creating {new_key}...")
project = clone_project(
source_key=base_project,
new_name=new_name,
new_key=new_key,
tags=base.get("tags", []) + [f"region:{region}"]
)
created_projects.append(project)
print(f"✓ Created {new_key}")
return created_projects
# Usage
clone_for_regions("customer-ai", ["us", "eu", "apac"])Result:
customer-ai-us- Customer Agent - UScustomer-ai-eu- Customer Agent - EUcustomer-ai-apac- Customer Agent - APAC
Multi-Tenant Cloning
Clone for different tenants/customers:
interface Tenant {
id: string;
name: string;
}
async function cloneForTenants(
baseProject: string,
tenants: Tenant[]
): Promise<Project[]> {
const pm = new ProjectManager();
const base = await pm.getProject(baseProject);
if (!base) {
throw new Error(`Base project '${baseProject}' not found`);
}
const createdProjects: Project[] = [];
for (const tenant of tenants) {
const newKey = `${baseProject}-${tenant.id}`;
const newName = `${base.name} - ${tenant.name}`;
console.log(`Creating ${newKey}...`);
const project = await cloneProject(
baseProject,
newName,
newKey,
[...(base.tags || []), `tenant:${tenant.id}`]
);
createdProjects.push(project);
console.log(`✓ Created ${newKey}`);
}
return createdProjects;
}
// Usage
const tenants = [
{ id: 'acme', name: 'Acme Corp' },
{ id: 'globex', name: 'Globex Inc' },
{ id: 'initech', name: 'Initech' },
];
cloneForTenants('saas-ai', tenants);Result:
saas-ai-acme- SaaS Agent - Acme Corpsaas-ai-globex- SaaS Agent - Globex Incsaas-ai-initech- SaaS Agent - Initech
Team-Based Cloning
Clone template project for multiple teams:
def clone_for_teams(template_project: str, teams: List[str]):
"""
Clone a template project for multiple teams.
Example:
clone_for_teams("ai-template", ["platform", "customer", "product"])
"""
pm = ProjectManager()
template = pm.get_project(template_project)
if not template:
raise ValueError(f"Template project '{template_project}' not found")
created_projects = []
for team in teams:
new_key = f"{team}-ai"
new_name = f"{team.title()} Team Agent"
print(f"Creating {new_key} for {team} team...")
project = clone_project(
source_key=template_project,
new_name=new_name,
new_key=new_key,
tags=["ai-configs", f"team:{team}"]
)
# Save SDK keys for team
save_sdk_key_to_env(
new_key,
"production",
env_file=f".env.{team}",
var_name="LAUNCHDARKLY_SDK_KEY"
)
created_projects.append(project)
print(f"✓ Created {new_key}")
return created_projects
# Usage
clone_for_teams("ai-template", ["platform", "customer", "product"])Bulk Cloning with CSV
Clone from a CSV file with project specifications:
import csv
def clone_from_csv(source_key: str, csv_file: str):
"""
Clone projects from CSV file.
CSV format:
project_key,project_name,tags
mobile-ai-us,Mobile Agent US,"mobile,us,production"
mobile-ai-eu,Mobile Agent EU,"mobile,eu,production"
"""
pm = ProjectManager()
created_projects = []
with open(csv_file, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
key = row['project_key']
name = row['project_name']
tags = row.get('tags', '').split(',') if row.get('tags') else []
print(f"Creating {key}...")
project = clone_project(source_key, name, key, tags)
created_projects.append(project)
print(f"✓ Created {key}")
return created_projects
# Usage
clone_from_csv("ai-template", "projects.csv")projects.csv:
project_key,project_name,tags
mobile-ai-us,Mobile Agent US,"mobile,us,production"
mobile-ai-eu,Mobile Agent EU,"mobile,eu,production"
web-ai-us,Web Agent US,"web,us,production"
web-ai-eu,Web Agent EU,"web,eu,production"Automated SDK Key Management
After cloning, automatically save SDK keys:
def clone_and_configure(
source_key: str,
new_key: str,
new_name: str,
env_file: str = None
):
"""Clone project and automatically save SDK keys."""
# Clone the project
project = clone_project(source_key, new_name, new_key)
print(f"✓ Cloned {source_key} → {new_key}")
# Save SDK keys for both environments
env_file = env_file or f".env.{new_key}"
for environment in ["production", "test"]:
var_name = f"LD_SDK_KEY_{environment.upper()}"
save_sdk_key_to_env(new_key, environment, env_file, var_name)
print(f"✓ Saved SDK keys to {env_file}")
return projectParallel Cloning
Clone multiple projects in parallel for speed:
import concurrent.futures
def clone_projects_parallel(clones: List[Dict[str, str]], max_workers: int = 5):
"""
Clone multiple projects in parallel.
Args:
clones: List of dicts with keys: source_key, new_key, new_name
max_workers: Max parallel requests
"""
def clone_single(clone_spec):
return clone_project(
source_key=clone_spec['source_key'],
new_name=clone_spec['new_name'],
new_key=clone_spec['new_key']
)
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(clone_single, spec): spec for spec in clones}
results = []
for future in concurrent.futures.as_completed(futures):
spec = futures[future]
try:
project = future.result()
print(f"✓ Cloned {spec['new_key']}")
results.append(project)
except Exception as e:
print(f"✗ Failed to clone {spec['new_key']}: {e}")
return results
# Usage
clones = [
{"source_key": "template", "new_key": "team-a-ai", "new_name": "Team A Agent"},
{"source_key": "template", "new_key": "team-b-ai", "new_name": "Team B Agent"},
{"source_key": "template", "new_key": "team-c-ai", "new_name": "Team C Agent"},
]
clone_projects_parallel(clones)Cloning with MCP Server
If using the LaunchDarkly MCP server:
// Note: MCP server may not have clone functionality
// You would create projects individually
async function cloneWithMCP(sourceKey: string, newKey: string, newName: string) {
// Get source project via MCP
const source = await mcp.getProject(sourceKey);
// Create new project with same settings
const project = await mcp.createProject({
name: newName,
key: newKey,
tags: [...(source.tags || []), 'cloned'],
});
return project;
}Best Practices
1. Naming Conventions
Use consistent naming across clones:
{base}-{region} → ai-service-us, ai-service-eu
{team}-{service} → platform-ai, customer-ai
{service}-{tenant} → saas-acme, saas-globex2. Tagging Strategy
Tag clones for easy filtering:
tags = [
"ai-configs",
f"region:{region}",
f"cloned-from:{source_key}",
f"created:{datetime.now().isoformat()}"
]3. Documentation
Document cloning relationships:
def clone_with_metadata(source_key: str, new_key: str, new_name: str):
"""Clone and document the relationship."""
project = clone_project(source_key, new_name, new_key)
# Create a mapping file
with open("project-clones.json", "a") as f:
f.write(json.dumps({
"source": source_key,
"clone": new_key,
"created_at": datetime.now().isoformat()
}) + "\n")
return project4. Verification
Verify clones after creation:
def verify_clones(clones: List[str]):
"""Verify all cloned projects exist and have SDK keys."""
pm = ProjectManager()
for project_key in clones:
project = pm.get_project(project_key)
if not project:
print(f"✗ {project_key} not found")
continue
sdk_key = pm.get_sdk_key(project_key, "production")
if sdk_key:
print(f"✓ {project_key} verified")
else:
print(f"⚠️ {project_key} missing SDK key")Next Steps
- Save SDK keys for cloned projects
- Automate with IaC
- Build admin tooling
Python Project Setup
Implementation patterns for Python applications using the LaunchDarkly API.
Prerequisites
pip install requests python-dotenvBasic Project Management Module
Create a reusable module for project operations:
# launchdarkly/projects.py
import os
import requests
from typing import Optional, Dict, List
API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN")
BASE_URL = "https://app.launchdarkly.com/api/v2"
class ProjectManager:
"""Manage LaunchDarkly projects via API."""
def __init__(self, api_token: Optional[str] = None):
self.api_token = api_token or API_TOKEN
self.headers = {
"Authorization": self.api_token,
"Content-Type": "application/json"
}
def create_project(self, name: str, key: str, tags: Optional[List[str]] = None) -> Optional[Dict]:
"""
Create a new LaunchDarkly project.
Args:
name: Human-readable project name
key: Unique identifier (lowercase, hyphens only)
tags: Optional list of tags
Returns:
Project dict if successful, None otherwise
"""
payload = {"name": name, "key": key}
if tags:
payload["tags"] = tags
response = requests.post(
f"{BASE_URL}/projects",
headers=self.headers,
json=payload
)
if response.status_code == 201:
return response.json()
elif response.status_code == 409:
print(f"Project '{key}' already exists")
return self.get_project(key)
else:
print(f"Error: {response.text}")
return None
def get_project(self, project_key: str) -> Optional[Dict]:
"""Get project with environments expanded."""
response = requests.get(
f"{BASE_URL}/projects/{project_key}",
headers=self.headers,
params={"expand": "environments"}
)
return response.json() if response.status_code == 200 else None
def get_sdk_key(self, project_key: str, environment: str = "production") -> Optional[str]:
"""Get SDK key for a specific environment."""
project = self.get_project(project_key)
if not project:
return None
envs = project.get("environments", {})
env_items = envs.get("items", []) if isinstance(envs, dict) else envs
for env in env_items:
if env["key"] == environment:
return env["apiKey"]
return None
def list_projects(self) -> List[Dict]:
"""List all projects in organization."""
response = requests.get(
f"{BASE_URL}/projects",
headers=self.headers
)
return response.json().get("items", []) if response.status_code == 200 else []Usage Examples
Create a Project
from launchdarkly.projects import ProjectManager
pm = ProjectManager()
# Create new project
project = pm.create_project(
name="Customer Support Agent",
key="support-ai",
tags=["ai-configs", "production"]
)
if project:
print(f"Created project: {project['key']}")Get SDK Key
pm = ProjectManager()
# Get production SDK key
sdk_key = pm.get_sdk_key("support-ai", "production")
print(f"Production SDK Key: {sdk_key}")
# Get test SDK key
test_sdk_key = pm.get_sdk_key("support-ai", "test")
print(f"Test SDK Key: {test_sdk_key}")List Projects
pm = ProjectManager()
projects = pm.list_projects()
for project in projects:
print(f"- {project['name']} ({project['key']})")FastAPI Integration
If you're using FastAPI, integrate project management into your app:
# app/config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
launchdarkly_api_token: str
launchdarkly_sdk_key: str
class Config:
env_file = ".env"
settings = Settings()
# app/main.py
from fastapi import FastAPI
from launchdarkly.projects import ProjectManager
from .config import settings
app = FastAPI()
pm = ProjectManager(api_token=settings.launchdarkly_api_token)
@app.on_event("startup")
async def startup():
# Ensure project exists
project = pm.create_project(
name="My API Service",
key="api-service"
)
if project:
print(f"LaunchDarkly project ready: {project['key']}")Django Integration
For Django applications:
# settings.py
import os
from launchdarkly.projects import ProjectManager
LAUNCHDARKLY_API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN")
LAUNCHDARKLY_PROJECT_KEY = os.environ.get("LAUNCHDARKLY_PROJECT_KEY", "django-app")
# Ensure project exists on startup
pm = ProjectManager(api_token=LAUNCHDARKLY_API_TOKEN)
project = pm.create_project(
name="Django Application",
key=LAUNCHDARKLY_PROJECT_KEY
)
LAUNCHDARKLY_SDK_KEY = pm.get_sdk_key(LAUNCHDARKLY_PROJECT_KEY, "production")CLI Tool
Create a management CLI for project operations:
# cli/ld_projects.py
import click
from launchdarkly.projects import ProjectManager
@click.group()
def cli():
"""LaunchDarkly project management CLI."""
pass
@cli.command()
@click.argument("name")
@click.argument("key")
@click.option("--tags", multiple=True, help="Project tags")
def create(name: str, key: str, tags: tuple):
"""Create a new project."""
pm = ProjectManager()
project = pm.create_project(name, key, list(tags))
if project:
click.echo(f"✓ Created: {project['name']} ({project['key']})")
@cli.command()
def list():
"""List all projects."""
pm = ProjectManager()
projects = pm.list_projects()
for project in projects:
click.echo(f"- {project['name']} ({project['key']})")
@cli.command()
@click.argument("project_key")
@click.option("--env", default="production", help="Environment")
def get_key(project_key: str, env: str):
"""Get SDK key for a project environment."""
pm = ProjectManager()
sdk_key = pm.get_sdk_key(project_key, env)
if sdk_key:
click.echo(sdk_key)
if __name__ == "__main__":
cli()Usage:
python cli/ld_projects.py create "My Agent" my-ai --tags ai-configs
python cli/ld_projects.py list
python cli/ld_projects.py get-key my-ai --env productionError Handling
Add robust error handling for production use:
class LaunchDarklyError(Exception):
"""Base exception for LaunchDarkly operations."""
pass
class ProjectManager:
def create_project(self, name: str, key: str, tags: Optional[List[str]] = None) -> Dict:
"""Create project with error handling."""
try:
response = requests.post(
f"{BASE_URL}/projects",
headers=self.headers,
json={"name": name, "key": key, "tags": tags or []},
timeout=10
)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 409:
# Project exists, return existing
return self.get_project(key)
elif e.response.status_code == 401:
raise LaunchDarklyError("Invalid API token")
elif e.response.status_code == 403:
raise LaunchDarklyError("Insufficient permissions (need projects:write)")
else:
raise LaunchDarklyError(f"API error: {e.response.text}")
except requests.exceptions.RequestException as e:
raise LaunchDarklyError(f"Request failed: {str(e)}")Testing
Mock the API for unit tests:
# tests/test_projects.py
import pytest
from unittest.mock import Mock, patch
from launchdarkly.projects import ProjectManager
@pytest.fixture
def mock_response():
mock = Mock()
mock.status_code = 201
mock.json.return_value = {
"name": "Test Project",
"key": "test-project"
}
return mock
@patch("requests.post")
def test_create_project(mock_post, mock_response):
mock_post.return_value = mock_response
pm = ProjectManager(api_token="test-token")
project = pm.create_project("Test Project", "test-project")
assert project["key"] == "test-project"
mock_post.assert_called_once()Next Steps
- Save SDK keys to .env
- Clone projects for different environments
- Build admin tooling
Quick Start: Create Your First Project
Basic project creation patterns for getting started quickly.
Prerequisites
- LaunchDarkly API access token with
projects:writepermission - Set
LAUNCHDARKLY_API_TOKENenvironment variable
Basic Project Creation
Using the LaunchDarkly API
Endpoint: POST https://app.launchdarkly.com/api/v2/projects
Required Headers:
Authorization: {YOUR_API_TOKEN}
Content-Type: application/jsonMinimal Payload:
{
"name": "My Agent Project",
"key": "my-ai-project"
}Recommended Payload:
{
"name": "Customer Support Agent",
"key": "support-ai",
"tags": ["ai-configs", "production"]
}Response Handling
Success (201 Created)
{
"name": "Customer Support Agent",
"key": "support-ai",
"environments": {
"items": [
{
"name": "Production",
"key": "production",
"apiKey": "sdk-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
{
"name": "Test",
"key": "test",
"apiKey": "sdk-yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
}
]
}
}Action: Extract and save SDK keys for use in your application.
Conflict (409)
Project with that key already exists.
Action: Either use the existing project or choose a different key.
Error (400, 401, 403)
{
"code": "invalid_request",
"message": "project key must be lowercase with hyphens"
}Action: Fix the payload based on error message.
Project Key Rules
Must follow these constraints:
- Pattern:
^[a-z][a-z0-9-]*$ - Start with: Lowercase letter
- Contains only: Lowercase letters, numbers, hyphens
- Unique: Across your entire organization
Valid Examples
support-ai
chat-bot-v2
recommendation-engine
customer-ai-prodInvalid Examples
Support_AI # uppercase and underscore
123-project # starts with number
my.project # contains dot
ai_chatbot # underscore not allowedExtracting SDK Keys
After creating a project, you'll need the SDK keys to connect your application.
Environments Created by Default
- Production (key:
production) - Test (key:
test)
Get SDK Key for an Environment
Endpoint: GET https://app.launchdarkly.com/api/v2/projects/{projectKey}?expand=environments
Parse Response:
{
"environments": {
"items": [
{
"key": "production",
"apiKey": "sdk-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
]
}
}Filter by environment.key to find the desired environment's apiKey.
List Existing Projects
Before creating a new project, you may want to check what exists.
Endpoint: GET https://app.launchdarkly.com/api/v2/projects
Response:
{
"items": [
{
"name": "Customer Support Agent",
"key": "support-ai",
"tags": ["ai-configs"]
}
]
}Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Uppercase in key | Rejected by API | Use lowercase only |
| Spaces in key | Invalid format | Use hyphens instead |
| Key collision | 409 conflict | Choose unique key or use existing |
| Missing API token | 401 unauthorized | Set LAUNCHDARKLY_API_TOKEN |
| Wrong permission | 403 forbidden | Request projects:write permission |
Next Steps
After creating your project:
1. Save SDK keys to your environment configuration 2. Initialize LaunchDarkly SDK in your application 3. Create configs within the project 4. Test the integration in test environment first
See Environment Configuration for saving SDK keys to your codebase.