AI-Powered Ad Copy Generation Guide
Provides TypeScript functions and database models for generating, storing, scoring, and A/B testing AI-powered Meta ad copy using OpenAI or Claude.
What this file does
Provides TypeScript functions and database models for generating, storing, scoring, and A/B testing AI-powered Meta ad copy using OpenAI or Claude.
When to use it
- Building an automated ad copy generation tool for Meta Ads
- Integrating OpenAI or Claude to produce multiple ad variants
- Setting up A/B tests for ad copy with performance analysis
- Scoring and refreshing ad creative based on quality and fatigue
Assumes this stack
AI-Powered Ad Copy Generation Guide
This guide explains how to implement AI-powered ad copy generation for Meta Ads using this database layer with OpenAI, Claude, or other LLM providers.
Note: This repository provides the data models and storage. You'll need to integrate with an AI provider (OpenAI, Anthropic Claude, etc.) separately.
Table of Contents
- Overview
- Database Models
- AI Provider Integration
- Copy Generation Strategies
- Creative Management
- A/B Testing Copy Variants
- Quality Scoring
- Best Practices
Overview
The system uses the GeneratedCopy model to store AI-generated ad copy and track its usage across campaigns, ad sets, and ads.
Copy Generation Flow
Business Context + Product Info
↓
AI Model (GPT-4, Claude, etc.)
↓
Generated Copy Variants
↓
Store in GeneratedCopy model
↓
Use in Ad creative
↓
Track performance & iterate
Database Models
GeneratedCopy Model
import { GeneratedCopyModel, IGeneratedCopy } from '@your-org/meta-ad-db/lib/db/models/GeneratedCopy';
interface IGeneratedCopy {
tenantId: string;
context: 'AD' | 'LANDING_PAGE' | 'EMAIL' | 'SOCIAL_POST' | 'UNKNOWN';
inputBrief: string; // User's input/prompt
outputText: string; // AI-generated text
model?: string; // e.g., 'gpt-4o', 'claude-3-opus', 'gpt-4-turbo'
qualityScore?: number; // 0-100 quality rating
tags?: string[]; // e.g., ['holiday', 'discount', 'urgency']
usedBy?: Array<{
entityType: 'CAMPAIGN' | 'AD_SET' | 'AD';
entityId: string;
}>;
createdAt?: Date;
updatedAt?: Date;
}
CreativeAsset Model
import { CreativeAssetModel, ICreativeAsset } from '@your-org/meta-ad-db/lib/db/models/creative-asset';
interface ICreativeAsset {
assetId: string;
type: 'IMAGE' | 'VIDEO' | 'CAROUSEL' | 'TEXT' | 'UNKNOWN';
url: string;
hash?: string; // Content hash for deduplication
metadata?: {
headline?: string;
primaryText?: string;
description?: string;
callToAction?: string;
dimensions?: { width: number; height: number };
};
usedBy: Array<{
entityType: 'CAMPAIGN' | 'AD_SET' | 'AD';
entityId: string;
}>;
}
AI Provider Integration
OpenAI Integration
import OpenAI from 'openai';
import { GeneratedCopy } from '@your-org/meta-ad-db/lib/db/models/GeneratedCopy';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function generateAdCopyWithOpenAI(
tenantId: string,
productInfo: {
name: string;
description: string;
targetAudience: string;
keyBenefits: string[];
tone: 'professional' | 'casual' | 'urgent' | 'friendly';
},
variants: number = 3
) {
const prompt = `You are an expert Meta Ads copywriter. Generate ${variants} high-converting ad copy variants.
Product: ${productInfo.name}
Description: ${productInfo.description}
Target Audience: ${productInfo.targetAudience}
Key Benefits: ${productInfo.keyBenefits.join(', ')}
Tone: ${productInfo.tone}
For each variant, provide:
1. Primary Text (max 125 characters, attention-grabbing)
2. Headline (max 40 characters, clear value prop)
3. Description (max 30 characters, call-to-action)
Format as JSON array with structure: [{ primaryText, headline, description }]`;
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: 'You are an expert Meta Ads copywriter focused on high conversion rates.'
},
{
role: 'user',
content: prompt
}
],
temperature: 0.8,
response_format: { type: 'json_object' }
});
const copyVariants = JSON.parse(response.choices[0].message.content || '{}');
// Store each variant in database
const savedCopies = [];
for (const variant of copyVariants.variants || []) {
const copy = await GeneratedCopy.create({
tenantId,
context: 'AD',
inputBrief: JSON.stringify(productInfo),
outputText: JSON.stringify(variant),
model: 'gpt-4o',
tags: [productInfo.tone, 'ai-generated', new Date().toISOString().split('T')[0]]
});
savedCopies.push(copy);
}
return savedCopies;
}
Anthropic Claude Integration
import Anthropic from '@anthropic-ai/sdk';
import { GeneratedCopy } from '@your-org/meta-ad-db/lib/db/models/GeneratedCopy';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
async function generateAdCopyWithClaude(
tenantId: string,
productInfo: {
name: string;
description: string;
targetAudience: string;
keyBenefits: string[];
tone: string;
},
variants: number = 3
) {
const prompt = `Generate ${variants} Meta/Facebook ad copy variants for this product.
Product: ${productInfo.name}
Description: ${productInfo.description}
Target Audience: ${productInfo.targetAudience}
Key Benefits: ${productInfo.keyBenefits.join(', ')}
Desired Tone: ${productInfo.tone}
Requirements:
- Primary Text: Max 125 characters, hook the reader immediately
- Headline: Max 40 characters, clear value proposition
- Description: Max 30 characters, strong CTA
Return as JSON: { "variants": [{ "primaryText": "...", "headline": "...", "description": "..." }] }`;
const message = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1024,
messages: [
{
role: 'user',
content: prompt
}
]
});
const content = message.content[0].type === 'text' ? message.content[0].text : '{}';
const copyVariants = JSON.parse(content);
const savedCopies = [];
for (const variant of copyVariants.variants || []) {
const copy = await GeneratedCopy.create({
tenantId,
context: 'AD',
inputBrief: JSON.stringify(productInfo),
outputText: JSON.stringify(variant),
model: 'claude-3-5-sonnet-20241022',
tags: [productInfo.tone, 'ai-generated']
});
savedCopies.push(copy);
}
return savedCopies;
}
Copy Generation Strategies
1. Product-Focused Copy
async function generateProductFocusedCopy(
tenantId: string,
product: {
name: string;
price: number;
discount?: number;
features: string[];
category: string;
}
) {
const discountText = product.discount
? `${product.discount}% OFF - `
: '';
const prompt = `Create 5 ad copy variants for this product:
${product.name} - $${product.price}
${discountText}Category: ${product.category}
Features: ${product.features.join(', ')}
Focus on:
1. Benefit-driven headlines
2. Scarcity/urgency where appropriate
3. Clear CTAs
4. Social proof suggestions`;
// Use your AI provider
const copies = await generateAdCopyWithOpenAI(tenantId, {
name: product.name,
description: `$${product.price} ${product.category}`,
targetAudience: 'Users interested in ' + product.category,
keyBenefits: product.features,
tone: product.discount ? 'urgent' : 'professional'
}, 5);
return copies;
}
2. Audience-Targeted Copy
async function generateAudienceTargetedCopy(
tenantId: string,
audienceSegment: {
demographics: { ageRange: string; gender?: string };
interests: string[];
painPoints: string[];
},
product: any
) {
const prompt = `Create ad copy targeting:
Age: ${audienceSegment.demographics.ageRange}
${audienceSegment.demographics.gender ? 'Gender: ' + audienceSegment.demographics.gender : ''}
Interests: ${audienceSegment.interests.join(', ')}
Pain Points: ${audienceSegment.painPoints.join(', ')}
Product: ${product.name}
Solution: How this product solves their pain points
Create 3 variants speaking directly to this audience.`;
// Generate copy addressing specific audience needs
const copies = await generateAdCopyWithOpenAI(tenantId, {
name: product.name,
description: product.description,
targetAudience: `${audienceSegment.demographics.ageRange} ${audienceSegment.demographics.gender || ''} interested in ${audienceSegment.interests[0]}`,
keyBenefits: audienceSegment.painPoints.map(pain => `Solves: ${pain}`),
tone: 'friendly'
}, 3);
return copies;
}
3. Campaign-Specific Copy
async function generateCampaignCopy(
tenantId: string,
campaign: {
objective: 'AWARENESS' | 'CONSIDERATION' | 'CONVERSION';
theme: string;
seasonality?: string;
},
product: any
) {
const objectivePrompts = {
AWARENESS: 'Focus on brand story, education, introducing the product',
CONSIDERATION: 'Emphasize features, comparisons, reviews, social proof',
CONVERSION: 'Strong CTAs, urgency, limited offers, clear next steps'
};
const prompt = `Campaign Objective: ${campaign.objective}
${objectivePrompts[campaign.objective]}
Theme: ${campaign.theme}
${campaign.seasonality ? 'Seasonality: ' + campaign.seasonality : ''}
Product: ${product.name}
Create 4 variants optimized for ${campaign.objective}.`;
const tone = campaign.objective === 'CONVERSION' ? 'urgent' :
campaign.objective === 'CONSIDERATION' ? 'professional' : 'friendly';
const copies = await generateAdCopyWithOpenAI(tenantId, {
name: product.name,
description: product.description + ' - ' + campaign.theme,
targetAudience: 'Target audience for ' + campaign.objective.toLowerCase(),
keyBenefits: product.keyBenefits || [],
tone
}, 4);
return copies;
}
4. Retargeting Copy
async function generateRetargetingCopy(
tenantId: string,
userAction: 'viewed_product' | 'added_to_cart' | 'initiated_checkout' | 'abandoned_cart',
product: any
) {
const retargetingMessages = {
viewed_product: 'Remind them of what they saw, add social proof',
added_to_cart: 'Create urgency, offer small discount, remind of cart',
initiated_checkout: 'Remove friction, offer support, guarantee trust',
abandoned_cart: 'Strong urgency, limited-time discount, scarcity'
};
const prompt = `User Action: ${userAction}
Strategy: ${retargetingMessages[userAction]}
Product they interacted with: ${product.name}
Create 3 retargeting ad variants that:
1. Acknowledge their previous interest
2. Provide new incentive to return
3. Remove objections
4. Create urgency`;
const tone = userAction.includes('abandoned') || userAction.includes('checkout')
? 'urgent' : 'friendly';
const copies = await generateAdCopyWithOpenAI(tenantId, {
name: product.name,
description: `Retargeting: ${userAction}`,
targetAudience: 'Users who ' + userAction.replace('_', ' '),
keyBenefits: ['Come back', 'Limited time', 'Special offer'],
tone
}, 3);
return copies;
}
Creative Management
Creating Complete Ad Creative
import { AdModel } from '@your-org/meta-ad-db/lib/db/models/ad';
import { CreativeAssetModel } from '@your-org/meta-ad-db/lib/db/models/creative-asset';
async function createAdWithGeneratedCopy(
adSetId: string,
campaignId: string,
accountId: string,
generatedCopyId: string,
imageUrl: string
) {
// Get the generated copy
const generatedCopy = await GeneratedCopyModel.findById(generatedCopyId);
if (!generatedCopy) throw new Error('Generated copy not found');
const copyData = JSON.parse(generatedCopy.outputText);
// Create or find creative asset
let creativeAsset = await CreativeAssetModel.findOne({ url: imageUrl });
if (!creativeAsset) {
creativeAsset = await CreativeAssetModel.create({
assetId: 'asset_' + Date.now(),
type: 'IMAGE',
url: imageUrl,
metadata: {
headline: copyData.headline,
primaryText: copyData.primaryText,
description: copyData.description
},
usedBy: []
});
}
// Create ad
const ad = await AdModel.create({
adId: 'ad_' + Date.now(),
adSetId,
campaignId,
accountId,
name: copyData.headline + ' - ' + new Date().toISOString().split('T')[0],
status: 'DRAFT',
creative: {
creativeId: creativeAsset.assetId,
type: 'IMAGE',
headline: copyData.headline,
body: copyData.primaryText,
callToAction: copyData.description,
linkUrl: process.env.LANDING_PAGE_URL
},
effectiveStatus: 'PAUSED',
issues: []
});
// Update usage tracking
await GeneratedCopyModel.findByIdAndUpdate(
generatedCopyId,
{ $push: { usedBy: { entityType: 'AD', entityId: ad.adId } } }
);
await CreativeAssetModel.findByIdAndUpdate(
creativeAsset._id,
{ $push: { usedBy: { entityType: 'AD', entityId: ad.adId } } }
);
return ad;
}
Bulk Copy Generation for Testing
async function generateCopyVariantsForTesting(
tenantId: string,
product: any,
numberOfVariants: number = 10
) {
const variations = {
tones: ['professional', 'casual', 'urgent', 'friendly'],
angles: ['benefit', 'problem-solution', 'social-proof', 'scarcity'],
ctas: ['Shop Now', 'Learn More', 'Get Started', 'Claim Offer']
};
const allCopies = [];
for (let i = 0; i < numberOfVariants; i++) {
const tone = variations.tones[i % variations.tones.length];
const angle = variations.angles[Math.floor(i / variations.tones.length) % variations.angles.length];
const copies = await generateAdCopyWithOpenAI(tenantId, {
name: product.name,
description: `${product.description} (Angle: ${angle})`,
targetAudience: product.targetAudience,
keyBenefits: product.keyBenefits,
tone: tone as any
}, 1);
allCopies.push(...copies);
}
return allCopies;
}
A/B Testing Copy Variants
Setting Up Copy Tests
interface ICopyTest {
testName: string;
variants: Array<{
copyId: string;
adId: string;
budget: number;
}>;
metric: 'CTR' | 'CPC' | 'ROAS' | 'CONVERSIONS';
startDate: Date;
duration: number; // days
}
async function setupCopyABTest(
tenantId: string,
adSetId: string,
campaignId: string,
accountId: string,
generatedCopyIds: string[],
budgetPerVariant: number
): Promise<ICopyTest> {
const variants = [];
for (const copyId of generatedCopyIds) {
const ad = await createAdWithGeneratedCopy(
adSetId,
campaignId,
accountId,
copyId,
'https://example.com/product-image.jpg' // Your image URL
);
variants.push({
copyId,
adId: ad.adId,
budget: budgetPerVariant
});
}
return {
testName: `Copy Test ${new Date().toISOString()}`,
variants,
metric: 'CTR',
startDate: new Date(),
duration: 7
};
}
Analyzing Test Results
import { fetchGraphNode } from '@your-org/meta-ad-db/lib/services/meta-sync/graph-client';
async function analyzeCopyTestResults(
test: ICopyTest,
accessToken: string
) {
const results = [];
for (const variant of test.variants) {
const insights = await fetchGraphNode(
accessToken,
`${variant.adId}/insights`,
{
fields: 'impressions,clicks,ctr,cpc,spend,conversions,cost_per_conversion',
date_preset: 'last_7d'
}
);
const copy = await GeneratedCopyModel.findById(variant.copyId);
results.push({
copyId: variant.copyId,
adId: variant.adId,
copyPreview: copy ? JSON.parse(copy.outputText).headline : 'Unknown',
metrics: insights,
performanceScore: calculatePerformanceScore(insights)
});
}
// Sort by performance
results.sort((a, b) => b.performanceScore - a.performanceScore);
return {
testName: test.testName,
winner: results[0],
allResults: results,
recommendation: results.length >= 2 && results[0].performanceScore > results[1].performanceScore * 1.2
? 'Clear winner - scale this copy'
: results.length < 2
? 'Need at least 2 variants to compare'
: 'Results inconclusive - continue testing'
};
}
const DEFAULT_HIGH_CPC = 999; // Fallback for missing CPC data (penalizes in score calculation)
function calculatePerformanceScore(insights: any): number {
const ctr = parseFloat(insights.ctr || 0);
const conversions = parseInt(insights.conversions || 0);
const cpc = parseFloat(insights.cpc || DEFAULT_HIGH_CPC);
// Weighted score: CTR (40%), Conversions (40%), CPC (20%)
return (ctr * 40) + (conversions * 4) + ((1 / cpc) * 20);
}
Quality Scoring
Automated Copy Quality Scoring
async function scoreGeneratedCopy(copyId: string): Promise<number> {
const copy = await GeneratedCopyModel.findById(copyId);
if (!copy) return 0;
const copyData = JSON.parse(copy.outputText);
let score = 0;
// Character length checks (Meta best practices)
if (copyData.primaryText && copyData.primaryText.length <= 125) score += 20;
if (copyData.headline && copyData.headline.length <= 40) score += 20;
if (copyData.description && copyData.description.length <= 30) score += 20;
// Content quality checks
if (copyData.primaryText) {
const text = copyData.primaryText.toLowerCase();
// Has emoji or attention grabber
if (/[🎉🔥💡✨⭐👉💰🎁]/.test(text) || /[!?]/.test(text)) score += 10;
// Has numbers/stats (credibility)
if (/\d+%|\d+ [a-z]+/.test(text)) score += 5;
// Has power words
const powerWords = ['free', 'save', 'new', 'proven', 'guarantee', 'limited'];
if (powerWords.some(word => text.includes(word))) score += 10;
// Has clear CTA
const ctas = ['shop now', 'learn more', 'get started', 'buy now', 'sign up'];
if (ctas.some(cta => text.includes(cta))) score += 15;
}
// Update score in database
await GeneratedCopyModel.findByIdAndUpdate(copyId, { qualityScore: score });
return score;
}
async function batchScoreCopies(tenantId: string): Promise<void> {
const copies = await GeneratedCopyModel.find({
tenantId,
qualityScore: { $exists: false }
});
for (const copy of copies) {
await scoreGeneratedCopy(copy._id.toString());
}
console.log(`Scored ${copies.length} copies`);
}
Best Practices
1. Copy Generation Guidelines
DO:
- ✅ Generate multiple variants (5-10) for A/B testing
- ✅ Include character limits in prompts (Meta's limits)
- ✅ Test different tones and angles
- ✅ Use product benefits, not just features
- ✅ Include social proof when available
- ✅ Create urgency appropriately (limited time, stock)
- ✅ Tag copies for easy filtering (tone, theme, season)
DON'T:
- ❌ Generate only 1-2 variants
- ❌ Ignore platform character limits
- ❌ Use misleading claims
- ❌ Overuse urgency/scarcity
- ❌ Forget to store input brief for iteration
- ❌ Skip A/B testing
2. Creative Refresh Strategy
Based on the Meta Ads Optimization Strategy document:
async function shouldRefreshCreative(adId: string, accessToken: string): Promise<boolean> {
const insights = await fetchGraphNode(
accessToken,
`${adId}/insights`,
{ fields: 'frequency,ctr,spend', date_preset: 'last_7d' }
);
const frequency = parseFloat(insights.frequency || 0);
const ctr = parseFloat(insights.ctr || 0);
const spend = parseFloat(insights.spend || 0);
// Refresh needed if:
// - Frequency > 3.0 (audience fatigue)
// - CTR < 0.5% (poor creative)
// - Spend > $500 with declining CTR
return frequency > 3.0 || ctr < 0.5 || (spend > 500 && ctr < 1.0);
}
async function refreshAdCreative(
adId: string,
tenantId: string,
product: any
) {
// Generate new copy variants
const newCopies = await generateAdCopyWithOpenAI(tenantId, product, 3);
// Create new ads with fresh creative
const ad = await AdModel.findOne({ adId });
if (!ad) return;
const newAds = [];
for (const copy of newCopies) {
const newAd = await createAdWithGeneratedCopy(
ad.adSetId,
ad.campaignId,
ad.accountId,
copy._id.toString(),
'https://example.com/new-product-image.jpg'
);
newAds.push(newAd);
}
// Pause old ad
await AdModel.findOneAndUpdate(
{ adId },
{ status: 'PAUSED', effectiveStatus: 'PAUSED' }
);
return newAds;
}
3. Model Selection Guide
| Model | Best For | Cost | Speed | Quality |
|---|---|---|---|---|
| GPT-4o | Production, high quality | Medium | Fast | Excellent |
| GPT-4 Turbo | Complex copy, long form | High | Medium | Excellent |
| GPT-3.5 Turbo | Bulk generation, testing | Low | Very Fast | Good |
| Claude 3.5 Sonnet | Creative copy, brand voice | Medium | Fast | Excellent |
| Claude 3 Opus | Premium campaigns | High | Slow | Outstanding |
| Claude 3 Haiku | High-volume, simple copy | Low | Very Fast | Good |
4. Prompt Engineering Tips
const EFFECTIVE_PROMPTS = {
// Specific and constrained
good: `Create 3 Meta ad headlines (max 40 chars each) for [product] targeting [audience].
Focus on [benefit]. Tone: [tone]. Include:
1. Emotional hook
2. Clear value prop
3. Strong CTA`,
// Too vague
bad: `Write some ad copy for my product`,
// With examples (few-shot learning)
withExamples: `Create Meta ad copy like these winning examples:
Example 1: "Save 40% Today! 🎉 Premium Quality, Half the Price"
Example 2: "Join 10,000+ Happy Customers - Free Shipping"
Now create 5 similar variants for [your product]`
};
5. Performance Optimization
// Cache frequently used prompts
const PROMPT_CACHE: Map<string, any> = new Map();
async function generateWithCache(key: string, generator: () => Promise<any>) {
if (PROMPT_CACHE.has(key)) {
return PROMPT_CACHE.get(key);
}
const result = await generator();
PROMPT_CACHE.set(key, result);
// Clear cache after 1 hour
setTimeout(() => PROMPT_CACHE.delete(key), 3600000);
return result;
}
// Batch API calls
async function generateMultipleCopiesBatch(
tenantId: string,
products: any[]
) {
// Instead of sequential API calls, batch them
const promises = products.map(product =>
generateAdCopyWithOpenAI(tenantId, product, 3)
);
return Promise.all(promises);
}
Example: Complete Implementation
// app/api/generate-copy/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { initializeDatabase } from '@your-org/meta-ad-db/lib/db';
import { generateAdCopyWithOpenAI } from './copy-generator';
export async function POST(request: NextRequest) {
await initializeDatabase();
const body = await request.json();
const { tenantId, product, variants = 5 } = body;
try {
// Generate copies
const copies = await generateAdCopyWithOpenAI(tenantId, product, variants);
// Score each copy
for (const copy of copies) {
await scoreGeneratedCopy(copy._id.toString());
}
// Return sorted by quality score
const scoredCopies = await GeneratedCopyModel
.find({ _id: { $in: copies.map(c => c._id) } })
.sort({ qualityScore: -1 });
return NextResponse.json({
success: true,
copies: scoredCopies.map(c => ({
id: c._id,
copy: JSON.parse(c.outputText),
qualityScore: c.qualityScore,
model: c.model
}))
});
} catch (error) {
return NextResponse.json(
{ error: 'Failed to generate copy', details: error.message },
{ status: 500 }
);
}
}
References
What's inside
9 sections: database models, 2 AI provider integrations, 4 copy strategies, creative management, A/B testing, quality scoring, best practices, and a complete example
Change this for your project
- Replace
@your-org/meta-ad-dbwith your own database package or path - Replace
process.env.OPENAI_API_KEYandprocess.env.ANTHROPIC_API_KEYwith your own env variable names - Replace
'https://example.com/product-image.jpg'with your actual image URL - Replace
process.env.LANDING_PAGE_URLwith your own landing page URL
Where it goes
Keep it in your repository where the agent or team that needs it will read it.
Worth borrowing
- Using a
GeneratedCopymodel to store input brief, output text, model name, quality score, and usage tracking across campaigns, ad sets, and ads - Generating multiple copy variants with different tones and angles, then scoring them automatically based on character limits, power words, and CTAs
- Implementing a creative refresh strategy that checks frequency and CTR to decide when to pause old ads and launch new ones
Related Documents
Creative Strategy Reference
Summarizes 2025 Meta ad creative best practices: hook types, video specs, UGC, carousel strategy, copy formulas, and testing metrics.
Purpose
Defines a specialist agent that writes platform-specific ad copy and video scripts for Facebook, Google, LinkedIn, and TikTok.
Copywriting: A Comprehensive Knowledge Base
1. [Introduction and Overview](#introduction-and-overview)
api-contracts
Defines request and response schemas for six API endpoints in a FastAPI ad optimizer backend.