Agility CMS documentationAgility CMS documentation
OverviewEditorsDevelopersOwners & AdminsTraining GuideApps
Sign inLet's Chat
Management SDK
Creating Content and Pages in Other LocalesContent Items

Content

Content Items

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:

  • Return shapes. JavaScript workflow methods return 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.
  • Coverage. Bulk workflow operations, advanced list filtering, history, and comments exist only in the JavaScript SDK today.

Method Overview

OperationJavaScript.NET
Retrieve a content item by IDgetContentItemGetContentItem
List content items in a containergetContentItems (deprecated)GetContentItems
List with advanced filteringgetContentList—
Save a single item (create or update)saveContentItemSaveContentItem
Save multiple items in bulksaveContentItemsSaveContentItems
Publish an itempublishContentPublishContent
Unpublish an itemunPublishContentUnPublishContent
Request approvalcontentRequestApprovalContentRequestApproval
Approve an itemapproveContentApproveContent
Decline an itemdeclineContentDeclineContent
Delete an itemdeleteContentDeleteContent
Bulk workflow on many items at oncebatchWorkflowContent—
Retrieve item historygetContentHistory—
Retrieve item commentsgetContentComments—

Bulk Operations Overview

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 MethodBulk MethodUse 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.


Retrieving a Content Item

Retrieves a specific content item by ID and locale.

ParameterTypeRequiredDescription
contentIDnumber / int?YesThe ID of the content item to retrieve
guidstringYesThe website GUID
localestringYesThe 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)


Listing Content Items

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.

ParameterJavaScript.NET
referenceNameYesYes
guidYesYes
localeYesYes
Pagination / sortinglistParams: ListParamstake, skip, sortField, sortDirection
Filteringvia listParamsfilter (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)


Listing with Advanced Filtering

getContentList retrieves content items using a POST request with a filter object, giving you more control than getContentItems.

ParameterTypeRequiredDescription
referenceNamestringYesThe reference name of the content model
guidstringYesThe website GUID
localestringYesThe locale code
listParamsListParamsYesPagination and filtering parameters
filterObjectContentListFilterModelNoAdvanced 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 a Single Content Item

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.

ParameterTypeRequiredDescription
contentItemContentItemYesThe content item object to save
guidstringYesThe website GUID
localestringYesThe locale code
returnBatchIdbooleanNoJavaScript 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)

The ContentItem Shape

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?>.


Saving Multiple Content Items (Bulk)

Recommended for bulk operations. Saves multiple content items in a single batch call.

ParameterTypeRequiredDescription
contentItemsContentItem[] / List<ContentItem?>YesThe content items to save
guidstringYesThe website GUID
localestringYesThe locale code
returnBatchIdbooleanNoJavaScript only — if true, returns the batch ID immediately without waiting

Notes that apply to both SDKs:

  • Order preservation: the returned IDs are in the same order as the input, so you can correlate each returned ID with its input item.
  • Performance: for 2+ items, always use the bulk method instead of looping over the single-item method.
  • Mixed operations: you can mix new items (contentID: -1) and updates (an existing contentID) in the same batch.
  • Failures: a -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)

Correlating Results with Input Data

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 }
// ]

Mixed Create and Update Operations

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)

Publishing

Publishes a single content item through the batch workflow system.

ParameterTypeRequiredDescription
contentIDnumber / int?YesThe ID of the content item to publish
guidstringYesThe website GUID
localestringYesThe locale code
commentsstringNoOptional comments for the publish operation
returnBatchIdbooleanNoJavaScript 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)


Unpublishing

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)


Requesting Approval

Submits a content item for approval through the workflow system.

ParameterTypeRequiredDescription
contentIDnumber / int?YesThe ID of the content item
guidstringYesThe website GUID
localestringYesThe locale code
commentsstringNoOptional comments for the approval request
returnBatchIdbooleanNoJavaScript 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)


Approving

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)


Declining

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)


Deleting

