Repository navigation
Import Export
oCMS provides comprehensive import and export functionality to backup, migrate, or transfer content between installations.
The import/export system supports:
- JSON Export: Portable JSON format for content data
- ZIP Export: JSON plus media files in a single archive
- Selective Export: Choose which content types to include
- Conflict Resolution: Handle duplicates during import
- Navigate to Admin > Export (administrators only)
- Select content types to include
- Tick Include Media Files (it needs Media ticked) to get a ZIP archive; otherwise you get a JSON file
- Click Download Export (Download ZIP Archive when media files are included)
| Option | Description |
|---|---|
| Pages | Page content, SEO metadata, and translation links |
| Page Status Filter | All pages, published only, or drafts only |
| Categories | Hierarchical category structure |
| Tags | Tag definitions |
| Media | Media metadata (ZIP includes files) |
| Menus | Menu structures and items |
| Forms | Form definitions (optionally with submissions) |
| Users | User accounts (passwords are never exported) |
| Languages | Language configuration |
| Site Config | Site settings |
Creates a single .json file containing all selected content. Best for smaller sites, API consumption, or version control.
Creates a .zip archive containing export.json (content data) and media/<storage>/<uuid>/<filename> (every original and variant of the selected media). Best for complete site backups, migration between servers, or sites with media files.
Limits: the compressed ZIP may be at most 100 MiB, with at most 5,000 media file entries, 32 MiB per entry and 512 MiB of expanded media in total. Exports obey the same limits, so oCMS does not create an archive it would refuse to import.
{
"version": "1.0",
"exported_at": "2024-01-15T10:30:00Z",
"site": {
"name": "My Site",
"description": "Optional site description",
"url": "https://example.com"
},
"languages": [...],
"users": [...],
"categories": [...],
"tags": [...],
"pages": [...],
"media": [...],
"menus": [...],
"forms": [...],
"config": {...}
}{
"id": 123,
"title": "About Us",
"slug": "about-us",
"body": "<p>Content here...</p>",
"status": "published",
"author_email": "admin@example.com",
"categories": ["company", "info"],
"tags": ["about", "company"],
"seo": {
"meta_title": "About Our Company",
"meta_description": "Learn about us",
"meta_keywords": "company,about",
"og_image": {"uuid": "550e8400-e29b-41d4-a716-446655440000", "filename": "og-image.jpg"},
"no_index": false,
"no_follow": false,
"canonical_url": ""
},
"featured_image": {"uuid": "550e8400-e29b-41d4-a716-446655440001", "filename": "hero.jpg"},
"translations": {
"ru": 456
},
"language_code": "en",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"published_at": "2024-01-10T12:00:00Z"
}- Navigate to Admin > Import (administrators only)
- Upload your export file (JSON or ZIP, at most 100 MiB)
- Click Validate File to preview changes. The uploaded file is kept for two hours on the instance that received it, so a load-balanced deployment must route the validate and import steps of one session to the same instance (sticky sessions)
- Configure import options
- Click Start Import
| Option | Description |
|---|---|
| Conflict Strategy | How to handle existing content |
| Content Types | Which types to import (checkboxes) |
| Import Media Files | Copy the media files from a ZIP archive into the uploads directory (ZIP only) |
| Dry Run | Preview without making changes |
When importing content that already exists (matched by slug or email):
| Strategy | Behavior |
|---|---|
| Skip | Keep existing, ignore imported |
| Overwrite | Replace existing with imported |
| Rename | Import with modified slug (adds -1, -2, etc.); users are skipped instead, since the email is their identity |
Users that do not exist yet are created with a random, unknown password; an administrator must set a new one under Admin > Users before they can sign in. Overwriting an existing user updates the name and role, but never grants or removes the admin role: that is changed on the user form, which asks for the acting administrator's password. Users whose role was kept are listed under Notes in the import result. When users are imported, an archive with a role other than admin, editor, public or the older name viewer (imported as public) fails validation; an import that leaves users out ignores their roles.
Content is imported in this order to maintain relationships:
- Languages
- Users
- Categories
- Tags
- Media
- Pages
- Menus
- Forms
- Site config
The validator checks for:
- Valid JSON format
- Required fields present
- Reference integrity (authors, categories exist)
- File format compatibility
Example validation output:
Found:
- 15 pages
- 5 categories
- 10 tags
- 50 media files
Warnings:
- Page "about-us" already exists (will skip)
- Category "news" already exists (will overwrite)
Errors:
- Page 5 references non-existent author "unknown@email.com"
- Export from source: Select all content types, choose ZIP format, download
- Prepare target: Install oCMS, run migrations, create admin user
- Import to target: Upload ZIP, validate, set conflict strategy to "Overwrite", import
- Verify: Check pages render correctly, verify media files load, test translation links
To migrate only specific content:
- Export with only needed content types selected
- Import to target with "Skip" conflict strategy
- Manually resolve any conflicts
For automated backups, consider:
- SQLite database backup (direct file copy)
- Uploads directory backup
- Periodic export via API
- Export includes all content types
- ZIP format includes media files
- Backup stored in separate location
- Periodic restore tests
- Backup rotation policy
Export is a form post that needs an administrator session cookie (named session in development and __Host-session in production). Each content type is switched on with include_<type>=on; there is no format field, and ZIP output is chosen with include_media=on&include_media_files=on.
curl -X POST "http://localhost:8080/admin/export" \
-H "Cookie: session=your-session-cookie" \
-d "include_pages=on&include_media=on" \
-o export.jsonImport is two steps in one session: upload the file to /admin/import/validate (multipart field import_file), then post the options to /admin/import. The upload is staged server-side for the second step, so the same session cookie must be used for both.
curl -c cookies.txt -b "session=your-session-cookie" -X POST "http://localhost:8080/admin/import/validate" \
-F "import_file=@export.json"
curl -b cookies.txt -X POST "http://localhost:8080/admin/import" \
-d "conflict_strategy=skip&import_pages=on&import_categories=on&import_tags=on"- Regular backups: Export weekly at minimum
- Test restores: Periodically test importing to a test instance
- Version exports: Include date in filenames
- Use ZIP for migration: Ensures media files are included
- Validate first: Always validate before importing
- Backup before import: Export existing content before importing new
- Review changes: Check validation output carefully
"Invalid JSON format"
- Ensure the file is valid JSON
- Check for file corruption during transfer
- Try re-exporting from source
"Reference not found"
- Import in correct order (users before pages)
- Ensure referenced content exists
- Check author emails match
"Media file missing"
- Use ZIP export/import for media
- Verify media directory permissions
- Check file paths in export
For very large sites:
- Split the import: Export/import content types separately
- Keep media batches within the archive limits: 100 MiB compressed, 5,000 files, 32 MiB per file and 512 MiB expanded media per ZIP
- Validate each batch before importing it
{
"version": "1.0",
"exported_at": "datetime",
"site": {
"name": "string",
"description": "string",
"url": "string"
},
"languages": [{
"code": "string",
"name": "string",
"native_name": "string",
"direction": "ltr|rtl",
"is_default": "boolean",
"is_active": "boolean",
"position": "number"
}],
"users": [{
"email": "string",
"name": "string",
"role": "string",
"created_at": "datetime"
}],
"categories": [{
"id": "number",
"name": "string",
"slug": "string",
"description": "string",
"parent_slug": "string",
"position": "number",
"language_code": "string",
"translations": {"lang_code": "category_id"},
"created_at": "datetime",
"updated_at": "datetime"
}],
"tags": [{
"id": "number",
"name": "string",
"slug": "string",
"language_code": "string",
"translations": {"lang_code": "tag_id"},
"created_at": "datetime",
"updated_at": "datetime"
}],
"pages": [{
"id": "number",
"title": "string",
"slug": "string",
"body": "string",
"status": "draft|published",
"author_email": "string",
"categories": ["string"],
"tags": ["string"],
"seo": {
"meta_title": "string",
"meta_description": "string",
"meta_keywords": "string",
"og_image": {"uuid": "string", "filename": "string"},
"no_index": "boolean",
"no_follow": "boolean",
"canonical_url": "string"
},
"featured_image": {"uuid": "string", "filename": "string"},
"translations": {"lang_code": "page_id"},
"language_code": "string",
"created_at": "datetime",
"updated_at": "datetime",
"published_at": "datetime|null",
"scheduled_at": "datetime|null",
"video_url": "string",
"video_title": "string"
}],
"media": [{
"uuid": "string",
"filename": "string",
"mime_type": "string",
"size": "number",
"width": "number|null",
"height": "number|null",
"alt": "string",
"caption": "string",
"folder_path": "string",
"uploaded_by": "email",
"language_code": "string",
"file_path": "media/originals/<uuid>/<filename> (ZIP only)",
"variants": [{
"type": "string",
"width": "number",
"height": "number",
"size": "number",
"file_path": "media/<variant>/<uuid>/<filename> (ZIP only)"
}],
"created_at": "datetime"
}],
"menus": [{
"id": "number",
"name": "string",
"slug": "string",
"language_code": "string",
"items": [{
"id": "number",
"title": "string",
"url": "string",
"target": "string",
"page_slug": "string",
"css_class": "string",
"is_active": "boolean",
"position": "number",
"children": ["menu item, nested the same way"]
}],
"created_at": "datetime",
"updated_at": "datetime"
}],
"forms": [{
"id": "number",
"name": "string",
"slug": "string",
"title": "string",
"description": "string",
"success_message": "string",
"email_to": "string",
"is_active": "boolean",
"language_code": "string",
"translations": {"lang_code": "form_id"},
"fields": [{
"type": "string",
"name": "string",
"label": "string",
"placeholder": "string",
"help_text": "string",
"options": "string",
"validation": "string",
"is_required": "boolean",
"position": "number"
}],
"submissions": [{
"data": "string (JSON-encoded field values)",
"ip_address": "string",
"user_agent": "string",
"is_read": "boolean",
"created_at": "datetime"
}],
"created_at": "datetime",
"updated_at": "datetime"
}],
"config": {"key": "value"}
}Getting Started
Core Features
- Content Management
- Media Library
- Taxonomy
- Menu Builder
- Forms
- Multi-Language
- SEO
- Feeds
- Video Embedding
- Agent-Ready
Administration
API & Integrations
Security
Theme Development
Module Development
Built-in Modules
- Module: Sentinel
- Module: Developer
- Module: DB Manager
- Module: Migrator
- Module: Informer
- Module: Demo Mode
- Module: Internal Analytics
- Module: External Analytics
- Module: Privacy
- Module: Embed
- Module: MCP Server
Deployment