# Containers & Lists

> Source: https://agilitycms.com/docs/javascript/management-sdk/containers

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.

## Method reference

| 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.

## Reading containers

### Get all containers

Returns every container in the instance.

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
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}`);
});
```

```csharp
var containers = await client.containerMethods.GetContainerList(guid);

foreach (var container in containers)
{
    Console.WriteLine($"{container.ReferenceName} (ID: {container.ContentViewID})");
}
```

</div>

**.NET signature:** `Task<List<Container?>> GetContainerList(string guid)`

### Get containers (paged)

Returns a paginated result with a total count, and supports filtering by container type and modification date. This one is **JavaScript only**.

```ts
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/paged` directly instead.

### Get a container by ID

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
const container = await apiClient.containerMethods.getContainerByID(
  contentViewID, // Container ID
  guid          // Instance GUID
);

console.log('Container details:', container);
```

```csharp
var container = await client.containerMethods.GetContainerById(contentViewID, guid);
Console.WriteLine($"Container: {container?.ReferenceName}");
```

</div>

**.NET signature:** `Task<Container?> GetContainerById(int? id, string guid)`

> Note the casing difference: JavaScript exports `getContainerByID`, .NET exposes `GetContainerById`.

### Get a container by reference name

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.

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
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');
}
```

```csharp
var container = await client.containerMethods.GetContainerByReferenceName("BlogPosts", guid);

if (container != null)
{
    Console.WriteLine($"Found: {container.ReferenceName} (ID: {container.ContentViewID})");
}
```

</div>

**.NET signature:** `Task<Container?> GetContainerByReferenceName(string? referenceName, string guid)`

### Get containers by model

Finds every container based on a specific content model — useful before changing or deleting a model.

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
const containers = await apiClient.containerMethods.getContainersByModel(modelId, guid);
containers.forEach(c => console.log(c.referenceName));
```

```csharp
var containers = await client.containerMethods.GetContainersByModel(modelId, guid);

foreach (var container in containers)
{
    Console.WriteLine($"Container: {container?.ReferenceName}");
}
```

</div>

**.NET signature:** `Task<List<Container?>> GetContainersByModel(int? modelId, string guid)`

### Get container security settings

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
const security = await apiClient.containerMethods.getContainerSecurity(contentViewID, guid);
```

```csharp
var security = await client.containerMethods.GetContainerSecurity(contentViewID, guid);
```

</div>

**.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.

### Get container notifications

Returns the notification recipients configured on a container.

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
const notifications = await apiClient.containerMethods.getNotificationList(contentViewID, guid);
notifications.forEach(n => console.log(n.emailAddress));
```

```csharp
var notifications = await client.containerMethods.GetNotificationList(contentViewID, guid);

foreach (var notification in notifications)
{
    Console.WriteLine(notification?.EmailAddress);
}
```

</div>

**.NET signature:** `Task<List<Notification?>> GetNotificationList(int? id, string guid)`

## Creating and updating containers

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.

### Container fields

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 `settings` object on a container.** All of these are top-level properties. If you've seen a `settings: { ... }` 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.

> **`contentDefinitionTypeID` is a model type, not a model ID.** The Management API publishes the numeric values at [`GET /api/v1/types`](https://mgmt.aglty.io/api/v1/types) under `contentModelTypes`: `Item = 0`, `List = 1`, `Module = 2`. The JavaScript SDK also ships a `ContentDefinitionTypeID` enum, but its numbering does not currently line up with the API's — so pass the integer from `/api/v1/types` rather 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:

```ts
const existing = await apiClient.containerMethods.getContainerByReferenceName('BlogPosts', guid);
console.log(JSON.stringify(existing, null, 2)); // copy the shape from a container that works
```

### Create a container

Look up the content model first so you can link the container to it.

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
// 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);
```

```csharp
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}");
```

</div>

**.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.

### Update a container

Retrieve the container, change the properties you need, and save it back. Keep the existing `contentViewID` so the save is treated as an update.

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
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);
}
```

```csharp
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}");
}
```

</div>

> 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.

### Point a container at a different model

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
const newModel = await apiClient.modelMethods.getModelByReferenceName('Article', guid);

existing.contentDefinitionID = newModel.id;

await apiClient.containerMethods.saveContainer(existing, guid, false);
```

```csharp
var newModel = await client.modelMethods.GetModelByReferenceName("Article", guid);

existing.ContentDefinitionID = newModel.ID;

await client.containerMethods.SaveContainer(existing, guid);
```

</div>

> 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.

## Deleting containers

### Delete a container

Delete by container ID.

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
await apiClient.containerMethods.deleteContainer(contentViewID, guid);
console.log('Container deleted successfully');
```

```csharp
var result = await client.containerMethods.DeleteContainer(contentViewID, guid);
Console.WriteLine($"Deleted: {result}");
```

</div>

**.NET signature:** `Task<string?> DeleteContainer(int? id, string guid)`

### Delete only when the container is empty

Check the container's content list before deleting so you don't remove a container that still holds items.

```ts
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;
}
```

> `getContentList` is JavaScript only. From .NET, use `GetContentItems` to check for items before deleting.

## Naming reference names

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:

- Use letters and numbers only. `BlogPosts`, `FeaturedPosts`, `ProductCatalog`.
- Mixed case is fine, and common — Agility's own containers use PascalCase. Just be aware that the Fetch API **lowercases reference names on read**, so don't rely on casing to tell two containers apart.
- Name by purpose, not position: `FeaturedPosts`, not `Container1`.
- Keep the container name close to its model name so the relationship is obvious.

```ts
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;
}
```

## Patterns

### Create a container only if it doesn't already exist

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.

```ts
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
  );
}
```

### Create containers in bulk

Run the create-if-missing helper over a list of container/model pairs, collecting per-item results instead of failing the whole batch.

```ts
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
);
```

## Error handling

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
try {
  const container = await apiClient.containerMethods.getContainerByReferenceName('BlogPosts', guid);
} catch (error) {
  console.error('Error retrieving container:', error);
}
```

```csharp
try
{
    var container = await client.containerMethods.GetContainerByReferenceName("BlogPosts", guid);
}
catch (ApplicationException ex)
{
    Console.Error.WriteLine($"Error: {ex.Message}");
}
```

</div>