Deletes a content item through the batch workflow system.

ParameterTypeRequiredDescription
contentIDnumber / int?YesThe ID of the content item to delete
guidstringYesThe website GUID
localestringYesThe locale code
commentsstringNoOptional comments for the deletion
returnBatchIdbooleanNoJavaScript 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)


Bulk Workflow Operations

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.

ParameterTypeRequiredDescription
contentIDsnumber[]YesArray of ALL content IDs to process (e.g. [101, 102, 103, 104, ...])
guidstringYesThe website GUID
localestringYesThe locale code
operationWorkflowOperationTypeYesThe workflow operation to perform
returnBatchIdbooleanNoIf true, returns the batch ID immediately without waiting

WorkflowOperationType Values

ValueDescription
WorkflowOperationType.PublishPublish content items
WorkflowOperationType.UnpublishUnpublish content items
WorkflowOperationType.ApproveApprove content items
WorkflowOperationType.DeclineDecline content items
WorkflowOperationType.RequestApprovalRequest 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.


Content Item History

Retrieves the history of changes for a specific content item.

ParameterTypeRequiredDescription
localestringYesThe locale code
guidstringYesThe website GUID
contentIDnumberYesThe ID of the content item
takenumberNoNumber of history entries to retrieve (default: 50)
skipnumberNoNumber 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.


Content Item Comments

Retrieves comments for a specific content item.

ParameterTypeRequiredDescription
localestringYesThe locale code
guidstringYesThe website GUID
contentIDnumberYesThe ID of the content item
takenumberNoNumber of comments to retrieve (default: 50)
skipnumberNoNumber 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.


Best Practices for Imports and Syncs

When importing content from an external system or syncing with a third-party data source, follow these guidelines.

  1. Do all lookups first — check which items already exist before making any changes.
  2. Batch your saves — use the bulk save method to write multiple items in a single call.
  3. Batch your publishes — in JavaScript, use batchWorkflowContent() to publish all saved items at once. In .NET, publish per item.
  4. Use single-threaded processing — process batches sequentially, not in parallel.

Why Avoid Parallelism?

Do not use parallel API calls to speed up imports. While it may seem faster, parallel requests can:

  • Overwhelm the API and cause rate limiting
  • Lead to race conditions and inconsistent state
  • Result in batch conflicts and failed operations

Instead, batch your updates in chunks and process the chunks sequentially in a single thread.

Batch Save and Publish Pattern

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);
    }
}

Strategy for Large Ongoing Syncs

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() and batchWorkflowContent(), which are JavaScript-only. In .NET, use GetContentItems() for the lookup and PublishContent() per item.

Strategy for Small Lists or Initial Imports

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`)
}

Summary

ScenarioStrategy
Large ongoing syncPage through existing → build map → batch saves → batch publishes
Small list / initial importPage through existing → local duplicate check → single batch save → single batch publish
Any importAlways use sequential batching, never parallel requests

Complete Bulk Workflow Example

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.


Error Handling

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}");
}
← Previous
Creating Content and Pages in Other Locales
On this page
Method OverviewBulk Operations OverviewRetrieving a Content ItemListing Content ItemsListing with Advanced FilteringSaving a Single Content ItemSaving Multiple Content Items (Bulk)PublishingUnpublishingRequesting ApprovalApprovingDecliningDeletingBulk Workflow OperationsContent Item HistoryContent Item CommentsBest Practices for Imports and SyncsComplete Bulk Workflow ExampleError Handling
Agility CMS documentationAgility CMS documentation

Documentation for the CMS built for editors, developers, and AI agents.

Docs
  • Overview
  • Editors
  • Developers
  • Owners & Admins
  • Training Guide
  • Changelog
Resources
  • Get Support
  • MCP Server
  • System Status
  • llms.txt
Agility
  • agilitycms.com
  • Start Free Trial
  • Sign in
  • Blog
© 2026 Agility Inc. All rights reserved.
Privacy PolicyTerms of Service