Export delta connector
Send only the products that changed since the last run.
An export-delta connector works like an export connector, but instead of sending all products to the channel every run, it determines which products to send based on what changed since the last run. The platform tracks product data statuses between runs and provides four separate input streams — new, modified, unchanged, and deleted — so your connector can perform the appropriate action for each product (create, update, skip, or delete).
Use export-delta when the third-party channel supports incremental updates — it's more efficient and reduces API calls.
Data flow
Productsup platform → Container API (4 delta streams) → Your connector → Third-party channel
↓
Container API (feedback) → Productsup platformInstead of a single input file, the Container API provides four streams based on what changed:
| Stream | Description |
|---|---|
| New | Products added to the site since the last run |
| Modified | Products that changed since the last run |
| Unchanged | Products with no changes since the last run |
| Deleted | Products removed since the last run |
How it works at runtime
- The platform starts your Docker container and passes export settings as environment variables
- Your code reads products from one or more of the four delta streams
- For each product, you perform the appropriate action on the third-party channel (create, update, skip, or delete)
- You write feedback for each product (success or failure)
- You send progress notifications to the Productsup notification panel
- Exit with code
0(success) or non-zero (failure) - The platform imports the feedback file as a data source on the next site run
Reading delta input
Each stream has its own methods for iterating and counting.
Iterate by stream
// New products — create them on the channel
foreach ($this->containerApi->yieldFromNewFile() as $product) {
$this->createOnChannel($product);
}
// Modified products — update them on the channel
foreach ($this->containerApi->yieldFromModifiedFile() as $product) {
$this->updateOnChannel($product);
}
// Unchanged products — usually skipped
foreach ($this->containerApi->yieldFromUnchangedFile() as $product) {
// No action needed in most cases
}
// Deleted products — remove them from the channel
foreach ($this->containerApi->yieldFromDeletedFile() as $product) {
$this->deleteFromChannel($product);
}Iterate in batches
Each stream also supports batch iteration:
foreach ($this->containerApi->yieldBatchFromNewFile(100) as $batch) {
$this->createBatchOnChannel($batch);
}
foreach ($this->containerApi->yieldBatchFromModifiedFile(100) as $batch) {
$this->updateBatchOnChannel($batch);
}Count items per stream
$newCount = $this->containerApi->countItemsFromNewFile();
$modifiedCount = $this->containerApi->countItemsFromModifiedFile();
$unchangedCount = $this->containerApi->countItemsFromUnchangedFile();
$deletedCount = $this->containerApi->countItemsFromDeletedFile();Writing feedback
Same as export connectors — write feedback for each product to report success or failure. The platform imports the feedback file as an additional data source on the next site run.
$this->containerApi->appendToFeedbackFile([
'id' => $product['id'],
'status' => 'success',
]);Each export destination can have at most one feedback file. See Feedback as an additional data source for how connection columns work.
Logging and notifications
Use log methods for operational messages visible in the Dev Portal run logs. Use notifications for end-user-facing messages that appear in the Productsup notification panel. End-users see notifications in a collapsible panel at the bottom of their site view:

// Logging — visible in Dev Portal run logs
$this->containerApi->info("Processing 50 new, 20 modified, 5 deleted products.");
$this->containerApi->warning("Product {$id} not found on channel, skipping delete.");
$this->containerApi->error("Channel API returned 500 for product {$id}.");
// Notifications — visible to end-users in the Productsup notification panel
$this->containerApi->sendNotification('success', 'Delta export completed: 75 products processed.');
$this->containerApi->sendNotification('warning', 'Delta export completed with 3 errors.');Available log levels: debug, info, notice, warning, error, critical, alert, emergency.
Available notification levels: info, notice, warning, error, success.
Example service
A minimal export-delta service that processes each delta stream, writes feedback, and reports progress:
<?php
namespace App\ExportDelta\Service;
use Productsup\CDE\ContainerApi\ContainerApiInterface;
readonly class ExportDeltaService
{
public function __construct(
private ContainerApiInterface $containerApi,
) {}
public function run(): void
{
$newCount = $this->containerApi->countItemsFromNewFile();
$modifiedCount = $this->containerApi->countItemsFromModifiedFile();
$deletedCount = $this->containerApi->countItemsFromDeletedFile();
$this->containerApi->info("Delta export: {$newCount} new, {$modifiedCount} modified, {$deletedCount} deleted.");
$errorCount = 0;
$errorCount += $this->processStream(
$this->containerApi->yieldFromNewFile(),
fn(array $product) => $this->createOnChannel($product),
);
$errorCount += $this->processStream(
$this->containerApi->yieldFromModifiedFile(),
fn(array $product) => $this->updateOnChannel($product),
);
$errorCount += $this->processStream(
$this->containerApi->yieldFromDeletedFile(),
fn(array $product) => $this->deleteFromChannel($product),
);
$totalProcessed = $newCount + $modifiedCount + $deletedCount;
if ($errorCount > 0) {
$this->containerApi->sendNotification('warning', "Delta export completed with {$errorCount} errors out of {$totalProcessed} products.");
return;
}
$this->containerApi->sendNotification('success', "Delta export completed: {$totalProcessed} products processed.");
}
private function processStream(\Traversable $stream, \Closure $action): int
{
$errorCount = 0;
foreach ($stream as $product) {
try {
$action($product);
$this->containerApi->appendToFeedbackFile([
'id' => $product['id'],
'status' => 'success',
]);
} catch (\Throwable $e) {
$errorCount++;
$this->containerApi->warning("Failed to process product {$product['id']}: {$e->getMessage()}");
$this->containerApi->appendToFeedbackFile([
'id' => $product['id'],
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
return $errorCount;
}
private function createOnChannel(array $product): void { /* Your create API call */ }
private function updateOnChannel(array $product): void { /* Your update API call */ }
private function deleteFromChannel(array $product): void { /* Your delete API call */ }
}Export vs. export delta
| Export | Export delta | |
|---|---|---|
| Input | All products every run | Only products that changed |
| Streams | 1 (input) | 4 (new, modified, unchanged, deleted) |
| Best for | Channels that expect a full feed | Channels that support incremental updates |
| Efficiency | Processes everything every time | Only processes changes |
Your connector type must match the input it requests. An export connector requesting new or modified input, or an export-delta connector requesting full input, will cause an error.
Configuration
Export-delta connectors share the same type-specific configuration as export connectors: channels, feedback file, and category-specific attributes.
See Type-specific config for details.
Next steps
- Export connector — if you need full-feed exports instead
- Container API reference — full API for reading delta input and writing feedback
How is this guide?