- Установка ImportX
- Как работать с ImportX
- Как подготовить данные для импорта, чтобы не запутаться
- Обновление существующих ресурсов через ImportX
- Включаем режим обновления (update)
- Обязательные поля при обновлении
- Общие правила для CSV при обновлении
- Логика работы в режиме update
- Фикс режима обновления ImportX (MODX 2 и MODX 3)
- Что будет после фикса
- 1. Исправляем setDefaultClassKey (важно для MODX 3)
- 2. Разделяем логику create и update
- Как этим пользоваться после фикса
- Заключение
Установка ImportX
Установка ImportX стандартная из основного репозитория .
Как работать с ImportX
После активации дополнения в верхнем меню «Пакеты» у вас проявиться пункт «ImportX» переходим в него. На открывшейся странице заходим во вкладку «Базовые настройки» и задаем их.

- Родитель: укажите id родителя в которого импортировать ресурсы;
- Опубликован: установите галку если хотите чтобы ресурс был сразу опубликован;
- Доступен для поиска: установите галку, чтобы импортированные ресурсы были доступны для поиска;
- Скрыть из меню: установите галку если хотите скрыть импортированные ресурсы из меню.
Переключаемся во вкладку «Ввод CSV» и в поле Чистый CSV, вписываем вот такую конструкцию:
pagetitle;longtitle;description;introtext;menutitle;alias;template;content;tv1 — т.е. перечисляем стандартные поля ресурса (пишите только нужные) + дополнительные поля в формате tv1 — где 1 это id TV поля в админке.
Далее в таком же формате и последовательности указываем контент разделенный через «;». и нажимаем кнопку «Начать импорт».

После нажатия на кнопку «Начать импорт», компонент проверит данные и если все хорошо произведет импорт.

После закрытия консоли (кнопка «OK»), дерево ресурсов обновляется и у нас появляется в нужном месте новые ресурсы.

Как подготовить данные для импорта, чтобы не запутаться
Я обычно подготавливаю все данные для импорта в excel, чтобы не запутаться.

Далее копирую это все добро в notepad++ и при помощи поиска и замены (CTRL+H) заменяю широкие пробелы (получившиеся в результате копирования с эксель -между колонками) на знак разделитель.

и получаю подготовленные данные для импорта которые вставляю в поле Чистый CSV. Смотрите чтобы у вас не было лишних разделителей и разделителей в контенте.

