Content
Create, read, update, publish, and delete Agility content items with the Management SDK, in JavaScript and .NET.
The ContentMethods class handles content items: creating, reading, updating, deleting, and moving them through the publishing and approval workflow. Every method is reached from the client instance you created in Getting Started — apiClient.contentMethods in JavaScript, client.contentMethods in .NET — and every method takes the instance guid and a locale in addition to its own arguments.
The JavaScript and .NET SDKs cover the same core operations but differ in two important ways:
Promise<number[]> (an array of content IDs). The .NET equivalents return Task<int?> for single-item operations and Task<List<object?>> for bulk saves. Single and batch saves are separate API endpoints, and a single save returns a single content ID — see Saving a Single Content Item.| Operation | JavaScript | .NET |
|---|---|---|
| Retrieve a content item by ID | getContentItem | GetContentItem |
| List content items in a container | getContentItems (deprecated) | GetContentItems |
| List with advanced filtering | getContentList | — |
| Save a single item (create or update) | saveContentItem | SaveContentItem |
| Save multiple items in bulk | saveContentItems | SaveContentItems |
| Publish an item | publishContent | PublishContent |
| Unpublish an item | unPublishContent | UnPublishContent |
| Request approval | contentRequestApproval | ContentRequestApproval |
| Approve an item | approveContent | ApproveContent |
| Decline an item | declineContent | DeclineContent |
| Delete an item | deleteContent | DeleteContent |
| Bulk workflow on many items at once | batchWorkflowContent | — |
| Retrieve item history | getContentHistory | — |
| Retrieve item comments | getContentComments | — |
When you are working with more than one content item, prefer the bulk methods. They accept arrays — pass all of your items or IDs in a single call rather than looping over the single-item methods.
| Single Item Method | Bulk Method | Use Case |
|---|---|---|
saveContentItem() / SaveContentItem() | saveContentItems() / SaveContentItems() | Creating or updating multiple items |
publishContent() | batchWorkflowContent() (JavaScript only) | Publishing multiple items at once |
unPublishContent() | batchWorkflowContent() (JavaScript only) | Unpublishing multiple items at once |
Both SDKs return content IDs in the same order as the input array, so you can correlate each result with the item you sent. In .NET, bulk workflow operations are not available — save in batches, then publish each item individually.
Retrieves a specific content item by ID and locale.
| Parameter | Type | Required | Description |
|---|---|---|---|
| contentID | number / int? | Yes | The ID of the content item to retrieve |
| guid | string | Yes | The website GUID |
| locale | string | Yes | The locale code (e.g. en-us) |
Returns the content item object — Promise<ContentItem> in JavaScript, Task<ContentItem> in .NET.
const contentItem = await apiClient.contentMethods.getContentItem(123, "your-guid", "en-us")
console.log(contentItem.fields.title)
var contentItem = await client.contentMethods.GetContentItem(123, guid, locale);
Console.WriteLine(contentItem.Fields["title"]);
.NET signature: Task<ContentItem> GetContentItem(int? contentID, string guid, string locale)
Lists content items from a container with pagination and basic filtering.
In JavaScript, getContentItems is deprecated — use getContentList instead. It takes a ListParams object holding take, skip, sortField, and sortDirection. In .NET, GetContentItems is the supported list method and takes the filtering options as discrete optional arguments.
| Parameter | JavaScript | .NET |
|---|---|---|
| referenceName | Yes | Yes |
| guid | Yes | Yes |
| locale | Yes | Yes |
| Pagination / sorting | listParams: ListParams | take, skip, sortField, sortDirection |
| Filtering | via listParams | filter (OData string), fields (comma-separated field names) |
Returns a ContentList containing the items and pagination info.
const listParams = {
take: 10,
skip: 0,
sortField: "title",
sortDirection: "asc",
}
const contentList = await apiClient.contentMethods.getContentItems("articles", "your-guid", "en-us", listParams)
var contentList = await client.contentMethods.GetContentItems(
referenceName: "blog-posts",
guid: guid,
locale: locale,
filter: null, // OData filter string (optional)
fields: null, // Comma-separated field names (optional)
sortDirection: "asc", // "asc" or "desc" (optional)
sortField: "title", // Field to sort by (optional)
take: 50, // Page size (default: 50)
skip: 0 // Record offset (default: 0)
);
Console.WriteLine($"Total: {contentList?.TotalCount}");
foreach (var item in contentList?.Items ?? [])
{
Console.WriteLine(item.ContentID);
}
.NET signature: Task<ContentList?> GetContentItems(string? referenceName, string guid, string locale, string? filter = null, string? fields = null, string? sortDirection = null, string? sortField = null, int? take = 50, int? skip = 0)
getContentList retrieves content items using a POST request with a filter object, giving you more control than getContentItems.
| Parameter | Type | Required | Description |
|---|---|---|---|
| referenceName | string | Yes | The reference name of the content model |
| guid | string | Yes | The website GUID |
| locale | string | Yes | The locale code |
| listParams | ListParams | Yes | Pagination and filtering parameters |
| filterObject | ContentListFilterModel | No | Advanced filter criteria |
const listParams = {
take: 20,
skip: 0,
sortField: "dateCreated",
sortDirection: "desc",
showDeleted: false,
}
const filterObject = {
publishedState: "published",
searchText: "important",
}
const contentList = await apiClient.contentMethods.getContentList(
"articles",
"your-guid",
"en-us",
listParams,
filterObject,
)
take defaults to 50 when you omit it, and skip defaults to 0. To walk a whole container, keep requesting pages with an increasing skip until a page comes back shorter than your take — don't assume one large request captured everything. If you already know which items you want, GET /api/v1/instance/{guid}/{locale}/items?ids=… fetches those specific items by ID instead of listing a container.
Not available in the .NET SDK yet.
Saving is not publishing. A save always writes to Staging. If the item was already Published it drops back to Staging, and your live site keeps serving the previous version until you call Publishing. A save with no publish is the most common reason a write appears to have done nothing.
Creates or updates a single content item. Use contentID: -1 (JavaScript also accepts 0) for new items, or an existing content ID to update.
| Parameter | Type | Required | Description |
|---|---|---|---|
| contentItem | ContentItem | Yes | The content item object to save |
| guid | string | Yes | The website GUID |
| locale | string | Yes | The locale code |
| returnBatchId | boolean | No | JavaScript only — if true, returns the batch ID immediately without waiting |
Saving one item and saving a batch are two distinct Management API endpoints:
POST /api/v1/instance/{guid}/{locale}/item saves one item.POST /api/v1/instance/{guid}/{locale}/item/multi is the batch endpoint, and is the one that deals in multiple items.Both respond with a batch ID, not a content ID. The write is queued, not performed, during your
request. saveContentItem returns the content ID because the SDK polls that batch for you and
hands back the result — see How writes complete.
Pass returnBatchId: true (JavaScript only) to get the batch ID immediately and poll it yourself.
So a single save yields one content ID. .NET surfaces it directly as Task<int>. The JavaScript SDK resolves to that same content ID and may hand it back wrapped in a single-element array (as in the example below), but the authoritative API contract for a single save is one integer, not a list.
For bulk operations, use the batch save methods described below instead.
// Create a new content item
const newItem: ContentItem = {
contentID: -1, // -1 or 0 for new items
properties: {
definitionName: "BlogPost",
referenceName: "blogposts",
},
fields: {
title: "New Article",
content: "Article content...",
author: "John Doe",
publishDate: "2024-01-15",
},
seo: {
metaDescription: "A great new article",
},
}
const savedIds = await apiClient.contentMethods.saveContentItem(newItem, "your-guid", "en-us")
console.log("Created content ID:", savedIds[0])
// Update an existing content item
const existingItem = await apiClient.contentMethods.getContentItem(123, "your-guid", "en-us")
existingItem.fields.title = "Updated Title"
const updatedIds = await apiClient.contentMethods.saveContentItem(existingItem, "your-guid", "en-us")
using agility.models;
// Create a new content item
var newItem = new ContentItem
{
ContentID = -1, // -1 for new items
Properties = new ContentItemProperties
{
DefinitionName = "BlogPost",
ReferenceName = "blog-posts"
},
Fields = new Dictionary<string, object?>
{
{ "title", "New Article" },
{ "slug", "new-article" },
{ "content", "Article content here..." }
}
};
var newContentID = await client.contentMethods.SaveContentItem(newItem, guid, locale);
Console.WriteLine($"Created content ID: {newContentID}");
// Update an existing item
var existing = await client.contentMethods.GetContentItem(123, guid, locale);
existing.Fields["title"] = "Updated Title";
var updatedID = await client.contentMethods.SaveContentItem(existing, guid, locale);
.NET signature: Task<int> SaveContentItem(ContentItem? contentItem, string guid, string locale)
In JavaScript, ContentItem is an interface:
interface ContentItem {
contentID: number // Use -1 or 0 for new items, existing ID for updates
properties: {
definitionName: string // The content model name
referenceName: string // The container reference name
itemOrder?: number // Optional ordering
releaseDate?: string // Optional scheduled release date
pullDate?: string // Optional scheduled pull date
}
fields: {
[key: string]: any // Field values matching the content model
}
seo?: {
// Optional SEO properties
metaDescription?: string
metaKeywords?: string
metaHTML?: string
menuVisible?: boolean
sitemapVisible?: boolean
}
scripts?: {
// Optional custom scripts
top?: string
bottom?: string
}
}
In .NET the equivalent type is ContentItem from the agility.models namespace, where Properties is a ContentItemProperties object (DefinitionName, ReferenceName) and Fields is a Dictionary<string, object?>.
Recommended for bulk operations. Saves multiple content items in a single batch call.
| Parameter | Type | Required | Description |
|---|---|---|---|
| contentItems | ContentItem[] / List<ContentItem?> | Yes | The content items to save |
| guid | string | Yes | The website GUID |
| locale | string | Yes | The locale code |
| returnBatchId | boolean | No | JavaScript only — if true, returns the batch ID immediately without waiting |
Notes that apply to both SDKs:
contentID: -1) and updates (an existing contentID) in the same batch.-1 in the returned array means that item failed to save. Log the failure and check the item manually — a precise per-item error message is not yet returned.JavaScript returns Promise<number[]>; .NET returns Task<List<object?>> holding the content IDs.
const contentItems: ContentItem[] = [
{
contentID: -1,
properties: {definitionName: "BlogPost", referenceName: "blogposts"},
fields: {title: "Article 1", slug: "article-1"},
},
{
contentID: -1,
properties: {definitionName: "BlogPost", referenceName: "blogposts"},
fields: {title: "Article 2", slug: "article-2"},
},
{
contentID: -1,
properties: {definitionName: "BlogPost", referenceName: "blogposts"},
fields: {title: "Article 3", slug: "article-3"},
},
]
const savedIds = await apiClient.contentMethods.saveContentItems(contentItems, "your-guid", "en-us")
// savedIds[0] corresponds to 'Article 1'
// savedIds[1] corresponds to 'Article 2'
// savedIds[2] corresponds to 'Article 3'
console.log("Created IDs:", savedIds)
var items = new List<ContentItem?>
{
new ContentItem
{
ContentID = -1,
Properties = new ContentItemProperties { DefinitionName = "BlogPost", ReferenceName = "blog-posts" },
Fields = new Dictionary<string, object?> { { "title", "Article 1" } }
},
new ContentItem
{
ContentID = -1,
Properties = new ContentItemProperties { DefinitionName = "BlogPost", ReferenceName = "blog-posts" },
Fields = new Dictionary<string, object?> { { "title", "Article 2" } }
}
};
var results = await client.contentMethods.SaveContentItems(items, guid, locale);
// results is a List<object?> containing the content IDs
Console.WriteLine($"Saved IDs: {string.Join(", ", results)}");
.NET signature: Task<List<object?>> SaveContentItems(List<ContentItem?> contentItems, string guid, string locale)
Because the returned IDs keep their input order, you can map them straight back onto your source records.
// Example: Import products and track their new IDs
const products = [
{sku: "SKU-001", name: "Product A", price: 29.99},
{sku: "SKU-002", name: "Product B", price: 49.99},
{sku: "SKU-003", name: "Product C", price: 19.99},
]
const contentItems: ContentItem[] = products.map((product) => ({
contentID: -1,
properties: {definitionName: "Product", referenceName: "products"},
fields: {
sku: product.sku,
name: product.name,
price: product.price,
},
}))
const savedIds = await apiClient.contentMethods.saveContentItems(contentItems, "your-guid", "en-us")
// Map the returned IDs back to original products (same order!)
const productsWithIds = products.map((product, index) => ({
...product,
contentID: savedIds[index],
}))
console.log(productsWithIds)
// [
// { sku: 'SKU-001', name: 'Product A', price: 29.99, contentID: 101 },
// { sku: 'SKU-002', name: 'Product B', price: 49.99, contentID: 102 },
// { sku: 'SKU-003', name: 'Product C', price: 19.99, contentID: 103 }
// ]
const contentItems: ContentItem[] = [
// New item
{
contentID: -1,
properties: {definitionName: "BlogPost", referenceName: "blogposts"},
fields: {title: "Brand New Post"},
},
// Update existing item
{
contentID: 456, // Existing content ID
properties: {definitionName: "BlogPost", referenceName: "blogposts"},
fields: {title: "Updated Existing Post"},
},
]
const savedIds = await apiClient.contentMethods.saveContentItems(contentItems, "your-guid", "en-us")
// savedIds[0] = new ID for 'Brand New Post'
// savedIds[1] = 456 (the updated item retains its ID)
Publishes a single content item through the batch workflow system.
| Parameter | Type | Required | Description |
|---|---|---|---|
| contentID | number / int? | Yes | The ID of the content item to publish |
| guid | string | Yes | The website GUID |
| locale | string | Yes | The locale code |
| comments | string | No | Optional comments for the publish operation |
| returnBatchId | boolean | No | JavaScript only — if true, returns the batch ID immediately without waiting |
JavaScript returns Promise<number[]> containing the published content ID; .NET returns Task<int?>.
In JavaScript, to publish more than one item use batchWorkflowContent() with WorkflowOperationType.Publish rather than looping.
// Publish a single item
const publishedIds = await apiClient.contentMethods.publishContent(123, "your-guid", "en-us", "Publishing update")
console.log("Published ID:", publishedIds[0])
// Return batch ID immediately for custom polling
const batchId = await apiClient.contentMethods.publishContent(123, "your-guid", "en-us", null, true)
var publishedID = await client.contentMethods.PublishContent(
contentID: 123,
guid: guid,
locale: locale,
comments: "Publishing update" // optional
);
Console.WriteLine($"Published ID: {publishedID}");
.NET signature: Task<int?> PublishContent(int? contentID, string guid, string locale, string? comments = null)
Unpublishes a single content item through the batch workflow system. Takes the same parameters as publishing.
In JavaScript, to unpublish more than one item use batchWorkflowContent() with WorkflowOperationType.Unpublish.
const unpublishedIds = await apiClient.contentMethods.unPublishContent(123, "your-guid", "en-us", "Temporary unpublish")
console.log("Unpublished ID:", unpublishedIds[0])
var unpublishedID = await client.contentMethods.UnPublishContent(
contentID: 123,
guid: guid,
locale: locale,
comments: "Temporarily unpublishing"
);
.NET signature: Task<int?> UnPublishContent(int? contentID, string guid, string locale, string? comments = null)
Submits a content item for approval through the workflow system.
| Parameter | Type | Required | Description |
|---|---|---|---|
| contentID | number / int? | Yes | The ID of the content item |
| guid | string | Yes | The website GUID |
| locale | string | Yes | The locale code |
| comments | string | No | Optional comments for the approval request |
| returnBatchId | boolean | No | JavaScript only — if true, returns the batch ID immediately without waiting |
const requestedIds = await apiClient.contentMethods.contentRequestApproval(123, "your-guid", "en-us", "Ready for review")
var id = await client.contentMethods.ContentRequestApproval(123, guid, locale, "Ready for review");
.NET signature: Task<int?> ContentRequestApproval(int? contentID, string guid, string locale, string? comments = null)
Approves a content item in the workflow system. Same parameters as requesting approval.
const approvedIds = await apiClient.contentMethods.approveContent(123, "your-guid", "en-us", "Approved for publication")
var id = await client.contentMethods.ApproveContent(123, guid, locale, "Approved for publication");
.NET signature: Task<int?> ApproveContent(int? contentID, string guid, string locale, string? comments = null)
Declines a content item in the workflow system. Same parameters as approving.
const declinedIds = await apiClient.contentMethods.declineContent(123, "your-guid", "en-us", "Needs revision")
var id = await client.contentMethods.DeclineContent(123, guid, locale, "Needs revision");
.NET signature: Task<int?> DeclineContent(int? contentID, string guid, string locale, string? comments = null)
Deletes a content item through the batch workflow system.
| Parameter | Type | Required | Description |
|---|---|---|---|
| contentID | number / int? | Yes | The ID of the content item to delete |
| guid | string | Yes | The website GUID |
| locale | string | Yes | The locale code |
| comments | string | No | Optional comments for the deletion |
| returnBatchId | boolean | No | JavaScript only — if true, returns the batch ID immediately without waiting |
const deletedIds = await apiClient.contentMethods.deleteContent(123, "your-guid", "en-us", "Removing outdated content")
var deletedID = await client.contentMethods.DeleteContent(
contentID: 123,
guid: guid,
locale: locale,
comments: "Removing outdated content"
);
.NET signature: Task<int?> DeleteContent(int? contentID, string guid, string locale, string? comments = null)
batchWorkflowContent performs a workflow operation on multiple content items at once, and is the recommended approach for bulk publishing, unpublishing, and approvals. Pass all of your content IDs in a single call instead of looping over the single-item methods.
| Parameter | Type | Required | Description |
|---|---|---|---|
| contentIDs | number[] | Yes | Array of ALL content IDs to process (e.g. [101, 102, 103, 104, ...]) |
| guid | string | Yes | The website GUID |
| locale | string | Yes | The locale code |
| operation | WorkflowOperationType | Yes | The workflow operation to perform |
| returnBatchId | boolean | No | If true, returns the batch ID immediately without waiting |
| Value | Description |
|---|---|
WorkflowOperationType.Publish | Publish content items |
WorkflowOperationType.Unpublish | Unpublish content items |
WorkflowOperationType.Approve | Approve content items |
WorkflowOperationType.Decline | Decline content items |
WorkflowOperationType.RequestApproval | Request approval for content items |
Returns Promise<number[]> — the processed content IDs, in the same order as the input array. All items are processed as a single atomic batch operation.
import {WorkflowOperationType} from "@agility/management-sdk"
// Pass ALL content IDs you want to publish in a single array
const contentIDsToPublish = [101, 102, 103, 104, 105, 106, 107, 108, 109, 110]
const publishedIds = await apiClient.contentMethods.batchWorkflowContent(
contentIDsToPublish, // All IDs in one call
"your-guid",
"en-us",
WorkflowOperationType.Publish,
)
// Returns all IDs in the same order: [101, 102, 103, 104, 105, 106, 107, 108, 109, 110]
console.log("Published:", publishedIds)
Unpublishing works the same way:
const contentIDs = [101, 102, 103]
const unpublishedIds = await apiClient.contentMethods.batchWorkflowContent(
contentIDs,
"your-guid",
"en-us",
WorkflowOperationType.Unpublish,
)
// unpublishedIds[0] = 101
// unpublishedIds[1] = 102
// unpublishedIds[2] = 103
A full approval process, batched at each step:
const contentIDs = [201, 202, 203]
// Step 1: Request approval for all items
const requestedIds = await apiClient.contentMethods.batchWorkflowContent(
contentIDs,
"your-guid",
"en-us",
WorkflowOperationType.RequestApproval,
)
console.log("Approval requested for:", requestedIds)
// Step 2: Approve all items (typically done by an approver)
const approvedIds = await apiClient.contentMethods.batchWorkflowContent(
contentIDs,
"your-guid",
"en-us",
WorkflowOperationType.Approve,
)
console.log("Approved:", approvedIds)
// Step 3: Publish approved items
const publishedIds = await apiClient.contentMethods.batchWorkflowContent(
approvedIds,
"your-guid",
"en-us",
WorkflowOperationType.Publish,
)
console.log("Published:", publishedIds)
Declining in bulk:
const contentIDs = [301, 302]
const declinedIds = await apiClient.contentMethods.batchWorkflowContent(
contentIDs,
"your-guid",
"en-us",
WorkflowOperationType.Decline,
)
console.log("Declined content IDs:", declinedIds)
Not available in the .NET SDK yet. In .NET, save in batches and then call
PublishContent(or the relevant workflow method) per item.
Retrieves the history of changes for a specific content item.
| Parameter | Type | Required | Description |
|---|---|---|---|
| locale | string | Yes | The locale code |
| guid | string | Yes | The website GUID |
| contentID | number | Yes | The ID of the content item |
| take | number | No | Number of history entries to retrieve (default: 50) |
| skip | number | No | Number of history entries to skip (default: 0) |
Returns Promise<ContentItemHistory> — the history entries with pagination.
const history = await apiClient.contentMethods.getContentHistory("en-us", "your-guid", 123, 25, 0)
console.log(history.items.length)
Not available in the .NET SDK yet.
Retrieves comments for a specific content item.
| Parameter | Type | Required | Description |
|---|---|---|---|
| locale | string | Yes | The locale code |
| guid | string | Yes | The website GUID |
| contentID | number | Yes | The ID of the content item |
| take | number | No | Number of comments to retrieve (default: 50) |
| skip | number | No | Number of comments to skip (default: 0) |
Returns Promise<ItemComments> — the comments with pagination.
const comments = await apiClient.contentMethods.getContentComments("en-us", "your-guid", 123, 10, 0)
console.log(comments.items.length)
Not available in the .NET SDK yet.
When importing content from an external system or syncing with a third-party data source, follow these guidelines.
batchWorkflowContent() to publish all saved items at once. In .NET, publish per item.Do not use parallel API calls to speed up imports. While it may seem faster, parallel requests can:
Instead, batch your updates in chunks and process the chunks sequentially in a single thread.
A recommended batch size is 50–100 items.
import {WorkflowOperationType} from "@agility/management-sdk"
const BATCH_SIZE = 50
const allSavedIds: number[] = []
for (let i = 0; i < contentItems.length; i += BATCH_SIZE) {
const batch = contentItems.slice(i, i + BATCH_SIZE)
const savedIds = await apiClient.contentMethods.saveContentItems(batch, guid, locale)
allSavedIds.push(...savedIds)
console.log(`Saved batch ${Math.floor(i / BATCH_SIZE) + 1}: ${savedIds.length} items`)
}
// Publish all saved items in batches
for (let i = 0; i < allSavedIds.length; i += BATCH_SIZE) {
const batch = allSavedIds.slice(i, i + BATCH_SIZE)
await apiClient.contentMethods.batchWorkflowContent(batch, guid, locale, WorkflowOperationType.Publish)
console.log(`Published batch ${Math.floor(i / BATCH_SIZE) + 1}: ${batch.length} items`)
}
const int BATCH_SIZE = 50;
var allIds = new List<object?>();
for (int i = 0; i < contentItems.Count; i += BATCH_SIZE)
{
var batch = contentItems.GetRange(i, Math.Min(BATCH_SIZE, contentItems.Count - i));
var savedIds = await client.contentMethods.SaveContentItems(batch, guid, locale);
allIds.AddRange(savedIds);
Console.WriteLine($"Saved batch {i / BATCH_SIZE + 1}: {savedIds.Count} items");
}
// Publish each item
foreach (var idObj in allIds)
{
if (idObj is int id && id > 0)
{
await client.contentMethods.PublishContent(id, guid, locale);
}
}
Page through the existing content list first, build a lookup map, then split your source records into creates and updates before batching. take defaults to 50, so keep requesting pages with an increasing skip until a short page tells you that you have reached the end.
import {WorkflowOperationType} from "@agility/management-sdk"
async function syncExternalData(externalItems: ExternalItem[]) {
const guid = "your-guid"
const locale = "en-us"
// Step 1: Page through the existing content list and build a lookup map.
// take defaults to 50, so keep paging with skip until a short page comes
// back — that way the map really does cover every existing item.
const PAGE_SIZE = 250
const existingMap = new Map<string, number>()
for (let skip = 0; ; skip += PAGE_SIZE) {
const page = await apiClient.contentMethods.getContentList("products", guid, locale, {
take: PAGE_SIZE,
skip,
})
// Build a map for fast duplicate checking (key = external ID or SKU)
for (const item of page.items) {
if (item.fields.externalId) {
existingMap.set(item.fields.externalId, item.contentID)
}
}
if (page.items.length < PAGE_SIZE) break
}
// Step 2: Separate items into creates vs updates
const itemsToCreate: ContentItem[] = []
const itemsToUpdate: ContentItem[] = []
for (const ext of externalItems) {
const existingId = existingMap.get(ext.externalId)
if (existingId) {
// Update existing item
itemsToUpdate.push({
contentID: existingId,
properties: {definitionName: "Product", referenceName: "products"},
fields: {name: ext.name, price: ext.price, externalId: ext.externalId},
})
} else {
// Create new item
itemsToCreate.push({
contentID: -1,
properties: {definitionName: "Product", referenceName: "products"},
fields: {name: ext.name, price: ext.price, externalId: ext.externalId},
})
}
}
// Step 3: Process in batches (recommended batch size: 50-100 items)
const BATCH_SIZE = 50
const allSavedIds: number[] = []
// Process creates in batches
for (let i = 0; i < itemsToCreate.length; i += BATCH_SIZE) {
const batch = itemsToCreate.slice(i, i + BATCH_SIZE)
const savedIds = await apiClient.contentMethods.saveContentItems(batch, guid, locale)
allSavedIds.push(...savedIds)
}
// Process updates in batches
for (let i = 0; i < itemsToUpdate.length; i += BATCH_SIZE) {
const batch = itemsToUpdate.slice(i, i + BATCH_SIZE)
const savedIds = await apiClient.contentMethods.saveContentItems(batch, guid, locale)
allSavedIds.push(...savedIds)
}
// Step 4: Publish all saved items in batches
for (let i = 0; i < allSavedIds.length; i += BATCH_SIZE) {
const batch = allSavedIds.slice(i, i + BATCH_SIZE)
await apiClient.contentMethods.batchWorkflowContent(batch, guid, locale, WorkflowOperationType.Publish)
}
return {
created: itemsToCreate.length,
updated: itemsToUpdate.length,
published: allSavedIds.length,
}
}
This example uses
getContentList()andbatchWorkflowContent(), which are JavaScript-only. In .NET, useGetContentItems()for the lookup andPublishContent()per item.
For smaller datasets or one-time initial imports, page through the existing content list and do duplicate checking locally.
async function initialImport(sourceData: SourceItem[]) {
const guid = "your-guid"
const locale = "en-us"
// For initial imports or small lists: page through everything and check
// locally. take defaults to 50, so loop with skip until a short page
// signals the end — otherwise the duplicate check silently misses items.
const PAGE_SIZE = 250
// Build hashtable/map for O(1) duplicate lookups
const existingBySlug = new Map<string, number>()
for (let skip = 0; ; skip += PAGE_SIZE) {
const page = await apiClient.contentMethods.getContentList("articles", guid, locale, {take: PAGE_SIZE, skip})
for (const item of page.items) {
existingBySlug.set(item.fields.slug, item.contentID)
}
if (page.items.length < PAGE_SIZE) break
}
// Filter to only new items
const newItems = sourceData.filter((item) => !existingBySlug.has(item.slug))
if (newItems.length === 0) {
console.log("No new items to import")
return
}
// Transform and save all new items
const contentItems = newItems.map((item) => ({
contentID: -1,
properties: {definitionName: "Article", referenceName: "articles"},
fields: {title: item.title, slug: item.slug, body: item.body},
}))
const savedIds = await apiClient.contentMethods.saveContentItems(contentItems, guid, locale)
// Publish all at once
const publishedIds = await apiClient.contentMethods.batchWorkflowContent(
savedIds,
guid,
locale,
WorkflowOperationType.Publish,
)
console.log(`Imported and published ${publishedIds.length} new items`)
}
| Scenario | Strategy |
|---|---|
| Large ongoing sync | Page through existing → build map → batch saves → batch publishes |
| Small list / initial import | Page through existing → local duplicate check → single batch save → single batch publish |
| Any import | Always use sequential batching, never parallel requests |
This example creates, saves, and publishes content items in bulk while tracking the correlation between the source records and the resulting content IDs.
import agilityMgmt, {WorkflowOperationType} from "@agility/management-sdk"
async function bulkImportAndPublish() {
const apiClient = agilityMgmt.getApi({
location: "USA",
websiteId: "your-guid",
securityKey: "your-api-key",
})
const guid = "your-guid"
const locale = "en-us"
// Source data to import
const sourceData = [
{externalId: "ext-001", title: "First Article", body: "Content 1..."},
{externalId: "ext-002", title: "Second Article", body: "Content 2..."},
{externalId: "ext-003", title: "Third Article", body: "Content 3..."},
{externalId: "ext-004", title: "Fourth Article", body: "Content 4..."},
]
// Step 1: Transform source data into ContentItem objects
const contentItems = sourceData.map((item) => ({
contentID: -1, // New items
properties: {
definitionName: "Article",
referenceName: "articles",
},
fields: {
title: item.title,
body: item.body,
externalId: item.externalId, // Track original ID in a field
},
}))
// Step 2: Bulk save all items using saveContentItems()
const savedIds = await apiClient.contentMethods.saveContentItems(contentItems, guid, locale)
// savedIds are in the SAME ORDER as contentItems input
console.log("Saved content IDs:", savedIds)
// Step 3: Create a mapping of external IDs to Agility content IDs
const idMapping = sourceData.map((item, index) => ({
externalId: item.externalId,
title: item.title,
contentID: savedIds[index], // Same order guarantees correct mapping
}))
// Step 4: Bulk publish all saved items using batchWorkflowContent()
const publishedIds = await apiClient.contentMethods.batchWorkflowContent(
savedIds,
guid,
locale,
WorkflowOperationType.Publish,
)
// publishedIds are in the SAME ORDER as savedIds input
console.log("Published content IDs:", publishedIds)
return idMapping
}
bulkImportAndPublish()
.then((mapping) => console.log("Import complete!", mapping))
.catch((err) => console.error("Import failed:", err))
Expected output:
Saved content IDs: [1001, 1002, 1003, 1004]
Published content IDs: [1001, 1002, 1003, 1004]
Import complete!
Uses
batchWorkflowContent(), which is JavaScript-only.
All methods throw on failure — Exception objects in JavaScript, ApplicationException in .NET.
try {
const contentItem = await apiClient.contentMethods.getContentItem(123, "your-guid", "en-us")
} catch (error) {
console.error("Failed to get content item:", error.message)
}
try
{
var item = await client.contentMethods.GetContentItem(123, guid, locale);
}
catch (ApplicationException ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
}