Content management at scale often requires duplicating entries across different content types or environments. Whether you’re migrating content, creating templates, or restructuring your content model, the ability to copy entries programmatically can save hours of manual work. In this guide, I will explore how to build a robust Contentful entry copying utility using C# and the Management API.
Why Copy Entries Programmatically?
Manual content duplication becomes impractical when dealing with:
- Large content migrations between different content types
- Template creation from existing successful content
- Content model restructuring without losing data
- Multi-environment deployments with consistent content
- A/B testing scenarios requiring content variations
Building the Copy Utility
My example solution leverages Contentful’s Management API to create a flexible entry copying system that preserves all field data while adapting to new content types.
Core Architecture
The CopyItem class encapsulates three main operations:
- Retrieve the source entry with all its fields
- Transform the field data for the target content type
- Create a new entry with the copied data
public class CopyItem
{
private readonly HttpClient _httpClient;
private readonly string _spaceId;
private readonly string _environmentId;
private readonly string _managementToken;
public CopyItem(string spaceId, string environmentId, string managementToken)
{
_spaceId = spaceId;
_environmentId = environmentId;
_managementToken = managementToken;
_httpClient = new HttpClient();
}
}
Step 1: Fetching the Source Entry
The first step retrieves the complete entry data using Contentful’s Management API:
private async Task<JObject?> GetEntryAsync(string entryId)
{
var url = $"https://api.contentful.com/spaces/{_spaceId}/environments/{_environmentId}/entries/{entryId}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _managementToken);
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"❌ Failed to fetch entry {entryId}: {response.StatusCode}");
return null;
}
var content = await response.Content.ReadAsStringAsync();
return JObject.Parse(content);
}
Key considerations:
- Uses Management API tokens for write access
- Includes proper error handling for missing entries
- Returns structured JSON for field manipulation
Step 2: Smart Field Extraction
The most critical part of our utility is intelligently handling different field types:
private Dictionary<string, object>? ExtractFieldsForContentType(JObject sourceEntry, string targetContentTypeId)
{
var fieldsJson = sourceEntry["fields"] as JObject;
var locale = "en-US";
var fields = new Dictionary<string, object>();
foreach (var field in fieldsJson.Properties())
{
var fieldName = field.Name;
var fieldValue = field.Value?[locale];
if (fieldValue != null)
{
// Handle different field types appropriately
switch (fieldValue.Type)
{
case JTokenType.String:
fields[fieldName] = new Dictionary<string, object> { [locale] = fieldValue.ToString() };
break;
case JTokenType.Array:
fields[fieldName] = new Dictionary<string, object> { [locale] = fieldValue };
break;
case JTokenType.Object:
fields[fieldName] = new Dictionary<string, object> { [locale] = fieldValue };
break;
// ... handle other types
}
}
}
return fields;
}
This approach handles:
- Text fields – Direct string copying
- Rich text – Preserves formatting and embedded entries
- Media fields – Maintains asset references
- Reference fields – Copies linked entry relationships
- Array fields – Preserves multiple values and references
Step 3: Creating the New Entry
Finally, we create the new entry with the transformed data:
private async Task<Entry<dynamic>?> CreateEntryAsync(Entry<dynamic> entry, string contentTypeId)
{
var url = $"https://api.contentful.com/spaces/{_spaceId}/environments/{_environmentId}/entries";
var json = JsonConvert.SerializeObject(entry);
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(json, Encoding.UTF8, "application/vnd.contentful.management.v1+json")
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _managementToken);
request.Headers.Add("X-Contentful-Content-Type", contentTypeId);
var response = await _httpClient.SendAsync(request);
// ... error handling and response processing
}
Advanced Implementation Strategies
Handling Field Validation
Different content types have different field requirements. Consider these enhancements:
// Validate required fields before creation
private bool ValidateFieldsForContentType(Dictionary<string, object> fields, string contentTypeId)
{
// Fetch content type definition
// Check required fields
// Validate field constraints
return true;
}
Batch Processing
For large-scale operations, implement batch processing:
public async Task<List<string>> CopyMultipleEntriesAsync(
List<string> sourceEntryIds,
string targetContentTypeId)
{
var results = new List<string>();
var semaphore = new SemaphoreSlim(5); // Limit concurrent requests
var tasks = sourceEntryIds.Select(async id =>
{
await semaphore.WaitAsync();
try
{
var result = await CopyEntryAsync(id, targetContentTypeId);
if (result != null) results.Add(result);
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
return results;
}
Cross-Environment Copying
Extend the utility for cross-environment operations:
public async Task<string?> CopyToEnvironmentAsync(
string sourceEntryId,
string targetEnvironment,
string targetContentTypeId)
{
// Fetch from source environment
// Transform for target environment
// Create in target environment
}
Best Practices and Considerations
API Rate Limiting
Contentful’s Management API has rate limits. Implement:
- Exponential backoff for failed requests
- Request queuing to avoid overwhelming the API
- Progress tracking for long-running operations
Field Mapping Strategy
Consider creating field mapping configurations:
public class FieldMapping
{
public string SourceField { get; set; }
public string TargetField { get; set; }
public Func<object, object> Transform { get; set; }
}
Error Recovery
Implement robust error handling:
- Partial failure recovery – Continue processing other entries
- Detailed logging – Track what succeeded and failed
- Rollback capabilities – Option to undo changes if needed
Security Considerations
- Never hardcode tokens in production code
- Use environment-specific tokens with minimal required permissions
- Implement token rotation for long-running processes
Usage Example
Here’s how to use the complete utility:
static async Task Main(string[] args)
{
var copyItem = new CopyItem(spaceId, environmentId, managementToken);
// Copy a single entry
var newEntryId = await copyItem.CopyEntryAsync("source123", "targetContentType");
if (newEntryId != null)
{
Console.WriteLine($"✅ Successfully created entry: {newEntryId}");
// Optionally publish the new entry
// await PublishEntryAsync(newEntryId);
}
}
Conclusion
Programmatic entry copying in Contentful opens up powerful content management workflows. This C# utility provides a foundation that you can extend based on your specific needs, whether you’re handling simple content duplication or complex cross-environment migrations.
The key to effective copying lies in understanding your content model, handling different field types appropriately, and implementing robust error handling. With these building blocks, you can automate content operations that would otherwise require hours of manual work.
Remember to test thoroughly in a development environment before running any copying operations on production content. The Management API is powerful, and with great power comes the responsibility to use it wisely.








Leave a Reply