Обновление существующих ресурсов через ImportX
По умолчанию ImportX создает новые ресурсы при импорте CSV. Но начиная с версии 1.1 компонент умеет не только создавать, но и обновлять уже существующий контент. Для этого нужно изменить одну системную настройку и немного по‑другому подготовить CSV.
Важно! Из коробки на данный момент есть серьезный баг https://github.com/modmore/importX/issues/64 , который делает этот функционал неюзабельным. Частичное решение есть в этом же ишьюсе. Так же смотрите ниже фикс от меня — перед тем как воспользоваться данной функцией)
Включаем режим обновления (update)
- Перейдите в «Системные настройки» MODX (верхнее меню «Система» → «Системные настройки»).
- В фильтре по пространству имён выберите
importx(или в общем поиске начните вводитьimportx.processor). - Найдите параметр
importx.processor. - Измените его значение с
createнаupdateи сохраните.
После этого ImportX при импорте будет пытаться обновлять существующие ресурсы по их ID, а если ресурс с таким ID не найден — создавать новый.
Обязательные поля при обновлении
В режиме update ImportX ориентируется на ID ресурса, но при этом процессор ресурса требует наличие alias (псевдонима) — без него будет ошибка вида «alias: This field is required». Поэтому для обновления рекомендуется указывать хотя бы:
id— числовой ID ресурса в MODX (как в дереве ресурсов);alias— псевдоним ресурса (должен совпадать с тем, что уже есть у ресурса).
Остальные поля указываете по необходимости: стандартные поля (pagetitle, longtitle, description, content, и т.д.) и/или TV-поля вида tv1, tv2 и т.п., где цифра — это ID TV.
Пример «шапки» и строки для обновления SEO‑полей через TV:
id;alias;tv1;tv2
14;certificates;Seo title;Seo description
id— ID ресурса;alias— его alias;tv1— TV с ID 1 (например, SEO title);tv2— TV с ID 2 (например, SEO description).
Важно: порядок полей в первой строке (заголовке) должен точно совпадать с порядком значений в каждой строке данных, а количество столбцов одинаково во всех строках.
Общие правила для CSV при обновлении
Все общие требования к CSV для ImportX остаются такими же, как и при создании ресурсов:
- Первая строка — заголовок с названиями полей:
id;alias;pagetitle;tv1;tv2и т.п.; - Строки разделяются переводом строки;
- В качестве разделителя по умолчанию используется
;, но при необходимости можно задать нестандартный (например,;;;) во вкладке «Ввод CSV», если в контенте встречаются точки с запятой; - Количество значений в каждой строке должно совпадать с количеством заголовков;
- TV указываются в формате
tvN, гдеN— ID ТВ-поля.
Логика работы в режиме update
После переключения importx.processor в update ImportX работает по следующему принципу:
- Берёт из CSV строку, читает
idресурса. - Пытается найти ресурс с таким ID.
- Если ресурс найден — обновляет переданные поля (включая TV).
- Если ресурс по этому ID не найден — создаёт новый ресурс и заполняет переданные поля.
Поэтому при массовом обновлении:
- Внимательно проверяйте
id, чтобы не перезаписать «чужие» ресурсы; - Следите за
alias, чтобы он соответствовал уже существующему ресурсу и не вызывал конфликтов.
Фикс режима обновления ImportX (MODX 2 и MODX 3)
В стандартной версии ImportX режимupdateработает нестабильно: при обновлении ресурсов вы можете получать ошибки по обязательным полям (alias,class_keyи т.п.), а поведение не совпадает с ожидаемым. Ниже — минимальный фикс, который делает обновление предсказуемым и одинаково понятным и в MODX 2, и в MODX 3.
Что будет после фикса
- Режим
createпродолжает создавать новые ресурсы из CSV, как и раньше. - Режим
updateобновляет только существующие ресурсы по ID, не пытаясь автоматически создавать новые, если ресурс не найден. - В CSV для обновления вам достаточно указать
id,aliasи нужные поля/TV, остальные данные ресурса сохраняются без изменений.
Пример CSV для обновления SEO‑TV:
id;alias;tv1;tv2 14;certificates;Seo title;Seo description
id— ID ресурса в MODX.alias— псевдоним ресурса (берётся либо из CSV, либо из самого ресурса).tv1,tv2— TV‑поля по их ID.
1. Исправляем setDefaultClassKey (важно для MODX 3)
Файл:core/components/importx/processors/startimport.class.php.
В конце класса StartImport есть метод:
protected function setDefaultClassKey(array $record): array
{
$modxVersion = $this->modx->getVersionData();
if (version_compare($modxVersion['full_version'], '3.0.0-dev', '>=') && empty($line['class_key'])) {
$record['class_key'] = 'MODX\\Revolution\\modDocument';
}
return $record;
}
Замените строку с ошибочной переменной$line на $record:
protected function setDefaultClassKey(array $record): array
{
$modxVersion = $this->modx->getVersionData();
if (version_compare($modxVersion['full_version'], '3.0.0-dev', '>=') && empty($record['class_key'])) {
$record['class_key'] = 'MODX\\Revolution\\modDocument';
}
return $record;
}
Как это работает:
- В MODX 2.x это условие не срабатывает, поэтому поведение остаётся стандартным: ядро само подставляет
modDocumentдля ресурсов безclass_key. - В MODX 3.x метод автоматически подставит
MODX\Revolution\modDocument, если вы не указалиclass_keyв CSV, и тем самым избавит от ошибок при создании/обновлении.
2. Разделяем логику create и update
В этом же файле startimport.class.php заменяем метод process()на обновлённый вариант.
Найдите существующий public function process() и полностью замените его на код ниже:
public function process()
{
// Получаем и готовим данные
$this->importX->getData();
sleep(1);
$lines = $this->importX->prepareData();
if ($lines === false) {
$this->importX->log('complete','');
return $this->modx->error->failure();
}
$this->importX->log('info', $this->modx->lexicon('importx.log.importvaluesclean', ['count' => count($lines)]));
$resourceCount = 0;
$processorMode = $this->modx->getOption('importx.processor', null, 'create'); // create или update
$processor = 'resource/' . $processorMode;
foreach ($lines as $line) {
// Для MODX 3 подставляем class_key при необходимости
$line = $this->setDefaultClassKey($line);
// ===== РЕЖИМ СОЗДАНИЯ (create) =====
if ($processorMode === 'create') {
/** @var modProcessorResponse $response */
$response = $this->modx->runProcessor($processor, $line);
}
// ===== РЕЖИМ ОБНОВЛЕНИЯ (update) =====
else {
$id = (int)($line['id'] ?? 0);
if (!$id) {
$this->modx->log(modX::LOG_LEVEL_ERROR, 'importX: ID is required for update, skipping row');
continue;
}
/** @var modResource $resource */
$resource = $this->modx->getObject('modResource', $id);
if (!$resource) {
$this->modx->log(modX::LOG_LEVEL_ERROR, "importX: Resource with ID {$id} not found, skipping row");
continue;
}
// Берём все поля из существующего ресурса
$data = $resource->toArray();
// Перезаписываем только те поля, которые пришли в CSV (кроме TV)
foreach ($line as $field => $value) {
if ($value === '' || $value === null) {
continue;
}
if (strpos($field, 'tv') === 0) {
continue;
}
$data[$field] = $value;
}
// Обязательные поля
$data['id'] = $id;
// alias: либо из CSV, либо текущий alias ресурса
if (!empty($line['alias'])) {
$data['alias'] = $line['alias'];
} else {
$data['alias'] = $resource->get('alias');
}
/** @var modProcessorResponse $response */
$response = $this->modx->runProcessor('resource/update', $data);
// Если обновление прошло успешно — обновляем TV по CSV
if (!$response->isError()) {
foreach ($line as $field => $value) {
if (strpos($field, 'tv') !== 0) {
continue;
}
$tvId = (int)substr($field, 2);
if ($tvId > 0) {
/** @var modTemplateVar $tv */
$tv = $this->modx->getObject('modTemplateVar', $tvId);
if ($tv) {
$tv->setValue($resource->get('id'), $value);
$tv->save();
}
}
}
}
}
if ($response->isError()) {
if ($response->hasFieldErrors()) {
$fieldErrors = $response->getAllErrors();
$errorMessage = implode("\n", $fieldErrors);
} else {
$errorMessage = $this->modx->lexicon('importx.err.savefailed') . "\n" . print_r($response->getMessage(),true);
}
$this->importX->log('warn', $resourceCount.' of ' . count($lines) . ' resources were imported successfully');
$this->importX->log('error', $errorMessage);
return $this->importX->log('complete','');
} else {
$resourceCount++;
}
}
sleep(1);
$this->importX->log('info', $this->modx->lexicon('importx.log.complete', ['count' => $resourceCount]));
sleep(1);
$this->importX->log('complete', '');
sleep(1);
return $this->success();
}
Как этим пользоваться после фикса
- В системных настройках установите:
importx.processor = update— для режима обновления. - Подготовьте CSV минимум с колонками
idиalias, плюс нужные поля/TV:
id;alias;tv1;tv2 14;certificates;Seo title;Seo description
- Запустите импорт как обычно через ImportX.
- ImportX:
- найдёт ресурс с указанным
id; - подставит его текущие данные;
- поверх обновит только те поля, что есть в CSV;
- отдельно сохранит TV
tv1,tv2и т.д. для этого ресурса.
- найдёт ресурс с указанным
Важно: режим update после этого фикса не создаёт новые ресурсы, если ID не найден — такие строки просто пропускаются с записью в лог. Для создания новых страниц продолжайте использовать режим create и стандартный сценарий из начала урока.
Заключение
ImportX хорош только тем что он бесплатный. Его можно использовать для создания категорий. Импортировать ресурсы с большим количеством контента у меня при помощи него не получилось. Но вы можете попробовать, для этого вам придется задать нестандартный разделитель типа «;;;» и скорее всего уже использовать CSV файл.
Для импорта ресурсов с большим количеством контента, я использую платное дополнение с modstore: GoogleSheets.
Для импорта категорий и товаров в minishop использую msImportExport (не смотря на то что GoogleSheets тоже может это делать). PS. Уже может импортировать ресурсы и т.д.
В следующем уроке разберем дополнение FormIt — которое поможет оживить формы сайта.










