How to Export Data from October CMS: Methods, Limits & Portability
October CMS has no single export button. Learn every extraction method — DB dumps, backend exports, custom APIs, Tailor content, and file attachments — with real constraints and edge cases.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
How to Export Data from October CMS: Methods, Limits & Portability
TL;DR: October CMS has no single "export everything" button. A complete export combines a database dump (MySQL, PostgreSQL, or SQLite), filesystem copies of themes/media/uploads, and custom scripting to resolve polymorphic file attachments and Tailor content. There are no vendor-imposed API limits — your constraints are server-level PHP and web server configurations.
October CMS is a self-hosted Laravel application, which means you have full server access. Unlike SaaS platforms that gate your data behind paginated APIs and rate limits, you control the database, the filesystem, and the runtime. That is an advantage — but it does not mean exporting is simple.
The data model is fragmented by design. Content is split across relational database tables, the system_files polymorphic attachment table, flat theme files, the media library filesystem, and plugin-specific storage where each plugin defines its own schema. Getting a complete, migration-ready export means combining multiple extraction methods and understanding exactly where each piece of data lives.
This guide covers every viable extraction method, the exact constraints you will hit, and the failure modes that catch teams mid-migration.
Verified against: October CMS v4.x (up to v4.3) running on Laravel 12. Older versions (v2.x, v3.x) share similar export mechanics but differ in Tailor availability and some artisan commands.
What Data Lives Where in October CMS
Before writing any scripts, map where your data actually resides. October CMS splits data between the database and the filesystem, and different subsystems use different storage strategies.
| Data Layer | Storage Location | Export Method | What People Miss |
|---|---|---|---|
| CMS Pages, Layouts, Partials | Flat .htm files in themes/your-theme/ |
Filesystem copy (rsync/tar) | If database-driven themes are enabled, the live version may be in the DB, not on disk |
| Tailor Content (v3+) | Auto-generated DB tables from YAML blueprints | mysqldump / Eloquent queries / custom API |
Table names are generated from blueprint UUIDs — not human-readable. Removed fields can linger until tailor:prune |
| Plugin Content (Blog, FAQ, etc.) | Plugin-specific DB tables (e.g., rainlab_blog_posts) |
mysqldump / ExportModel CSV / custom API |
Each plugin defines its own schema; no universal export format |
| File Attachments | system_files table + storage/app/uploads/ |
DB query + filesystem copy | Polymorphic relations — must preserve attachment_type, attachment_id, and field mappings |
| Media Library | storage/app/media/ (or a configured disk like S3) |
Filesystem/bucket copy | No database records for media files — only path-based references in content fields |
| Localized Content | rainlab_translate_attributes and rainlab_translate_indexes tables (RainLab.Translate) |
mysqldump targeted tables |
Shadow tables for translated attributes are invisible unless you query them explicitly |
| Backend Users & Permissions | backend_users, backend_user_roles, backend_user_groups |
mysqldump |
Passwords are Bcrypt-hashed; cannot export cleartext credentials |
| Frontend Users | users table (via RainLab.User or similar) |
mysqldump / CSV export |
Password hashes can be ported to other Laravel/PHP apps but not to non-Bcrypt systems |
| System & Plugin Settings | system_settings table + System\Models\SettingModel |
mysqldump |
Values are often serialized PHP or JSON — may need deserialization. Plugin settings live in the DB, not config files |
| Multisite Content | Site-scoped records with site_id columns |
Export per site context | A naïve full-table export mixes data from all sites or locales together |
| Soft-Deleted Records | Same tables as live records, deleted_at IS NOT NULL |
Filter on deleted_at IS NULL during import |
mysqldump exports deleted records — non-Laravel import targets may ingest them as live content |
Method 1: Direct Database and Filesystem Export
Since October CMS is self-hosted, mysqldump (or pg_dump for PostgreSQL) is the fastest path to a full data export. This is the method the official October CMS upgrade guide recommends for backups before major version changes.
# MySQL / MariaDB
mysqldump -u root -p database_name > october_export.sql
# PostgreSQL
pg_dump -U username -d database_name -F c -f october_export.dump
# SQLite
sqlite3 storage/database.sqlite .dump > october_export.sqlWhat you get
- All plugin tables (blog posts, products, custom content)
- All Tailor content tables
system_filesmetadata (file paths, attachment mappings)- Backend and frontend users, roles, groups
- System and plugin settings
- Migration version history (
system_plugin_history,system_plugin_versions) - Soft-deleted records (rows where
deleted_at IS NOT NULL) — filter these during import
What you do not get
- Actual files on disk —
system_filesstores metadata and relative paths, not binaries. You must separately copystorage/app/uploads/andstorage/app/media/. - Theme template files — pages, layouts, partials live in
themes/your-theme/as flat.htmfiles. - Plugin source code — lives in
plugins/. Useful for understanding schemas, not needed for data import. - Blueprint YAML files — Tailor blueprints in
app/blueprints/orthemes/your-theme/blueprints/define the content structure.
Do not skip the filesystem. A database dump alone is incomplete. A common mistake is dumping only the DB, then discovering all images are broken because the physical files were never copied. You need storage/app/, themes/, and your blueprint files for a full export.
Verify your export with row counts
Before moving on, reconcile row counts between your source database and exported files. Run this against your source database and again after import to confirm completeness:
-- Row count reconciliation across key tables
SELECT 'system_files' AS table_name, COUNT(*) AS row_count FROM system_files
WHERE deleted_at IS NULL
UNION ALL
SELECT 'rainlab_blog_posts', COUNT(*) FROM rainlab_blog_posts
WHERE deleted_at IS NULL
UNION ALL
SELECT 'backend_users', COUNT(*) FROM backend_users
WHERE deleted_at IS NULL
UNION ALL
SELECT 'system_settings', COUNT(*) FROM system_settings;Save these counts. On the target, run the same query after import and diff the results. Unexplained discrepancies indicate missing tables, incomplete dumps, or soft-deleted records being imported as live data.
Selective table export for large databases
If you only need specific plugin data — for example, blog posts for migration to another CMS — export targeted tables:
mysqldump -u root -p database_name \
rainlab_blog_posts \
rainlab_blog_categories \
system_files \
> blog_export.sqlAlways include system_files when exporting any content that has file attachments. The polymorphic relationship means all attachment metadata is centralized in that single table.
Run from CLI, not the browser
For large exports, always run from the command line. PHP's default max_execution_time is 30 seconds for web requests but unlimited (0) on CLI. Browser-triggered exports fail long before the data is complete on anything beyond trivial datasets. (php.net)
Treat the SQL dump as an archival source of truth, not as a clean migration payload. You will still need transformation scripts to map October's schema to your target platform.
Method 2: Built-in Backend Export via ImportExportController
October CMS ships with a Backend\Behaviors\ImportExportController that plugins can implement to offer export functionality from the admin panel. This is not a platform-wide feature — it is opt-in per plugin. (docs.octobercms.com)
How it works
A plugin developer creates an export model extending Backend\Models\ExportModel and implements the exportData() method. The admin panel then provides a UI where users can select columns and download a file.
class PostExport extends \Backend\Models\ExportModel
{
public function exportData($columns, $sessionKey = null)
{
$posts = Post::all();
$posts->each(function($post) use ($columns) {
$post->addVisible($columns);
});
return $posts->toArray();
}
}To add this behavior to any backend controller:
namespace Acme\MyPlugin\Controllers;
class MyData extends \Backend\Classes\Controller
{
public $implement = [
\Backend\Behaviors\ImportExportController::class,
];
public $importExportConfig = 'config_import_export.yaml';
}In the configuration YAML:
export:
title: Export My Data
modelClass: Acme\MyPlugin\Models\MyDataExport
list: $/acme/myplugin/models/mydata/columns.yamlIn October CMS 4.x, the defaultFormatOptions.fileFormat setting supports json, csv, and csv_custom. Older versions (2.x, 3.x) default to CSV only, so check your installed version before assuming JSON export is available. (docs.octobercms.com)
Limitations
- Only available if the plugin implements it. Many third-party plugins do not include export support.
- No automatic relationship resolution. A blog post with a
belongsTocategory relationship exports the foreign key ID, not the category name, unless the developer explicitly handles it. - File attachments export as nothing. Polymorphic file relations (
$attachOne,$attachMany) are not included — no file URLs, no paths. - No pagination or streaming for large datasets. The
exportData()method loads all records into memory. Tables with 100K+ rows can exhaust PHP's memory limit. - Relational data flattens. A post with multiple categories becomes a comma-separated string. Reconstructing many-to-many relationships from a flat file is error-prone.
The RainLab.Blog plugin — the most widely used October CMS content plugin — does implement import/export. But the export only covers post metadata and body content, not attached images or relational category hierarchies in a structured format.
Method 3: Custom API Endpoints or Laravel Routes
October CMS does not ship with a built-in REST API for content. There is no /api/posts endpoint out of the box. If you need programmatic data extraction — to feed a migration pipeline or sync to another system — you build it yourself.
Because October runs on Laravel, you have multiple options.
Option A: Laravel routes in a plugin
Define standard routes in a plugin's routes.php:
// plugins/acme/api/routes.php
use Tailor\Models\EntryRecord;
use Illuminate\Support\Facades\Route;
Route::get('api/v1/export/articles', function () {
$posts = EntryRecord::inSection('Blog\Post')
->with('cover_image')
->paginate(100);
return response()->json($posts);
});Option B: CMS pages as JSON endpoints
October CMS lets you create API endpoints using CMS pages that return JSON instead of HTML. The official docs describe using layouts as shared middleware for authentication and throttling. (docs.octobercms.com)
url = "/api/posts"
layout = "api"
==
<?php
function onStart()
{
$posts = \RainLab\Blog\Models\Post::with(['categories', 'featured_images'])
->published()
->get();
return response()->json($posts);
}
==Option C: Third-party API plugins
Plugins like Simple API and API Generator can auto-generate REST endpoints from your models. These work but carry caveats:
- Maintenance risk — community plugins may lag behind major October CMS versions.
- No built-in rate limiting unless you add middleware.
- Basic authentication only in most cases.
Server-level constraints
Because you host the application, there are no vendor API limits. Your constraints are all server-level:
- PHP
memory_limit: Typically 128MB or 256MB. Loading thousands of Eloquent models into memory causes fatal errors. Always usepaginate()orchunk(). - PHP
max_execution_time: 30–60 seconds for web requests. Complex transformations (like Markdown-to-HTML parsing) before returning JSON may time out. - Nginx/Apache timeouts: Your web server or load balancer may drop the connection after 60 seconds even if PHP is configured for longer.
Method 4: Artisan Console Commands (Scripted Export)
For migration exports, a custom artisan command is often the most practical approach. It runs on CLI (no execution time limit), can resolve polymorphic relationships, handles pagination with chunk(), and produces structured output you can run repeatedly during testing.
The artisan approach is the most migration-relevant method for sites with complex data. Here is a complete example that handles polymorphic file resolution, soft-delete filtering, and chunked processing to avoid memory exhaustion:
namespace Acme\Export\Console;
use Illuminate\Console\Command;
use RainLab\Blog\Models\Post;
class ExportPosts extends Command
{
protected $name = 'export:posts';
protected $description = 'Export all published blog posts to JSON with resolved file attachments';
public function handle()
{
$exportDir = storage_path('app/export');
if (!is_dir($exportDir)) {
mkdir($exportDir, 0755, true);
}
$total = 0;
// chunk() processes records in batches of 200 to avoid memory exhaustion
Post::with(['categories', 'featured_images'])
->whereNull('deleted_at') // exclude soft-deleted records
->chunk(200, function ($posts) use ($exportDir, &$total) {
foreach ($posts as $post) {
$data = $post->toArray();
// Resolve polymorphic file attachments to actual paths
if ($post->featured_images->count()) {
$data['image_urls'] = $post->featured_images
->map(fn($img) => $img->getPath())
->toArray();
// Also capture disk_name for filesystem mapping during import
$data['image_disk_names'] = $post->featured_images
->map(fn($img) => $img->disk_name)
->toArray();
}
// Strip Twig shortcodes and parse Markdown to HTML if needed
// $data['content_html'] = (new \Markdown\Parser)->parse($post->content);
$filename = "{$exportDir}/post-{$post->id}.json";
file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$total++;
}
});
$this->info("Exported {$total} posts to {$exportDir}.");
}
}Key decisions in this implementation:
whereNull('deleted_at')— explicitly excludes soft-deleted posts. Remove this only if your migration target needs a full history.chunk(200, ...)— processes 200 records at a time. Adjust downward if posts have many eager-loaded relationships.disk_namecapture — preserves the hashed filename used to locate the physical file instorage/app/uploads/, making file remapping on the target deterministic.- Markdown-to-HTML comment — if your target platform expects HTML rather than Markdown (Webflow, Contentful, Gutenberg), uncomment and add a Parsedown step. October CMS uses Parsedown internally for Markdown rendering.
For one-time migration exports, do not over-engineer a REST API. A quick artisan command that queries the database and writes JSON files to disk is faster and more reliable than building and securing a full API you will use once.
Exporting Tailor Content
Tailor — introduced in October CMS v3 — lets you define content structures via YAML blueprints without building a full plugin. Tailor content lives in auto-generated database tables, and extracting it requires understanding the mapping between blueprints and tables.
The challenge
Tailor table names are generated from blueprint UUIDs, not human-readable names. You cannot look at the database schema and immediately know which table maps to which content type. To export Tailor data cleanly:
- Read the blueprint YAML files from
app/blueprints/orthemes/your-theme/blueprints/ - Query using Tailor's model API with human-readable handles rather than raw table names
use Tailor\Models\EntryRecord;
use Tailor\Models\GlobalRecord;
$posts = EntryRecord::inSection('Blog\Post')->get();
$posts->load(['categories', 'author']);
file_put_contents(
storage_path('app/exports/blog-posts.json'),
json_encode($posts->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
$config = GlobalRecord::findForGlobal('Blog\Config');
file_put_contents(
storage_path('app/exports/blog-config.json'),
json_encode($config?->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);October documents EntryRecord, StructureRecord, StreamRecord, SingleRecord, and GlobalRecord for querying Tailor content by handle. Always use these models instead of hard-coding UUID-derived table names. (docs.octobercms.com)
Tailor never drops columns. If you remove a field from a blueprint, the database column is renamed with a prefix, not deleted. Exported Tailor tables may contain orphaned columns that no longer correspond to any active blueprint field. Filter your export against current blueprint definitions — not every column in the raw table. Run php artisan tailor:prune to clean up orphaned columns before exporting if possible.
The system_files Problem: Exporting File Attachments
This is where most October CMS exports go wrong.
All file attachments — regardless of which plugin uploaded them — are tracked in a single system_files table using polymorphic relationships. Understanding this table's schema is essential for any migration that must preserve file associations.
system_files schema (key columns)
| Column | Type | Description |
|---|---|---|
id |
int | Primary key |
attachment_type |
varchar | Fully-qualified model class (e.g., RainLab\Blog\Models\Post) |
attachment_id |
int | ID of the parent record |
field |
varchar | Relationship name on the model (e.g., featured_images) |
disk_name |
varchar | Hashed filename on disk (e.g., 5f3a2b1c.jpg) — used to locate the physical file |
file_name |
varchar | Original upload filename |
file_size |
int | File size in bytes |
content_type |
varchar | MIME type |
sort_order |
int | Position when multiple files are attached to one field |
is_public |
tinyint | 1 = storage/app/uploads/public/, 0 = storage/app/uploads/protected/ |
created_at |
timestamp | Upload timestamp |
The actual files live in storage/app/uploads/public/ (when is_public = 1) or storage/app/uploads/protected/ (when is_public = 0), organized into partition subdirectories derived from the first few characters of disk_name. A file with disk_name = 5f3a2b1c.jpg lives at storage/app/uploads/public/5f/3a/5f3a2b1c.jpg.
Protected files are served through October's routing layer with access checks — they are not publicly accessible by direct URL. When migrating protected files to a non-October platform, you must decide whether to make them public, implement equivalent access control, or serve them from a different authenticated endpoint.
How to export attachments correctly
- Dump the
system_filestable to preserve all relationship mappings - Copy the entire
storage/app/uploads/directory (bothpublic/andprotected/) - During import to the target platform, use
attachment_type+attachment_id+fieldto map files back to their parent records
-- Find all images attached to blog posts, with disk path reconstruction
SELECT
sf.id,
sf.disk_name,
sf.file_name,
sf.file_size,
sf.content_type,
sf.attachment_id,
sf.field,
sf.sort_order,
sf.is_public,
CONCAT(
CASE WHEN sf.is_public = 1 THEN 'storage/app/uploads/public/' ELSE 'storage/app/uploads/protected/' END,
SUBSTRING(sf.disk_name, 1, 2), '/',
SUBSTRING(sf.disk_name, 3, 2), '/',
sf.disk_name
) AS local_path
FROM system_files sf
WHERE sf.attachment_type = 'RainLab\\Blog\\Models\\Post'
ORDER BY sf.attachment_id, sf.sort_order;Media Library files work differently. Images added via the Media Manager (not the File Upload widget) are just files in storage/app/media/ with no database records. References in content fields are stored as relative paths like /media/my-image.jpg. If your media library is configured to use S3 or another external disk, the files are not on the app server at all.
Exporting S3-backed media
If your installation uses an S3-compatible storage driver for media, the files are not on the application server. Use the AWS CLI to enumerate and download bucket contents:
# List all media files
aws s3 ls s3://your-bucket/media/ --recursive
# Sync entire media directory to local for export
aws s3 sync s3://your-bucket/media/ ./media-export/
# Sync uploads (file attachments)
aws s3 sync s3://your-bucket/uploads/ ./uploads-export/The bucket path structure depends on your config/filesystems.php configuration. Verify the root path for your configured disk before running sync commands.
If attachments use an external storage driver, October's getLocalPath() method downloads a temporary local copy for scripting. Convenient for extraction, but the canonical binary may not live on the application server. For bulk exports, use the storage provider's native tools (AWS CLI, rclone) rather than iterating getLocalPath() per record. (docs.octobercms.com)
Exporting Multilingual Content (RainLab.Translate)
Sites using RainLab.Translate store translated content in separate shadow tables that are invisible to a naïve database export workflow.
RainLab.Translate uses two key tables:
rainlab_translate_attributes— stores translated field values as JSON, keyed bymodel_id,model_type, andlocalerainlab_translate_indexes— stores individual translated field values for query indexing
When exporting a multilingual site, always include these tables explicitly:
mysqldump -u root -p database_name \
rainlab_blog_posts \
rainlab_blog_categories \
rainlab_translate_attributes \
rainlab_translate_indexes \
system_files \
> multilingual_export.sqlTo reconstruct translated content per locale during migration:
use RainLab\Translate\Models\Attribute;
// Get all translations for blog posts in French
$translations = Attribute::where('model_type', 'RainLab\Blog\Models\Post')
->where('locale', 'fr')
->get()
->keyBy('model_id');
// Merge translations onto post records
$posts = \RainLab\Blog\Models\Post::whereNull('deleted_at')->get();
foreach ($posts as $post) {
$translated = $translations->get($post->id);
$post->fr_title = $translated ? ($translated->attribute_data['title'] ?? $post->title) : $post->title;
// ... map other translated fields
}If your migration target uses locale-specific records rather than an attribute table (WordPress WPML, Contentful locale variants), you will need to denormalize this data — one record per locale per content item — rather than keeping the translated attributes in a separate table.
Theme Files, Database-Driven Templates, and Static Pages
October CMS themes are flat files. Pages, layouts, partials, and content blocks are .htm files in themes/your-theme/. A simple tar or rsync handles the export:
tar -czf theme-export.tar.gz themes/your-theme/But watch for these complications:
- Database-driven themes: Some deployments push theme data to the database for multi-server setups (configured in
config/cms.phpwithdatabaseTemplates). If enabled, the filesystem copy is stale — the live templates are in the database. Runphp artisan theme:copy <theme> --import-dbto sync database templates back to disk before exporting. (docs.octobercms.com) - Static Pages plugin (RainLab.Pages) stores page content as flat files in
themes/your-theme/content/static-pages/, but menu structures may live in the database. - Multisite themes: A multisite install may assign different themes per site. Export all active themes, not just one.
Backup Plugins: Spatie-Based Full Export
For full-site archival (not migration-ready, but useful for backups), the panakour/backup plugin wraps Laravel's spatie/laravel-backup package. It creates full backups including database and files, outputs as a ZIP, and can store to local disk or external services.
php artisan backup:run # Full backup (files + DB)
php artisan backup:run --only-db # Database onlyLimitations: requires mysqldump (or equivalent) on the server, not compatible with Windows servers per the plugin's own docs, and produces a backup archive — not a structured, migration-ready export.
Recreating the Schema on a Clean Target
If you are migrating October CMS to a fresh server rather than moving data to a different platform, use october:up to recreate the schema before importing your dump:
# On the target server (fresh October installation)
php artisan october:upThis runs October's own migration system, which differs from plain artisan migrate in that it also seeds plugin version history (system_plugin_history, system_plugin_versions) and handles October-specific schema bootstrapping. Running artisan migrate alone may leave the plugin registry in an inconsistent state, causing plugin updates to fail or re-run migrations that already exist in your imported dump.
After october:up, import your SQL dump. If you encounter duplicate migration errors, it means october:up applied migrations that your dump also contains — you may need to truncate system_plugin_history from your dump or import selectively.
Common Export Traps That Break Migrations
- Exporting themes without checking for database-driven templates. The live version may be in the DB, not on disk.
- Dumping
system_filesrows without copyinguploads/publicanduploads/protected. Metadata without binaries is useless. - Importing soft-deleted records as live content.
mysqldumpexports all rows. Non-Laravel targets do not understanddeleted_at. Always filterWHERE deleted_at IS NULLduring transformation. - Missing RainLab.Translate shadow tables. A multilingual site export that omits
rainlab_translate_attributesandrainlab_translate_indexesloses all non-default locale content. - Assuming every Tailor column is current. Old fields and tables linger until pruned.
- Flattening relational data to CSV too early. You lose foreign keys, hierarchy, and site context that matter during target mapping.
- Forgetting plugin settings in the database. Settings stored via
System\Models\SettingModelare in the DB, not config files. (docs.octobercms.com) - Mixing multisite data. A full-table export without filtering by
site_idblends content from different sites or locales. October'sSite::withContext(...)service lets you scope exports per site. (docs.octobercms.com) - Neglecting content formatting. October CMS uses Markdown and Twig-based shortcodes (especially in RainLab plugins). If the target platform expects HTML or block-based JSON (Webflow, Contentful, WordPress Gutenberg), you need a transformation step: Markdown parsing (October uses Parsedown internally), shortcode/Twig stripping, and URL rewriting.
- Serialized PHP data. Older plugins store settings and multi-select fields as PHP serialized strings (e.g.,
a:2:{i:0;s:4:"value";}). Non-PHP migration targets need a deserialization step before import. - Protected file accessibility. Files in
uploads/protected/are served through October's auth layer. When migrating to a platform without equivalent access control, determine before migration whether these files should become public or require a new access mechanism.
Version-Specific Gotchas
- v2.x → v3.x: Tailor did not exist before v3.x. If your site runs v2.x, there are no Tailor tables to worry about.
- v3.x → v4.x: v4.0 requires PHP 8.2+ and Laravel 12. Plugin compatibility may break. Always test data exports against the actual installed version.
ImportExportControllerJSON format: Available only in v4.x. Sites on v2.x or v3.x are CSV-only for backend exports.databaseTemplates: The config key has been consistent across versions, but the artisan sync command (theme:copy --import-db) may behave differently in v2.x. Test before relying on it.
Export Checklist for Migration
- Database dump — full
mysqldumporpg_dump - Row count reconciliation — run the count query before export; re-run after import and diff
-
storage/app/uploads/— all file attachments (bothpublic/andprotected/subdirectories) -
storage/app/media/— all media library files (or sync from configured media disk/bucket) -
themes/your-theme/— all CMS templates, pages, layouts, partials -
app/blueprints/and theme-level blueprints — Tailor schema definitions (v3+) -
plugins/— plugin source (needed to understand schemas, not for import) -
system_filesmapping — exported separately as CSV/JSON for attachment resolution, withdisk_name-to-path reconstruction -
rainlab_translate_attributesandrainlab_translate_indexes— if using RainLab.Translate - Soft-deleted records filtered — verify
deleted_at IS NULLapplied during transformation - Database-driven themes synced — run
theme:copy --import-dbifdatabaseTemplatesis enabled - S3/external media exported — use provider's native CLI tools if media is not on the app server
- Multisite context verified — export per-site if using multisite
- Plugin-specific exports checked — verify if critical plugins (Blog, Pages, Translate) have their own export tools
- Protected files access plan confirmed — determine how
uploads/protected/files will be served on the target
When to Skip DIY and Get Help
October CMS exports are straightforward if you are comfortable with Laravel, Eloquent, and direct database access. Complexity multiplies fast when:
- You have 10+ plugins with interconnected data models
- Tailor content uses nested blueprints with cross-references
- File attachments span thousands of records across multiple plugins and need remapping to a different CMS's file system
- You need to transform October CMS's data model into a different platform's schema (WordPress, Webflow, a headless CMS)
- Multisite data needs to be separated or merged during export
- Multilingual content in RainLab.Translate must be denormalized into per-locale records for the target platform
- Content formatting requires Markdown-to-HTML parsing, shortcode stripping, and URL rewriting across thousands of records
- Protected file assets require access control decisions that differ from October's approach
The export is the straightforward part. The hard part is making it import-ready for the target platform — untangling polymorphic file relationships, filtering soft-deleted records, deserializing PHP arrays, reconstructing translated content per locale, mapping dynamic Tailor blueprints to a fixed schema, and rewriting content references.
Frequently Asked Questions
- Does October CMS have a built-in data export feature?
- October CMS has no platform-wide export button. It ships with a Backend ImportExportController that plugins can implement to offer CSV or JSON exports for their specific data. Each plugin must opt in, and many don't. For a full export, you need a database dump plus filesystem copies.
- How do I export file attachments from October CMS?
- File attachments are tracked in the system_files table via polymorphic relationships (attachment_type, attachment_id, field columns). The actual files live in storage/app/uploads/. Export the system_files table to preserve mappings, then copy the entire uploads directory. You need both to reconstruct file-to-record relationships in the target platform.
- How do I export Tailor content from October CMS?
- Tailor content is stored in auto-generated database tables with names derived from blueprint UUIDs. Use Tailor's model API (EntryRecord::inSection(), GlobalRecord::findForGlobal()) to query by human-readable handle, then serialize to JSON. Include the blueprint YAML files from app/blueprints/ to document the schema.
- Can I use an API to export October CMS content?
- October CMS does not include a REST API for content out of the box. You can build custom endpoints using Laravel routes or CMS pages, or install community plugins like Simple API. For one-time migration exports, a custom artisan command that writes JSON to disk is faster and more reliable.
- Are there API rate limits when exporting from October CMS?
- There are no vendor-imposed rate limits because October CMS is self-hosted. Your practical limits are server-level: PHP memory_limit, max_execution_time (30s default for web requests, unlimited on CLI), and Nginx/Apache connection timeouts.