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 the containerMethods group of the Management SDK client — apiClient.containerMethods in JavaScript, client.containerMethods in .NET.
| Operation | JavaScript | .NET |
|---|---|---|
| List all containers | getContainerList(guid) | GetContainerList(guid) |
| List containers (paged) | getContainerListPaged(...) | not available |
| Get by ID | getContainerByID(id, guid) | GetContainerById(id, guid) |
| Get by reference name | getContainerByReferenceName(referenceName, guid) | GetContainerByReferenceName(referenceName, guid) |
| Get containers by model | getContainersByModel(modelId, guid) | GetContainersByModel(modelId, guid) |
| Get security settings | getContainerSecurity(id, guid) | GetContainerSecurity(id, guid) |
| Get notifications | getNotificationList(id, guid) | GetNotificationList(id, guid) |
| Create or update | saveContainer(container, guid, forceReferenceName) | SaveContainer(container, guid) |
| Delete | deleteContainer(id, guid) | DeleteContainer(id, guid) |
Only one method differs between the SDKs: paged listing exists in JavaScript and not in .NET. Everything else is available in both.
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.containerMethods.GetContainerList(guid);
foreach (var container in containers)
{
Console.WriteLine($"{container.ReferenceName} (ID: {container.ContentViewID})");
}
.NET signature: Task<List<Container?>> GetContainerList(string guid)
Returns a paginated result with a total count, and supports filtering by container type and modification date. This one is JavaScript only.
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));
Signature: getContainerListPaged(guid: string, pageSize?: number, recordOffset?: number, contentType?: ContentViewType, includeModules?: boolean, updatedSince?: Date): Promise<PagedResult<Container>>
| 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. |
Not available in the .NET SDK. Call
GET /api/v1/instance/{guid}/container/list/pageddirectly instead.
const container = await apiClient.containerMethods.getContainerByID(
contentViewID, // Container ID
guid // Instance GUID
);
console.log('Container details:', container);
var container = await client.containerMethods.GetContainerById(contentViewID, guid);
Console.WriteLine($"Container: {container?.ReferenceName}");
.NET signature: Task<Container?> GetContainerById(int? id, string guid)
Note the casing difference: JavaScript exports
getContainerByID, .NET exposesGetContainerById.
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.containerMethods.GetContainerByReferenceName("BlogPosts", guid);
if (container != null)
{
Console.WriteLine($"Found: {container.ReferenceName} (ID: {container.ContentViewID})");
}
.NET signature: Task<Container?> GetContainerByReferenceName(string? referenceName, string guid)
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.containerMethods.GetContainersByModel(modelId, guid);
foreach (var container in containers)
{
Console.WriteLine($"Container: {container?.ReferenceName}");
}
.NET signature: Task<List<Container?>> GetContainersByModel(int? modelId, string guid)
const security = await apiClient.containerMethods.getContainerSecurity(contentViewID, guid);
var security = await client.containerMethods.GetContainerSecurity(contentViewID, guid);
.NET signature: Task<Container?> GetContainerSecurity(int? id, string guid)
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.containerMethods.GetNotificationList(contentViewID, guid);
foreach (var notification in notifications)
{
Console.WriteLine(notification?.EmailAddress);
}
.NET signature: Task<List<Notification?>> GetNotificationList(int? id, string guid)
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.models;
// First, get the content model to link
var model = await client.modelMethods.GetModelByReferenceName("BlogPost", guid);
var newContainer = new Container
{
ContentViewID = -1,
ReferenceName = "BlogPosts",
ContentViewName = "Blog Posts",
Title = "Blog Posts",
ContentDefinitionID = model.ID,
ContentDefinitionTypeID = ContentDefinitionTypeID.List
};
var saved = await client.containerMethods.SaveContainer(newContainer, guid);
Console.WriteLine($"Created container: {saved?.ReferenceName}");
.NET signature: Task<Container?> SaveContainer(Container container, string guid)
The JavaScript saveContainer takes a third forceReferenceName argument. When false, Agility may adjust the reference name to keep it unique; when true, the reference name you supplied is used as-is. There is no .NET equivalent.
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.containerMethods.GetContainerByReferenceName("BlogPosts", guid);
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.containerMethods.SaveContainer(existing, guid);
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.modelMethods.GetModelByReferenceName("Article", guid);
existing.ContentDefinitionID = newModel.ID;
await client.containerMethods.SaveContainer(existing, guid);
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');
var result = await client.containerMethods.DeleteContainer(contentViewID, guid);
Console.WriteLine($"Deleted: {result}");
.NET signature: Task<string?> DeleteContainer(int? id, string guid)
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;
}
getContentListis JavaScript only. From .NET, useGetContentItemsto check for items before 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.containerMethods.GetContainerByReferenceName("BlogPosts", guid);
}
catch (ApplicationException ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
}