| path | docs/api/Collection_API.mdx | ||||
|---|---|---|---|---|---|
| title | Collection API Reference | ||||
| description | Complete API documentation for SveltyCMS Collections - endpoints, parameters, response formats, and advanced features | ||||
| order | 41 | ||||
| icon | mdi:api | ||||
| author | admin | ||||
| created | 2025-08-06 | ||||
| updated | 2025-10-02 | ||||
| tags |
|
The Collection API provides comprehensive endpoints for managing collection entries in SveltyCMS. This API supports advanced features including pagination, filtering, sorting, batch operations, and multi-tenant isolation.
- Base Endpoints
- Authentication & Authorization
- Common Parameters
- Entry Management
- Batch Operations
- Error Handling
- Performance Optimization
- Multi-Tenant Support
All collection API endpoints follow the pattern:
/api/collections/[collectionId]/[operation]
| Method | Endpoint | Description | Notes |
|---|---|---|---|
GET |
/api/collections |
List all collections | ✅ API endpoint (metadata only) |
GET |
❌ REMOVED - Use +page.server.ts load() |
||
POST |
/api/collections/{collectionId} |
Create new entry | ✅ Client-side mutation |
GET |
❌ REMOVED - Use +page.server.ts load() |
||
PATCH |
/api/collections/{collectionId}/{entryId} |
Update entry | ✅ Client-side mutation |
DELETE |
/api/collections/{collectionId}/{entryId} |
Delete entry | ✅ Client-side mutation (returns 204) |
POST |
/api/collections/{collectionId}/batch |
Batch operations | ✅ Client-side mutation |
PATCH |
/api/collections/{collectionId}/{entryId}/status |
Update entry status | ✅ Client-side mutation |
GET |
/api/collections/{collectionId}/{entryId}/revisions |
Get revision history | ✅ API endpoint |
POST |
/api/collections/{collectionId}/import |
Import data (JSON/CSV) | ✅ Client-side mutation |
GET |
/api/collections/{collectionId}/export |
Export data (JSON/CSV) | ✅ API endpoint |
Authorization: Bearer <jwt_token>
Content-Type: application/json- Admin: Full access to all operations
- User: Limited to published content (non-admin users)
- Role-based: Permissions defined by user role configuration
In multi-tenant mode, all operations are automatically scoped to the tenant context from the JWT token.
collectionId(string, required): The unique identifier of the collectionentryId(string, optional): The unique identifier of a specific entry
{
page?: number; // Page number (default: 1)
pageSize?: number; // Items per page (default: 25)
}{
filter?: string; // JSON string of filter criteria
}Filter Examples:
// Simple equality
{ "status": "published" }
// Negation operator
{ "status": "!=deleted" }
// Multiple conditions
{
"status": "published",
"category": "news"
}{
sortField?: string; // Field to sort by (default: "createdAt")
sortDirection?: "asc" | "desc"; // Sort direction (default: "desc")
}Alternative Sorting (Legacy):
{
sort?: string; // JSON string: {"fieldName": 1|-1}
}{
contentLanguage?: string; // Language code for translated content
_langChange?: number; // Timestamp for cache invalidation
_cacheBust?: number; // Timestamp for cache busting
}Retrieves metadata for all collections accessible to the authenticated user.
Request:
GET /api/collections?includeFields=true&includeStats=falseQuery Parameters:
includeFields(boolean, optional) - Include field schemas in response (default:false)includeStats(boolean, optional) - Include entry statistics (default:false)
Response:
{
"success": true,
"data": {
"collections": [
{
"id": "23d772dd3783492a9115ee9ea6bc6185",
"name": "posts",
"label": "Posts",
"description": "Blog posts collection",
"icon": "mdi:post",
"path": "/posts",
"permissions": {
"read": true,
"write": true
},
"fields": [],
"stats": {
"totalEntries": 0,
"publishedEntries": 0,
"draftEntries": 0
}
}
],
"total": 1
},
"performance": {
"duration": 12.5
}
}+page.server.ts load() function for SSR data loading instead.
For initial page loads, use SvelteKit's load function:
// +page.server.ts
export const load: PageServerLoad = async ({ locals, params }) => {
const entries = await dbAdapter
.queryBuilder(`collection_${collectionId}`)
.where({ tenantId: locals.tenantId })
.paginate({ page: 1, pageSize: 25 })
.execute();
return { entries: entries.data };
};Request:
GET /api/collections/posts?page=1&pageSize=10&filter={"status":"published"}Response:
{
"success": true,
"data": {
"items": [
{
"_id": "entry123",
"title": "Sample Post",
"content": "Post content...",
"status": "published",
"createdAt": "2025-08-06T10:00:00Z",
"updatedAt": "2025-08-06T10:00:00Z"
}
],
"total": 150,
"page": 1,
"pageSize": 10,
"totalPages": 15
},
"performance": {
"duration": 45.2
}
}Request:
POST /api/collections/posts
Content-Type: application/json
{
"title": "New Post",
"content": "Post content...",
"status": "draft",
"tags": ["technology", "web"]
}Response:
{
"success": true,
"data": {
"_id": "entry456",
"title": "New Post",
"content": "Post content...",
"status": "draft",
"tags": ["technology", "web"],
"createdAt": "2025-08-06T10:30:00Z",
"updatedAt": "2025-08-06T10:30:00Z",
"createdBy": "user123",
"updatedBy": "user123"
}
}Request:
PATCH /api/collections/posts/entry456
Content-Type: application/json
{
"title": "Updated Post Title",
"status": "published"
}Response:
{
"success": true,
"data": {
"_id": "entry456",
"title": "Updated Post Title",
"status": "published",
"updatedAt": "2025-08-06T11:00:00Z",
"updatedBy": "user123"
}
}Request:
DELETE /api/collections/posts/entry456Response:
HTTP/1.1 204 No ContentNo response body is returned. A successful deletion returns HTTP status 204 (No Content).
Request:
POST /api/collections/posts/batch
Content-Type: application/json
{
"action": "status",
"entryIds": ["entry1", "entry2", "entry3"],
"status": "publish"
}Note: Valid status values are: publish, unpublish, draft, archived.
**Available Actions:**
- `status`: Update status of multiple entries (requires `status` field: `publish`, `unpublish`, `draft`, `archived`)
- `delete`: Delete multiple entries
- `clone`: Clone multiple entries (requires `cloneCount` field)
**Response:**
```json
{
"success": true,
"data": {
"processed": 3,
"successful": 3,
"failed": 0,
"results": [
{
"entryId": "entry1",
"success": true,
"operation": "status_update"
}
]
}
}
Request:
POST /api/collections/posts/batch
Content-Type: application/json
{
"action": "delete",
"entryIds": ["entry1", "entry2", "entry3"]
}Response:
{
"success": true,
"message": "3 entries deleted successfully",
"data": {
"action": "delete",
"results": [
{
"entryId": "entry1",
"success": true
}
],
"summary": {
"total": 3,
"successful": 3,
"failed": 0
}
}
}{
"success": false,
"error": "Error message",
"code": "ERROR_CODE",
"details": {
"field": "validation error details"
}
}| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or missing authentication |
FORBIDDEN |
403 | Insufficient permissions |
NOT_FOUND |
404 | Collection or entry not found |
VALIDATION_ERROR |
400 | Invalid request data |
SERVER_ERROR |
500 | Internal server error |
{
"success": false,
"error": "Validation failed",
"details": {
"title": "Title is required",
"email": "Invalid email format"
}
}The API implements a two-tier caching system for optimal performance:
- Pattern:
collection:{collectionId}:page:{page}:size:{pageSize}:filter:{...}:sort:{...}:mode:{mode}:tenant:{tenantId} - TTL: 300 seconds (5 minutes)
- Usage: Caches server-rendered collection pages
- Invalidation: Automatic on any data mutation (create, update, delete, status change)
- Pattern:
api:userId:/api/collections/{collectionId}* - TTL: 300 seconds (5 minutes)
- Usage: Caches API responses for read operations
- Invalidation: Automatic on data mutations
All mutation endpoints automatically invalidate cached data:
// Pattern used to clear all cached pages for a collection
const cachePattern = `collection:${collectionId}:*`;
await cacheService.clearByPattern(cachePattern);Endpoints that trigger cache invalidation:
POST /collections/{collectionId}(create entry)PATCH /collections/{collectionId}/{entryId}(update entry)DELETE /collections/{collectionId}/{entryId}(delete entry)PATCH /collections/{collectionId}/{entryId}/status(update status)POST /collections/{collectionId}/batch(batch operations)
This ensures that after any data change, subsequent requests will fetch fresh data from the database.
- Indexes: Automatic indexing on
_id,status,createdAt,tenantId - Projection: Only requested fields returned
- Pagination: Efficient limit/skip with total count
All API responses include performance metrics:
{
"performance": {
"duration": 45.2, // milliseconds
"cacheHit": false,
"queryTime": 38.5
}
}When MULTI_TENANT=true, all operations are automatically scoped by tenantId:
// Automatically applied to all queries
const filter = {
...userFilter,
tenantId: extractedFromJWT
};- Data isolation: Complete separation of tenant data
- Permission isolation: Role-based access within tenant
- Collection isolation: Tenant-specific collection schemas
All collection entries use centralized TypeScript types from @src/content/types.ts:
import type { CollectionEntry, Schema, StatusType } from '@src/content/types';
// CollectionEntry interface extends Record<string, unknown> with standard fields:
interface CollectionEntry extends Record<string, unknown> {
_id: string;
status: StatusType;
createdAt: string;
updatedAt: string;
createdBy: string;
updatedBy: string;
tenantId?: string;
}
// Status types
type StatusType = 'draft' | 'published' | 'archived' | 'scheduled' | 'unpublished';This ensures type consistency across the entire codebase and simplifies maintenance.
All entries are processed through the widget system for:
- Field validation: Based on widget configuration
- Data transformation: Custom widget processing
- Permission checks: Field-level access control
GET /api/collections/posts/entry123/revisionsReturns complete revision history for audit trails.
Built-in status system supports:
draft: Unpublished contentpublished: Public contentarchived: Soft-deleted contentscheduled: Time-based publishing- Custom statuses via collection configuration
- Translated fields: Automatic language detection
- Content language: Request-specific language content
- Fallback languages: Configurable fallback chain
import { getData } from '@utils/apiClient';
// List entries with filters
const entries = await getData({
collectionId: 'posts',
page: 1,
pageSize: 10,
filter: JSON.stringify({ status: 'published' }),
contentLanguage: 'en'
});const response = await fetch('/api/collections/posts', {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();# List entries
curl -X GET "https://api.example.com/api/collections/posts?page=1&pageSize=5" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json"
# Create entry
curl -X POST "https://api.example.com/api/collections/posts" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"New Post","content":"Content here","status":"published"}'
# Update entry
curl -X PATCH "https://api.example.com/api/collections/posts/entry123" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Updated Title","status":"published"}'
# Batch delete
curl -X POST "https://api.example.com/api/collections/posts/batch" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"delete","entryIds":["id1","id2"]}'
# Batch status update
curl -X POST "https://api.example.com/api/collections/posts/batch" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"status","entryIds":["id1","id2"],"status":"published"}'The Collection API has comprehensive integration tests located in tests/bun/api/collections.test.ts.
Test Coverage:
| Test Area | Tests | Description |
|---|---|---|
| Authentication | 6 | Verify auth requirements for all endpoints |
| Collection Listing | 2 | Test GET /api/collections with various parameters |
| Content Structure | 3 | Test collection metadata and recompilation |
| Search Functionality | 3 | Test POST /api/search with different queries |
| CRUD Operations | 4 | Test create, read, update, delete operations |
| Data Export | 1 | Test GET /api/exportData endpoint |
| Total | 19 | Comprehensive API coverage |
# Run all collection tests
bun test tests/bun/api/collections.test.ts
# Run with verbose output
bun test tests/bun/api/collections.test.ts --verbose
# Run specific test suite
bun test tests/bun/api/collections.test.ts -t "RESTful Collection Operations"// Tests verify that all endpoints require authentication
it('should fail without authentication', async () => {
const response = await fetch(`${API_BASE_URL}/api/collections`);
expect(response.status).toBe(401);
});it('should list all collections with admin authentication', async () => {
const response = await fetch(`${API_BASE_URL}/api/collections`, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(200);
const result = await response.json();
expect(result.success).toBe(true);
expect(result.data.collections).toBeDefined();
});// Create entry
it('should create a new entry', async () => {
const response = await fetch(`${API_BASE_URL}/api/collections/${collectionId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`
},
body: JSON.stringify({ title: 'Test Post', content: 'Test content' })
});
expect(response.status).toBe(200);
expect(result.success).toBe(true);
});
// Get entries with pagination
it('should get collection entries with pagination', async () => {
const response = await fetch(`${API_BASE_URL}/api/collections/${collectionId}?page=1&pageSize=10`, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(200);
});it('should search content across collections', async () => {
const response = await fetch(`${API_BASE_URL}/api/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`
},
body: JSON.stringify({ query: 'test', collections: [] })
});
expect(response.status).toBe(200);
expect(result.success).toBe(true);
});it('should return 404 for invalid collection ID', async () => {
const response = await fetch(`${API_BASE_URL}/api/collections/invalid-collection-id`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`
},
body: JSON.stringify({ title: 'Test Post' })
});
expect(response.status).toBe(404);
});curl -X GET "http://localhost:5173/api/collections" \
-H "Authorization: Bearer YOUR_TOKEN"curl -X POST "http://localhost:5173/api/collections/23d772dd3783492a9115ee9ea6bc6185" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Test Post",
"content": "This is test content",
"status": "published"
}'curl -X GET "http://localhost:5173/api/collections/23d772dd3783492a9115ee9ea6bc6185?page=1&pageSize=10" \
-H "Authorization: Bearer YOUR_TOKEN"curl -X POST "http://localhost:5173/api/search" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "test",
"collections": []
}'curl -X POST "http://localhost:5173/api/content-structure" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "recompile"}'The test suite uses the following setup pattern:
beforeEach(async () => {
// Clean database to ensure test isolation
await cleanupTestDatabase();
// Create admin user and get authentication token
authToken = await loginAsAdminAndGetToken();
});For testing collection operations in your own code:
import { getData, postData, patchData, deleteData } from '@utils/apiClient';
// Test creating an entry
const newEntry = await postData({
endpoint: `/api/collections/${collectionId}`,
data: { title: 'Test', content: 'Content' }
});
// Test retrieving entries
const entries = await getData({
endpoint: `/api/collections/${collectionId}`,
params: { page: 1, pageSize: 10 }
});
// Test updating an entry
const updated = await patchData({
endpoint: `/api/collections/${collectionId}/${entryId}`,
data: { title: 'Updated Title' }
});
// Test deleting an entry
await deleteData({
endpoint: `/api/collections/${collectionId}/${entryId}`
});| Endpoint | Method | Coverage |
|---|---|---|
/api/collections |
GET | ✅ Tested |
/api/collections/{id} |
GET | ✅ Tested |
/api/collections/{id} |
POST | ✅ Tested |
/api/collections/{id}/{entryId} |
GET | |
/api/collections/{id}/{entryId} |
PATCH | |
/api/collections/{id}/{entryId} |
DELETE | |
/api/collections/{id}/batch |
POST | ❌ Needs tests |
/api/collections/{id}/export |
GET | |
/api/collections/{id}/import |
POST | ❌ Needs tests |
/api/search |
POST | ✅ Tested |
/api/content-structure |
GET | ✅ Tested |
/api/content-structure |
POST | ✅ Tested |
/api/exportData |
GET | ✅ Tested |
Legend:
- ✅ Tested: Comprehensive test coverage
⚠️ Partial: Basic tests exist, needs expansion- ❌ Needs tests: No test coverage yet
- Use pagination: Always specify reasonable
pageSizelimits - Optimize filters: Use indexed fields in filter conditions
- Cache responses: Implement client-side caching for repeated queries
- Batch operations: Use batch endpoints for multiple operations
- Validate input: Always validate and sanitize user input
- Permission checks: Verify user permissions before operations
- Rate limiting: Implement rate limiting for public APIs
- HTTPS only: Use secure connections in production
- Atomic operations: Use transactions for related operations
- Validation: Implement comprehensive data validation
- Backup strategy: Regular backups of critical data
- Audit trails: Enable revision tracking for important collections
This API provides a robust foundation for content management with features including performance optimization, security, and scalability.