Skip to content

Assets API

The Assets API manages the media library — uploading files, organizing them in folders, serving them to the browser, and tracking where assets are used across content. Assets are stored in a configurable directory alongside content files.

All endpoints are relative to the API base URL (default: /__cms/api).


List All Assets

GET /assets

List all assets in the media library as a flat list.

Parameters: None

Response: 200 OK

{
  assets: AssetEntry[]
}

Where AssetEntry is:

interface AssetEntry {
  filename: string;
  originalName: string;
  mimeType: string;
  size: number;
  width?: number; // Image dimensions (if applicable)
  height?: number;
  alt?: string; // Alt text for accessibility
  uploadedAt: string; // ISO date
  uploadedBy?: string; // Email of uploader
  folder?: string; // Folder path (default: '/')
  tags?: string[]; // Categorization tags
}

Example:

curl http://localhost:4321/__cms/api/assets

List Folder Contents

GET /assets/folders

List assets and subfolders within a specific folder. Use this for folder-based navigation in the media library UI.

Parameters:

NameInTypeRequiredDescription
pathquerystringnoFolder path to list (default: /)

Response: 200 OK

{
  assets: AssetEntry[]
  folders: Array<{
    name: string   // Folder name
    path: string   // Full folder path
  }>
}

Errors:

StatusCodeDescription
400INVALID_REQUESTPath traversal detected in folder path

Example:

# List root folder
curl http://localhost:4321/__cms/api/assets/folders

# List a subfolder
curl "http://localhost:4321/__cms/api/assets/folders?path=/images/blog"

Get Folder Tree

GET /assets/folder-tree

Get the complete folder hierarchy with asset counts for each folder. Useful for rendering a folder tree sidebar.

Parameters: None

Response: 200 OK

{
  tree: FolderTreeNode[]
}

Where FolderTreeNode is:

interface FolderTreeNode {
  name: string;
  path: string;
  assetCount: number;
  children: FolderTreeNode[];
}

Example:

curl http://localhost:4321/__cms/api/assets/folder-tree
{
  "tree": [
    {
      "name": "images",
      "path": "/images",
      "assetCount": 5,
      "children": [
        {
          "name": "blog",
          "path": "/images/blog",
          "assetCount": 3,
          "children": []
        }
      ]
    }
  ]
}

Get Asset Metadata

GET /assets/meta/:path

Get metadata for a single asset by path. The :path parameter supports subfolder paths (e.g., images/hero.jpg).

Parameters:

NameInTypeRequiredDescription
pathpathstringyesAsset path, including subfolders (e.g., hero.jpg or images/hero.jpg)

Response: 200 OK

Returns an AssetEntry object.

Errors:

StatusCodeDescription
404ASSET_NOT_FOUNDNo asset with this filename

Example:

curl http://localhost:4321/__cms/api/assets/meta/hero-image.jpg

# Asset in a subfolder
curl http://localhost:4321/__cms/api/assets/meta/images/blog/photo.jpg

Get Asset Usage

GET /assets/usage/:path

Get information about where an asset is referenced across content (which pages or blocks use it). Useful for determining whether an asset can be safely deleted. The :path parameter supports subfolder paths.

Parameters:

NameInTypeRequiredDescription
pathpathstringyesAsset path, including subfolders (e.g., hero.jpg or images/hero.jpg)

Response: 200 OK

{
  usage: Array<{
    page: string; // Content ID or pathname
    field: string; // Field where the asset is referenced
  }>;
}

Example:

curl http://localhost:4321/__cms/api/assets/usage/hero-image.jpg
{
  "usage": [
    { "page": "/about", "field": "ogImage" },
    { "page": "/blog/post-1", "field": "content" }
  ]
}

Serve Asset File

GET /assets/serve/:path

Serve the actual asset file for display in the browser. Supports files in subfolders via the path parameter. Returns the file with appropriate Content-Type and long-lived cache headers.

Parameters:

NameInTypeRequiredDescription
pathpathstringyesRelative path to the asset (e.g., image.jpg or images/blog/photo.jpg)

Response: 200 OK

Headers:

  • Content-Type: <mime-type>
  • Cache-Control: public, max-age=31536000, immutable

Body: Raw file contents

Errors:

StatusCodeDescription
404ASSET_NOT_FOUNDFile not found at the specified path

Example:

# Serve a file from root
curl http://localhost:4321/__cms/api/assets/serve/hero-image.jpg

# Serve a file from a subfolder
curl http://localhost:4321/__cms/api/assets/serve/images/blog/photo.jpg

Upload Asset

POST /assets/upload