В результате тестирования режима обновления нарвался на баг, который делает этот функционал неюзабельным.
Баг подтвержден в ишьюсах — https://github.com/modmore/importX/issues/64
Частичное решение есть в этом же ишьюсе, но, имхо, не стоит данный функционал вообще упоминать, как рабочий
Честно говоря не пользовался функцией обновления) Посмотрел исходник на гитхаб, увидел косяки, которые правятся в принципе) Добавил в статью фикс этого бага (не испытывал его, но работать должен). Можете протестить и отписаться)
Я пошел немного другим путем — переработал скрипт /core/components/minishop2/import/csv.php.
Ваше решение протестирую на этой неделе и отпишусь
Тоже вариант)
Проверил. Ситуация не изменилась. Дело в том, что в принципе этот процессор «update» как-то странно реализован. Вместо того,чтобы банально обновлять поля ресурса по его id, реализована странная логика. Там куча обязательных полей надо указывать, чтобы все прошло гладко (см. https://webinmind.ru/modx/solutions/working-with-resources/editing-resources-through-the-processor?ysclid=mlmllx4glt511275660). Мне проще было вместо него использовать прямую замену полей через использование связки:
$res = $modx->getObject(‘modResource’, $data[‘id’]);
+ для основных полей:
$res->set($field_id, $field_value);
$res->save();
+ для доп. полей:
$res->setTVValue((int)$field_tv_id, $field_tv_value); // сохранять ресурс в этом случае не надо
Ага, хорошо что поделились. Возможно как нибудь займусь и сделаю форк рабочий и более функциональный
еще момент — в случае обновления, необходимо указывать id ресурса и его alias. Т.е. импорт должен быть вида:
id;alias;tv1;tv2
14;certificates;Seo title;Seo description
Если не сложно, добавьте в текст статьи инфо про обновление контента. Для этого необходимо в системных настройках переключить параметр ‘importx.processor’ c ‘create’ на ‘update’. В этом случае уже существующий контент будет обновлен, а не существующий — создан.
с уважением
Не сложно, добавил)
Спасибо! А есть какой-то вариант затем экспортировать это на другой сайт? Который тоже конечно же на modx revo?)
Я пользуюсь для таких целей плагином https://modstore.pro/packages/import-and-export/msimportexport