Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

Assets to Files

A CLI migration utility for ExpressionEngine that converts legacy EEHarbor Assets fields into native ExpressionEngine File fields — in place.

  • Field IDs, short names (handles), labels, and channel/site assignments are preserved. Your templates keep working under the same field names.
  • Entry values are rewritten as modern {file:ID:url} references.
  • Native file records are created (via EE's own sync machinery) for files EE does not know yet; Assets per-file metadata (title, description / alt text, author, location) is copied onto native records where the native side is empty.
  • Assets data (exp_assets_*) is never modified — it remains your rollback source of truth until you uninstall Assets.

MIT licensed. Community utility, not a commercial migration framework.


⚠️ Back up first

This tool modifies field definitions and entry data. Before any live run:

  1. Create a database backup — e.g. php system/ee/eecli.php backup:database.
  2. Test the full migration on a staging copy before production.

There is no rollback command. Recovery is: restore the backup.

Requirements

  • ExpressionEngine 7.4.0 or later
  • Assets 5.0.7 or later (its tables must still exist)
  • file_manager_compatibility_mode off (the modern file manager) — the command aborts otherwise
  • PHP CLI access to run eecli.php

Installation

Copy the assets_to_files folder into system/user/addons/. That's it — CLI commands are discovered without installing the add-on in the control panel. (If your EE version does not list the command, run php system/ee/eecli.php addons:install assets_to_files.)

Usage

php system/ee/eecli.php assets-to-files:migrate [options]
Option Effect
--dry-run, -d Validate and report only. Nothing is written.
--audit Adds filesystem-heavy checks: per-file existence/readability, rename predictions, clean-name collisions.
--field=12,18 / -f Migrate only these fields (numeric IDs or short names). Default: every Assets field.
--multi-file=strict|skip Entries holding multiple assets: strict (default) marks the whole field unsupported; skip migrates the field and leaves those entries untouched, listed for manual cleanup.
--yes, -y Skip the backup confirmation prompt.

Recommended workflow

# 1. See what would happen, including filesystem verification
php system/ee/eecli.php assets-to-files:migrate --dry-run --audit

# 2. Migrate the smallest field first and spot-check it in the CP
php system/ee/eecli.php assets-to-files:migrate --field=26

# 3. Re-run the same field: everything should report "already migrated"
php system/ee/eecli.php assets-to-files:migrate --field=26 --dry-run

# 4. Migrate the rest
php system/ee/eecli.php assets-to-files:migrate

Speeding up large migrations

Files that have no native exp_files record yet are registered one by one through EE's sync machinery (which also generates thumbnails). If the dry run reports many files "would be registered", it is faster to bulk register them first with core's own tooling, then migrate:

php system/ee/eecli.php sync:upload-directory --upload-id=1

The migration only touches files actually referenced by content — library size in bytes is irrelevant; only the number of referenced files matters. For very large sites, run field by field (--field=...); the command is safe to re-run at any time and resumes where it left off.

Entry values are written through ChannelEntry models to keep EE's file usage bookkeeping correct, which means every normal per-save side effect fires once per migrated entry. Three of those dominate the runtime on a large field, so the command switches them off for the duration of the write batch and restores them afterwards:

  • Entry revisions. Writing a revision per save is the most expensive part of the save, and it evicts real editor revisions (max_revisions is usually 3, and an entry in several migrated fields is saved once per field). Suppressed — see the revisions caveat below.
  • Cache-breaking extensions, e.g. BoldMinded Speedy.
  • Search indexers, e.g. Pro Search, which deletes and fully re-tokenises each entry on every save.

Extension suspension is purely in-memory — exp_extensions is never touched, so nothing is disabled outside the running process. Whatever was suspended is listed under Follow-up at the end of the run, together with the one-off command or CP action that makes up for it.

The write phase reports its own throughput (wrote N entries in X.Xs), so you can measure the effect of any of this on your own data rather than taking the above on trust.

What it does, per field

  1. Reads exp_assets_selections (the authoritative entry↔file link), joins Assets file and folder records, and resolves every selection to an upload directory + relative path.
  2. Prunes: selections of deleted entries, duplicate selections of the same asset, entries already holding {file:ID:url} values, and multi-file entries per --multi-file.
  3. Resolves each path against native exp_files records (including subfolder trees). Paths claimed by more than one native record are reported as collisions and skipped rather than guessed at.
  4. Registers missing native records via EE's syncFiles() and copies Assets metadata onto native records (only into empty fields).
  5. Converts the field definition to file with clean native settings (allowed directory translated from Assets filedirs; Assets UI preferences discarded).
  6. Rewrites entry values in batches through ChannelEntry models, so exp_file_usage, file usage counts, and caches stay correct.

Idempotency

Re-running is always safe:

  • Fields already converted report "already migrated".
  • Entries already holding a {file:ID:url} value are skipped.
  • A killed run (timeout, ctrl-C) resumes by simply running the command again. There are no state files or tracker tables.

Caveats you should know

  • File renames: when registering a file whose name fails EE's cleaning rules (illegal characters etc.), EE physically renames the file on disk. The report lists every rename (FILE_RENAMED_ON_SYNC). Run --dry-run --audit first to see predicted renames.
  • Entry revisions: migrated entries do not gain a revision, even on channels with versioning enabled. This is deliberate — the revision would be written after the change (so it is useless for rollback) while pushing a real editor revision out of max_revisions. Existing history is left intact. versioning_enabled is restored per batch; if the process is hard-killed (SIGKILL, power loss) mid-batch, up to one batch of entries can be left with versioning off. Re-running the migration will not repair it — those entries are already migrated and get skipped — so fix it directly: UPDATE exp_channel_titles SET versioning_enabled = 'y' WHERE entry_id IN (...);
  • Settings widening: Assets fields allowing multiple upload directories become native fields allowing all directories (native File fields support one directory or all).
  • Multi-site (MSM): newly registered files are stamped with the current site context's site_id. On MSM installs with cross-site upload directories, verify the results on staging first.

Scope (v1)

Not supported, by design: rollback, Grid/File Grid/Matrix content, multi-file values (beyond skip-and-report), S3/remote Assets sources, Pro Variables content, EE Pro drafts, CP interface, template rewriting. Fields or entries in these situations are reported and left untouched.

After migrating

Keep the Assets add-on installed until you have verified the site. Uninstalling Assets drops all exp_assets_* tables — that is the point of no return. Once verified:

  1. Update any Assets-specific template syntax (this tool does not touch templates; preserved field handles keep {field_name} tags working for common cases, but Assets tag pairs/modifiers need review).
  2. Uninstall Assets.

License

MIT

About

Converts existing Asset Fields into native File Fields on ExpressionEngine sites

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages