Data sync destination
Build an Optimizely Connect Platform (OCP) app that creates custom destinations that users who install your app can use in data syncs in the OCP Sync Manager.
You can add a custom destination to your Optimizely Connect Platform (OCP) app by completing the following steps:
- Declare destinations in the app manifest file (
app.yml
). - Declare the schema for your destinations (one schema per destination).
src/destinations/schema/*.yml
orsrc/destinations/{SchemaFunction}.ts
- Implement the logic for the destinations.
Prerequisite
Register and scaffold your app.
Declare destinations in the app manifest file
You can define multiple destinations in a single app. You must declare every destination in the destinations
section of the app manifest file (app.yml
), and each destination must have its own schema and logic.
Required properties
schema
– A string referencing a static schema name (must match the schema name defined insrc/destinations/schemas/
) or an object withentry_point
for dynamic schemas.entry_point
– Name of the class implementing the sync logic.
Optional properties
description
– Human-readable sync purpose.
For example:
destinations:
bynder_asset_sync:
entry_point: BynderAssetSync
description: Sync Assets to Bynder
schema: asset
When a user installs your app, any destinations you defined in the app display in the Destinations drop-down list on the Sync Manager page in OCP.

Declare the schema
Schema is where you define which properties your destinations accept, which then display as options in the data sync field mappings. OCP calls your destination class method with properties defined in your schema.

You can choose to implement a static or dynamic schema:
- Static – Use when your data structure is fixed and known in advance, all destinations of this type share the same data structure, and you want to define the schema declaratively without code.
- Dynamic schema – Use when you need to fetch schema information from external systems, and when different instances of the same destination might have different fields.
Declare static schema
You must create a matching .yml
file in src/destinations/schemas
for each schema
field you defined in the app.yml
file with the following:
name
– Unique identifier matching theschema
value in the manifest.display_name
– User-friendly name.description
– The field's purpose.fields
– Data structure and validation rules.type
– Type of value for the field. The following types are supported:string
boolean
int
float
long
primary
– Designates the unique identifier field for the object.
The following is an example for asset.yml
schema:
name: asset
display_name: Asset
fields:
- name: bynder_id
type: string
display_name: Bynder Id
description: Primary Id in Bynder
primary: true
- name: bynder_asset_name
type: string
display_name: Bynder Asset Name
description: The name of the Asset
Declare dynamic schema
You must create and export a class in src/destinations/
that extends DestinationSchemaFunction
. The class name must match the schema entry_point
in the app manifest.
Implement the getDestinationsSchema()
method, which returns a DestinationSchema
object.
The following shows an example of the app manifest:
destinations:
bynder_asset_sync:
...
schema:
entry_point: BynderAssetSchema
...
The following is an example implementation:
import * as App from '@zaiusinc/app-sdk';
import { Bynder } from '../lib/Bynder';
const convertType = (type: string): string | undefined => {
switch (type) {
case 'string':
return 'string';
case 'boolean':
return 'boolean';
case 'int':
return 'int';
case 'float':
return 'float';
case 'long':
return 'long';
default:
return undefined;
}
};
export class BynderAssetSchema extends App.DestinationSchemaFunction {
public async getDestinationsSchema(): Promise<App.DestinationSchema> {
const bynder = await Bynder.init();
const rawFields = await bynder.getFullFieldList();
const fields: App.DestinationSchemaField[] = rawFields.reduce((acc, field) => {
const type = convertType(field.type);
if (!type) return acc; // Skip fields with unknown type
acc.push({
name: field.id,
display_name: field.name,
description: field.name,
type,
primary: field.id === 'id'
});
return acc;
}, [] as any[]);
return {
name: 'bynder_asset',
description: 'Bynder Asset',
display_name: 'Bynder Asset',
fields: fields.sort((a, b) => a.display_name.localeCompare(b.display_name)),
};
}
}
Implement the logic
Define your destination logic in a class that extends the Destination<T>
abstract class. OCP calls your destination class methods. The key method is deliver
, which accepts your list of items and sends them to your destination service. Item property names match the fields you defined in your schema and are the values that display as options in the data sync field mappings.
Class structure
- Extend
Destination<T>
insrc/destinations
directory. - Match the class name with
entry_point
in the app manifest file (for example,BynderAssetSync
).
Methods to implement
ready()
- Check credentials and configuration validity.
- Return
DestinationReadyResult
withready: boolean
and errormessage
, if applicable.
deliver(batch: DestinationBatch<T>)
- Process
DestinationBatch<T>
containing the following:items[]
– List of items to process.attempt
– Count of delivery attempts.sync
–id
andname
of the data sync.
- Return
DestinationDeliverResult
with the following:success
– Indicates batch completion status.retriable
– Flag for retry eligibility on failure.failureReason
– (Optional) Internal error description.
- Process
The following is an example implementation:
import * as App from '@zaiusinc/app-sdk';
export interface BynderAsset {
id: string;
name: string;
}
export class BynderAssetSycn extends App.Destination<BynderAsset>{
public async ready(): Promise<App.DestinationReadyResult> {
return { ready: true };
}
public async deliver(batch: App.DestinationBatch<BynderAsset>): Promise<App.DestinationDeliverResult> {
return { success: true, retryable: false };
}
}
Handle errors and retries
Handle errors and retries in the deliver
method.
- Use
retriable: true
for transient errors (like network issues). A batch is retried a maximum of three times. - Set
retriable: false
for non-recoverable errors (like invalid credentials).
Directory Structure
- Destination classes –
src/destinations/{EntryPointClassName}.ts
- Schemas –
src/destinations/schema/*.yml
See the following example:
// successfully processed, no retry
return { success: true, retriable: false };
// unsuccesfully processed, no retry
return { success: false, retriable: false };
// unsuccessfully processed, retry
return { success: false, retriable: true };
Complete and publish your app
Updated 11 days ago