Skip to content

Import Export

Claude edited this page Oct 23, 2026 · 4 revisions

Import/Export

oCMS provides comprehensive import and export functionality to backup, migrate, or transfer content between installations.

Overview

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

Exporting Content

Accessing Export

  1. Navigate to Admin > Export (administrators only)
  2. Select content types to include
  3. Tick Include Media Files (it needs Media ticked) to get a ZIP archive; otherwise you get a JSON file
  4. Click Download Export (Download ZIP Archive when media files are included)

Export Options

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

Export Formats

JSON Export

Creates a single .json file containing all selected content. Best for smaller sites, API consumption, or version control.

ZIP Export

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.

Export Schema

{
    "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": {...}
}

Page Export Format

{
    "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"
}

Importing Content

Accessing Import

  1. Navigate to Admin > Import (administrators only)
  2. Upload your export file (JSON or ZIP, at most 100 MiB)
  3. 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)
  4. Configure import options
  5. Click Start Import

Import Options

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

Conflict Resolution

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.

Import Order

Content is imported in this order to maintain relationships:

  1. Languages
  2. Users
  3. Categories
  4. Tags
  5. Media
  6. Pages
  7. Menus
  8. Forms
  9. Site config

Validation

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"

Migration Guide

Migrating to a New Server

  1. Export from source: Select all content types, choose ZIP format, download
  2. Prepare target: Install oCMS, run migrations, create admin user
  3. Import to target: Upload ZIP, validate, set conflict strategy to "Overwrite", import
  4. Verify: Check pages render correctly, verify media files load, test translation links

Partial Migration

To migrate only specific content:

  1. Export with only needed content types selected
  2. Import to target with "Skip" conflict strategy
  3. Manually resolve any conflicts

Backup Strategies

Automated Backups

For automated backups, consider:

  1. SQLite database backup (direct file copy)
  2. Uploads directory backup
  3. Periodic export via API

Backup Checklist

  • Export includes all content types
  • ZIP format includes media files
  • Backup stored in separate location
  • Periodic restore tests
  • Backup rotation policy

API Access

Export via API

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.json

Import via API

Import 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"

Best Practices

  1. Regular backups: Export weekly at minimum
  2. Test restores: Periodically test importing to a test instance
  3. Version exports: Include date in filenames
  4. Use ZIP for migration: Ensures media files are included
  5. Validate first: Always validate before importing
  6. Backup before import: Export existing content before importing new
  7. Review changes: Check validation output carefully

Troubleshooting

Import Fails

"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

Large Imports

For very large sites:

  1. Split the import: Export/import content types separately
  2. Keep media batches within the archive limits: 100 MiB compressed, 5,000 files, 32 MiB per file and 512 MiB expanded media per ZIP
  3. Validate each batch before importing it

Full Export Schema Reference

{
    "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"}
}

Clone this wiki locally