Upload a file to the media library using multipart form data. The file is stored with a sanitized filename and optional metadata.

Content-Type: multipart/form-data

Form Fields:

NameTypeRequiredDescription
fileFileyesThe file to upload
folderstringnoTarget folder path (default: root)
altstringnoAlt text for accessibility
uploadedBystringnoEmail of the uploader
widthstringnoImage width in pixels
heightstringnoImage height in pixels

Response: 201 Created

Returns the created AssetEntry.

Errors:

StatusCodeDescription
400INVALID_REQUESTNo file provided in the file field
400ASSET_UPLOAD_FAILEDUpload validation failed

Example:

curl -X POST http://localhost:4321/__cms/api/assets/upload \
  -F "file=@./image.jpg" \
  -F "folder=images/blog" \
  -F "alt=Blog hero image"
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('folder', 'images/blog');
formData.append('alt', 'Blog hero image');

const response = await fetch('/__cms/api/assets/upload', {
  method: 'POST',
  body: formData,
});
const asset = await response.json();

Import Asset from URL

POST /assets/import-url

Import an asset from an external URL. The file is downloaded and stored in the media library.

Request Body:

{
  url: string         // URL to download from (required)
  alt?: string        // Alt text
  uploadedBy?: string // Uploader email
  folder?: string     // Target folder
}

Response: 201 Created

Returns the created AssetEntry.

Errors:

StatusCodeDescription
400INVALID_REQUESTurl is missing
400ASSET_UPLOAD_FAILEDDownload or import failed

Example:

curl -X POST http://localhost:4321/__cms/api/assets/import-url \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/photo.jpg",
    "alt": "External photo",
    "folder": "images/imported"
  }'

Move Assets

POST /assets/move

Move one or more assets between folders.

Request Body:

{
  filenames: string[]    // Array of filenames to move
  sourceFolder: string   // Current folder path
  targetFolder: string   // Destination folder path
}

Response: 200 OK

{
  success: true;
  moved: number; // Count of files actually moved
}

Errors:

StatusCodeDescription
400INVALID_REQUESTInvalid filenames, sourceFolder, or targetFolder
400INVALID_REQUESTPath traversal detected

Example:

curl -X POST http://localhost:4321/__cms/api/assets/move \
  -H "Content-Type: application/json" \
  -d '{
    "filenames": ["photo1.jpg", "photo2.jpg"],
    "sourceFolder": "/",
    "targetFolder": "/images/archive"
  }'

Create Folder

POST /assets/folders

Create a new folder in the media library.

Request Body:

{
  path: string; // Folder path to create (e.g., '/images/new-folder')
}

Response: 201 Created

{
  success: true;
}

Errors:

StatusCodeDescription
400INVALID_REQUESTpath is missing or folder already exists

Example:

curl -X POST http://localhost:4321/__cms/api/assets/folders \
  -H "Content-Type: application/json" \
  -d '{ "path": "/images/2024" }'

Update Asset Metadata

PATCH /assets/meta/:path

Update metadata for an existing asset (alt text, tags). The :path parameter supports subfolder paths.

Parameters:

NameInTypeRequiredDescription
pathpathstringyesAsset path, including subfolders (e.g., hero.jpg or images/hero.jpg)

Request Body:

{
  alt?: string      // Updated alt text
  tags?: string[]   // Updated tags
}

Response: 200 OK

Returns the updated AssetEntry.

Errors:

StatusCodeDescription
404ASSET_NOT_FOUNDAsset not found

Example:

curl -X PATCH http://localhost:4321/__cms/api/assets/meta/hero-image.jpg \
  -H "Content-Type: application/json" \
  -d '{ "alt": "Updated alt text", "tags": ["hero", "homepage"] }'

Delete Asset

DELETE /assets/meta/:path

Delete an asset from the media library. Check usage first with the usage endpoint to avoid breaking references. The :path parameter supports subfolder paths.

Parameters:

NameInTypeRequiredDescription
pathpathstringyesAsset path to delete, including subfolders (e.g., hero.jpg or images/hero.jpg)

Response: 200 OK

{
  success: true;
}

Errors:

StatusCodeDescription
404ASSET_NOT_FOUNDAsset not found

Example:

curl -X DELETE http://localhost:4321/__cms/api/assets/meta/old-image.jpg

Error Codes

CodeDescription
ASSET_NOT_FOUNDAsset file does not exist
ASSET_UPLOAD_FAILEDUpload or import operation failed
ASSET_INVALID_FORMATFile format not supported
ASSET_TOO_LARGEFile exceeds the size limit
INVALID_REQUESTInvalid parameters or path traversal detected
INTERNAL_ERRORUnexpected server error