What is your developer-dependent CMS actually costing you? Calculate your costs and get a full report
What is your developer-dependent CMS actually costing you? Calculate your costs and get a full report
Containers
Read, create, update, and delete Agility containers (content lists) with the Management SDK, in both JavaScript and .NET.
In Agility CMS a Container is a content list. It links a content model to the content items stored against it, and its reference name is what you query when fetching content. A container can hold many items, or be configured to hold a single item.
Content Model (BlogPost) → Container (BlogPosts) → Content Items
All container operations live on apiClient.containerMethods in JavaScript and on client.Containers in .NET.
The .NET examples on this page use
Agility.Management.SDK2.0. If you are on 1.x, see Migrating to 2.0 for the method names.
| Operation | JavaScript | .NET |
|---|---|---|
| List all containers | getContainerList(guid) | GetContainerListAsync(guid) |
| List containers (paged) | getContainerListPaged(...) | GetContainerListPagedAsync(guid, options) |
| Get by ID | getContainerByID(id, guid) | GetContainerAsync(guid, id) |
| Get by reference name | getContainerByReferenceName(referenceName, guid) | GetContainerByReferenceNameAsync(guid, referenceName) |
| Get containers by model | getContainersByModel(modelId, guid) | GetContainersByModelAsync(guid, modelId) |
| Get security settings | getContainerSecurity(id, guid) | GetContainerSecurityAsync(guid, id) |
| Get notifications | getNotificationList(id, guid) | GetNotificationsAsync(guid, id) |
| Create or update | saveContainer(container, guid, forceReferenceName) | SaveContainerAsync(guid, container, forceReferenceName) |
| Delete | deleteContainer(id, guid) | DeleteContainerAsync(guid, id) |
Every operation is available in both SDKs. In .NET the instance GUID always comes first.
Returns every container in the instance.
const containers = await apiClient.containerMethods.getContainerList(guid);
containers.forEach(container => {
console.log(`Container: ${container.referenceName}`);
console.log(`- Model ID: ${container.contentDefinitionID}`);
console.log(`- Container ID: ${container.contentViewID}`);
});
var containers = await client.Containers.GetContainerListAsync(guid);
foreach (var container in containers)
{
Console.WriteLine($"{container.ReferenceName} (ID: {container.ContentViewID})");
}
.NET signature: Task<List<ContentContainer>> GetContainerListAsync(string instanceGuid, DateTime? updatedSince = null, CancellationToken cancellationToken = default)
Returns a paginated result with a total count, and supports filtering by container type and modification date.
import { ContentViewType } from '@agility/management-sdk';
const paged = await apiClient.containerMethods.getContainerListPaged(
guid,
20, // pageSize
0, // recordOffset
ContentViewType.All, // contentType
true, // includeModules
new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) // updatedSince (optional)
);
console.log('Total containers:', paged.totalCount);
paged.items.forEach(c => console.log(c.referenceName));
using Agility.Management.Sdk.Clients;
using Agility.Management.Sdk.Models;
var paged = await client.Containers.GetContainerListPagedAsync(guid, new ContainerListOptions
{
PageSize = 20,
RecordOffset = 0,
ContentType = ContentViewType.All,
IncludeModules = true,
UpdatedSince = DateTime.UtcNow.AddDays(-7), // optional
});
Console.WriteLine($"Total containers: {paged.TotalCount}");
foreach (var c in paged.Items ?? [])
{
Console.WriteLine(c.ReferenceName);
}
Signature: getContainerListPaged(guid: string, pageSize?: number, recordOffset?: number, contentType?: ContentViewType, includeModules?: boolean, updatedSince?: Date): Promise<PagedResult<Container>>
.NET signature: Task<ContentContainerPagedResult> GetContainerListPagedAsync(string instanceGuid, ContainerListOptions options = null, CancellationToken cancellationToken = default). The ContainerListOptions properties are PageSize, RecordOffset, ContentType, IncludeModules and UpdatedSince.
| Parameter | Default | Description |
|---|---|---|
guid | — | Instance GUID. |
pageSize | 20 | Number of containers per page. |
recordOffset | 0 | Number of records to skip. |
contentType | All | Filters the type of container returned. ContentViewType values are All, Shared, Linked, DynamicPageList. |
includeModules | true | Whether module (component) containers are included. |
updatedSince | — | Only return containers modified after this date. |
REST:
GET /api/v1/instance/{guid}/container/list/paged.
const container = await apiClient.containerMethods.getContainerByID(
contentViewID, // Container ID
guid // Instance GUID
);
console.log('Container details:', container);
var container = await client.Containers.GetContainerAsync(guid, contentViewID);
Console.WriteLine($"Container: {container?.ReferenceName}");
.NET signature: Task<ContentContainer> GetContainerAsync(string instanceGuid, int containerId, CancellationToken cancellationToken = default)
Note the naming difference: JavaScript exports
getContainerByID, .NET exposesGetContainerAsync.
This is the usual way to look up a container, since the reference name is the identifier you use elsewhere in the API. The JavaScript method is typed to return Container | null and resolves to null on a 404 rather than throwing, so check the result before using it.
const container = await apiClient.containerMethods.getContainerByReferenceName(
'BlogPosts', // Container reference name
guid // Instance GUID
);
if (container) {
console.log('Found container:', container.referenceName);
} else {
console.log('Container not found');
}
var container = await client.Containers.GetContainerByReferenceNameAsync(guid, "BlogPosts");
if (container != null)
{
Console.WriteLine($"Found: {container.ReferenceName} (ID: {container.ContentViewID})");
}
.NET signature: Task<ContentContainer> GetContainerByReferenceNameAsync(string instanceGuid, string referenceName, CancellationToken cancellationToken = default)
Finds every container based on a specific content model — useful before changing or deleting a model.
const containers = await apiClient.containerMethods.getContainersByModel(modelId, guid);
containers.forEach(c => console.log(c.referenceName));
var containers = await client.Containers.GetContainersByModelAsync(guid, modelId);
foreach (var container in containers)
{
Console.WriteLine($"Container: {container?.ReferenceName}");
}
.NET signature: Task<List<ContentContainer>> GetContainersByModelAsync(string instanceGuid, int modelId, CancellationToken cancellationToken = default)
const security = await apiClient.containerMethods.getContainerSecurity(contentViewID, guid);
var security = await client.Containers.GetContainerSecurityAsync(guid, contentViewID);
.NET signature: Task<ContentContainer> GetContainerSecurityAsync(string instanceGuid, int containerId, CancellationToken cancellationToken = default)
The returned Container carries the currentUserCan* flags — currentUserCanEdit, currentUserCanDelete, currentUserCanPublish, and so on — which tell you what the authenticated token is allowed to do with this container.
Returns the notification recipients configured on a container.
const notifications = await apiClient.containerMethods.getNotificationList(contentViewID, guid);
notifications.forEach(n => console.log(n.emailAddress));
var notifications = await client.Containers.GetNotificationsAsync(guid, contentViewID);
foreach (var notification in notifications)
{
Console.WriteLine(notification?.EmailAddress);
}
.NET signature: Task<List<Notification>> GetNotificationsAsync(string instanceGuid, int containerId, CancellationToken cancellationToken = default)
Both SDKs use a single save method for create and update. Set the container ID to -1 to create a new container; pass an existing container ID to update that container.
The Container object is wide — these are the fields that matter when creating one:
| JavaScript | .NET | Description |
|---|---|---|
contentViewID | ContentViewID | Container ID. Use -1 for a new container. |
referenceName | ReferenceName | Unique reference name used to query the container. |
contentViewName | ContentViewName | The container's name. |
title | Title | Display title shown in the CMS. |
contentDefinitionID | ContentDefinitionID | ID of the content model the container is based on. |
contentDefinitionTypeID | ContentDefinitionTypeID | The model's type — not its ID. See the note below. |
defaultSortColumn | DefaultSortColumn | Column the CMS listing sorts by. |
defaultSortDirection | DefaultSortDirection | asc or desc. |
numRowsInListing | NumRowsInListing | Rows shown per page in the CMS listing. |
isDynamicPageList | IsDynamicPageList | Whether the container drives dynamic pages. |
requiresApproval | RequiresApproval | Whether items need approval before publishing. |
There is no
settingsobject on a container. All of these are top-level properties. If you've seen asettings: { ... }payload in older examples, it was never part of the contract — the API ignores it, so sort order and page size set that way silently do nothing.
contentDefinitionTypeIDis a model type, not a model ID. The Management API publishes the numeric values atGET /api/v1/typesundercontentModelTypes:Item = 0,List = 1,Module = 2. The JavaScript SDK also ships aContentDefinitionTypeIDenum, but its numbering does not currently line up with the API's — so pass the integer from/api/v1/typesrather than relying on the enum, and check the value on an existing container before creating a new one.
The most reliable way to build a container payload is to read one you already have and mirror its shape:
const existing = await apiClient.containerMethods.getContainerByReferenceName('BlogPosts', guid);
console.log(JSON.stringify(existing, null, 2)); // copy the shape from a container that works
Look up the content model first so you can link the container to it.
// First, get the content model
const model = await apiClient.modelMethods.getModelByReferenceName('BlogPost', guid);
if (!model) {
throw new Error('Content model not found');
}
const containerPayload = {
contentViewID: -1, // -1 for new containers
referenceName: 'BlogPosts',
contentViewName: 'Blog Posts',
title: 'Blog Posts',
contentDefinitionID: model.id, // link to the model
contentDefinitionTypeID: 1 // 1 = List (see /api/v1/types)
};
const savedContainer = await apiClient.containerMethods.saveContainer(
containerPayload,
guid,
false // forceReferenceName
);
console.log('Created container:', savedContainer.referenceName);
using Agility.Management.Sdk.Models;
// First, get the content model to link
var model = await client.Models.GetModelByReferenceNameAsync(guid, "BlogPost");
var newContainer = new ContentContainer
{
ContentViewID = -1,
ReferenceName = "BlogPosts",
ContentViewName = "Blog Posts",
Title = "Blog Posts",
ContentDefinitionID = model.Id,
ContentDefinitionTypeID = 1 // 1 = List (see /api/v1/types)
};
var saved = await client.Containers.SaveContainerAsync(guid, newContainer, forceReferenceName: false);
Console.WriteLine($"Created container: {saved?.ReferenceName}");
.NET signature: Task<ContentContainer> SaveContainerAsync(string instanceGuid, ContentContainer container, bool? forceReferenceName = null, CancellationToken cancellationToken = default)
Both SDKs take a forceReferenceName argument: the third argument of saveContainer in JavaScript, and an optional bool? on SaveContainerAsync in .NET. When false, Agility may adjust the reference name to keep it unique; when true, the reference name you supplied is used as-is.
Retrieve the container, change the properties you need, and save it back. Keep the existing contentViewID so the save is treated as an update.
const existing = await apiClient.containerMethods.getContainerByReferenceName(
'BlogPosts',
guid
);
if (existing) {
existing.title = 'Blog Posts (Archive)';
existing.numRowsInListing = 50;
existing.defaultSortColumn = 'Date';
existing.defaultSortDirection = 'desc';
const updated = await apiClient.containerMethods.saveContainer(existing, guid, false);
console.log('Updated container:', updated.referenceName);
}
var existing = await client.Containers.GetContainerByReferenceNameAsync(guid, "BlogPosts");
if (existing != null)
{
existing.Title = "Blog Posts (Archive)";
existing.NumRowsInListing = 50;
existing.DefaultSortColumn = "Date";
existing.DefaultSortDirection = "desc";
// Keep the existing ContentViewID so this is treated as an update
var updated = await client.Containers.SaveContainerAsync(guid, existing);
Console.WriteLine($"Updated container: {updated?.ReferenceName}");
}
Read the container first and mutate what you got back. Constructing a partial object and saving it can blank out fields you didn't set.
const newModel = await apiClient.modelMethods.getModelByReferenceName('Article', guid);
existing.contentDefinitionID = newModel.id;
await apiClient.containerMethods.saveContainer(existing, guid, false);
var newModel = await client.Models.GetModelByReferenceNameAsync(guid, "Article");
existing.ContentDefinitionID = newModel.Id;
await client.Containers.SaveContainerAsync(guid, existing);
Repointing a container at a model with different fields leaves existing items holding values the new model doesn't define. Check the field overlap first.
Delete by container ID.
await apiClient.containerMethods.deleteContainer(contentViewID, guid);
console.log('Container deleted successfully');
// Returns no value. A failure throws AgilityManagementException.
await client.Containers.DeleteContainerAsync(guid, contentViewID);
.NET signature: Task DeleteContainerAsync(string instanceGuid, int containerId, CancellationToken cancellationToken = default)
Check the container's content list before deleting so you don't remove a container that still holds items.
async function safeDeleteContainer(containerName: string, guid: string, locale: string) {
const container = await apiClient.containerMethods.getContainerByReferenceName(
containerName,
guid
);
if (!container) {
console.log('Container not found');
return false;
}
const contentList = await apiClient.contentMethods.getContentList(
containerName,
guid,
locale,
{ take: 1, skip: 0 }
);
if (contentList.totalCount > 0) {
console.warn(`Cannot delete container - contains ${contentList.totalCount} content items`);
return false;
}
await apiClient.containerMethods.deleteContainer(container.contentViewID, guid);
return true;
}
From .NET, do the same check with
client.Content.GetContentListAsync(guid, locale, containerName, new ContentListOptions { Take = 1, Skip = 0 })and readTotalCountbefore deleting.
Reference names are the identifier every API call uses, so they're worth getting right the first time — renaming one breaks every query that used it.
Avoid hyphens. This is the one that bites hardest, and it isn't obvious. A container named Blog-Posts works fine in the CMS and through the Management API, but the GraphQL API derives its field names from the reference name, and a hyphenated container ends up unqueryable there — the content simply doesn't appear. We hit this on this very documentation site: a container named ManagementSDK-Articles returned its items over REST and zero items over GraphQL, with no error to explain why. Renaming it to ManagementSDKArticles fixed it.
Beyond that:
BlogPosts, FeaturedPosts, ProductCatalog.FeaturedPosts, not Container1.function validateReferenceName(name: string): string[] {
const errors: string[] = [];
if (!name) {
errors.push('Reference name is required');
}
if (name.includes('-')) {
errors.push('Hyphens break GraphQL queries — use letters and numbers only');
}
if (name && !/^[A-Za-z][A-Za-z0-9]*$/.test(name)) {
errors.push('Must start with a letter and contain only letters and numbers');
}
return errors;
}
Look up the model, check for an existing container by reference name, and create it only when missing. This makes the operation safe to re-run.
async function createContainerForModel(
modelReferenceName: string,
containerReferenceName: string,
guid: string
) {
const model = await apiClient.modelMethods.getModelByReferenceName(
modelReferenceName,
guid
);
if (!model) {
throw new Error(`Model '${modelReferenceName}' not found`);
}
const existing = await apiClient.containerMethods.getContainerByReferenceName(
containerReferenceName,
guid
);
if (existing) {
console.log('Container already exists:', existing.referenceName);
return existing;
}
return apiClient.containerMethods.saveContainer(
{
contentViewID: -1,
referenceName: containerReferenceName,
contentViewName: containerReferenceName,
title: containerReferenceName,
contentDefinitionID: model.id,
contentDefinitionTypeID: 1
},
guid,
true // force the reference name we asked for
);
}
Run the create-if-missing helper over a list of container/model pairs, collecting per-item results instead of failing the whole batch.
async function createMultipleContainers(
configs: Array<{ containerName: string; modelName: string }>,
guid: string
) {
const results = [];
for (const config of configs) {
try {
const container = await createContainerForModel(
config.modelName,
config.containerName,
guid
);
results.push({ success: true, ...config, containerID: container.contentViewID });
} catch (error) {
results.push({ success: false, ...config, error: (error as Error).message });
}
}
return results;
}
const results = await createMultipleContainers(
[
{ containerName: 'BlogPosts', modelName: 'BlogPost' },
{ containerName: 'StaticPages', modelName: 'StaticPage' },
{ containerName: 'Products', modelName: 'ProductCatalog' }
],
guid
);
try {
const container = await apiClient.containerMethods.getContainerByReferenceName('BlogPosts', guid);
} catch (error) {
console.error('Error retrieving container:', error);
}
try
{
var container = await client.Containers.GetContainerByReferenceNameAsync(guid, "BlogPosts");
}
catch (AgilityManagementException ex)
{
Console.Error.WriteLine($"Error ({ex.StatusCode}): {ex.ApiMessage ?? ex.Message}");
}