Extending
Three registries, all event-driven: collectors turn an element type into records, writers turn records into files, and value serializers make one field type’s values portable. Each is a few dozen lines from a module.
Add a collector
Teach Archive about an element type it doesn’t ship support for — your plugin’s own, or one from a plugin Archive has never heard of.
use justinholtweb\archive\services\CollectorRegistry;
use justinholtweb\archive\events\RegisterCollectorsEvent;
use yii\base\Event;
Event::on(
CollectorRegistry::class,
CollectorRegistry::EVENT_REGISTER_COLLECTORS,
function(RegisterCollectorsEvent $event) {
$event->collectors[] = new ProductCollector();
}
);
A collector implements CollectorInterface:
| Method | Does what |
|---|---|
key() | The key records are filed under in a bundle — entries, products. Must be unique and safe as a filename, since CSV writes one file per key. |
label() | The name shown on the export screen. |
isAvailable() | Whether it can run right now. Collectors for optional plugins return false when that plugin isn’t installed, and simply don’t appear. |
collect() | Pushes records into the export context. |
estimate() | Roughly how many records will result, so a queued export can show progress. Should count, not collect. |
Extending BaseCollector gets you most of it — the element query batching, per-site iteration, field serialization and relation extraction are all there. Two hooks worth knowing: isLocalized(), which returns false for element types with no per-site content so they’re walked once instead of per site, and shouldCollect(), for filtering a query can’t express.
Add a format
A writer takes the collected records and puts files in the staging directory. This is where a target-specific format goes — WordPress WXR, an in-house importer’s shape — without Archive having to bless it.
use justinholtweb\archive\services\WriterRegistry;
use justinholtweb\archive\events\RegisterWritersEvent;
use yii\base\Event;
Event::on(
WriterRegistry::class,
WriterRegistry::EVENT_REGISTER_WRITERS,
function(RegisterWritersEvent $event) {
$event->writers[] = new WxrWriter();
}
);
WriterInterface is three methods: format() for the key used in settings and on the form, label() for the dropdown, and write($context, $stagingDir), which returns the bundle-relative paths of the files it wrote so the manifest can list them.
Registered writers appear in the format dropdown and are accepted by --format on the console command, with no further wiring.
Teach it a field type
Without a serializer, an unrecognised field still exports — through the field’s own serializeValue(), as kind: "raw". The value travels, but what comes out is whatever that plugin happens to store, which usually means Craft element IDs that mean nothing anywhere else. A serializer fixes that.
use justinholtweb\archive\services\FieldSerializer;
use justinholtweb\archive\events\RegisterValueSerializersEvent;
use yii\base\Event;
Event::on(
FieldSerializer::class,
FieldSerializer::EVENT_REGISTER_VALUE_SERIALIZERS,
function(RegisterValueSerializersEvent $event) {
$event->serializers[] = new MyFieldSerializer();
}
);
ValueSerializerInterface is two methods: supports($field), and serialize($value, $element, $field, $context) returning kind and value. Guard supports() on class_exists() so the serializer is inert when the plugin it covers isn’t installed — that’s how the ones that ship with Archive (Hyper, FreeLink, Google Maps, SEOMatic) stay out of the way.
Prefer an existing kind over inventing one. An importer already knows what link, relation and blocks mean; a bespoke kind is one more thing for it to learn.
Hook the export itself
use justinholtweb\archive\services\Export;
use justinholtweb\archive\events\ExportEvent;
use yii\base\Event;
Event::on(Export::class, Export::EVENT_BEFORE_EXPORT, function(ExportEvent $event) {
// Cancel the run outright
if (!MyPolicy::allows($event->config)) {
$event->isValid = false;
}
});
Event::on(Export::class, Export::EVENT_AFTER_EXPORT, function(ExportEvent $event) {
// $event->bundle is the ledger row — ship it somewhere, notify someone
MyUploader::send($event->bundle);
});
EVENT_BEFORE_EXPORT carries the config and the context and can cancel the run by setting isValid to false. EVENT_AFTER_EXPORT also carries the finished bundle ledger row, which is the hook for pushing a bundle to S3, opening a ticket, or firing a webhook when a scheduled export completes.
Adding warnings
Anything your code can’t represent losslessly belongs in the manifest rather than in a log nobody reads:
$context->warn("Couldn’t export the “specs” field: unsupported unit system.");
Warnings surface on the bundle detail screen and in manifest.json. The contract Archive tries to keep is that an empty warnings array means the export really was complete — so err towards warning.