# Reverse ETL Connection YAML Reference

<p>This guide is the YAML reference for Reverse ETL connection resources in a Rudder CLI project.</p>

<blockquote class="warning">
  <div class="tip-quote">
    
    <div class="tip-text"><p><strong>Reverse ETL connection support is experimental and requires Rudder CLI v0.27.0 or later</strong>.</p>
<p>See <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/retl-connections/#enable-reverse-etl-connection-support" >Enable Reverse ETL connection support</a> for the flags to set before you validate or apply these specs.</p>
</div>
  </div>
</blockquote>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="overview">Overview</h2><p>Set <code>kind</code> to <code>retl-connections</code> and <code>version</code> to <code>rudder/v1</code>. <code>spec.connections</code> is a <strong>list</strong>. Each entry is one Reverse ETL source to destination link, together with its sync configuration. Store the files anywhere under the project root.</p>
<p>Each <code>source</code> must reference a <a href="#supported-sources" >supported Reverse ETL source</a>, and each <code>destination</code> a <a href="#supported-destinations" >supported destination</a>. Unlike <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/yaml-connections/" >Event Stream connections</a>, every entry needs a <code>config</code> block.</p>
<p>Each connection uses one of two mapping flows: object mapping if <code>config.object</code> is set (the dashboard&rsquo;s Visual Data Mapper), JSON mapper otherwise (the JSON Data Mapper). See <a href="#mapping-flows" >Mapping flows</a>. The <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/yaml-destinations/" >destination spec</a> must set <code>connection_mode.warehouse</code>; see <a href="#destination-requirements" >Destination requirements</a>.</p>
<p>Rudder CLI rejects keys it doesn&rsquo;t recognize at every level of the spec, so a misspelled key fails validation instead of being ignored.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="examples">Examples</h2><div class="rs-tabs">
	<div class="rs-tabs__list">
		<button type="button" class="rs-tabs__tab"
			id="tab-dacefb"
		
		>JSON mapper</button>
		<button type="button" class="rs-tabs__tab"
			id="tab-becadf"
		
		>Object mapping</button>
	</div>
	
<div class="rs-tabs__panel" id="panel-dacefb">

<p>This connection syncs a SQL model to an HTTP Webhook destination every 30 minutes, sending each row as an <code>identify</code> event:</p>
<div class="rs-code">
  <div class="rs-code__head">yaml<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml">version: rudder/v1
kind: retl-connections
metadata:
  name: retl-connections
spec:
  connections:
    - id: users-to-webhook
      source: &#34;#retl-source-sql-model:users-model&#34;
      destination: &#34;#destination:my-http-destination&#34;
      enabled: true
      config:
        sync_behaviour: upsert
        schedule:
          type: basic
          every_minutes: 30
        identifiers:
          - from: user_id
            to: user_id
        mappings:
          - from: email
            to: traits.email
        event:
          type: identify</code></pre></div>
</div>

</div>

<div class="rs-tabs__panel" id="panel-becadf" hidden>

<p>This connection syncs a SQL model to the <code>contacts</code> object of a HubSpot destination every hour. Setting <code>object</code> selects the object mapping flow, so the entry has a single identifier and no <code>event</code>:</p>
<div class="rs-code">
  <div class="rs-code__head">yaml<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml">version: rudder/v1
kind: retl-connections
metadata:
  name: retl-connections
spec:
  connections:
    - id: users-to-hubspot
      source: &#34;#retl-source-sql-model:users-model&#34;
      destination: &#34;#destination:hubspot-prod&#34;
      config:
        object: contacts
        sync_behaviour: upsert
        schedule:
          type: basic
          every_minutes: 60
        identifiers:
          - from: email
            to: email
        mappings:
          - from: first_name
            to: firstname</code></pre></div>
</div>
<p>Rudder CLI doesn&rsquo;t check <code>object</code> or the <code>to</code> names against the destination. To get the exact names for your destination, set up a connection in the dashboard&rsquo;s Visual Data Mapper and <a href="#import-existing-connections" >import it</a>.</p>

</div>

</div>

<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="connection-fields">Connection fields</h2><p>Each entry in <code>spec.connections</code> has these fields:</p>
<table>
  <thead>
      <tr>
          <th>Property</th>
          <th>Type</th>
          <th><div>Description</div></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>id</code> <br/> Required</td>
          <td>String</td>
          <td>Local identifier. Must be unique across Reverse ETL connection entries in the project. Don&rsquo;t change it after the first apply.</td>
      </tr>
      <tr>
          <td><code>source</code> <br/> Required</td>
          <td>String</td>
          <td>Reference to a Reverse ETL source in this project — <code>#retl-source-sql-model:[source.id]</code>. See <a href="#supported-sources" >Supported sources</a>.</td>
      </tr>
      <tr>
          <td><code>destination</code> <br/> Required</td>
          <td>String</td>
          <td>Reference to a destination in this project — <code>#destination:[destination.id]</code>.</td>
      </tr>
      <tr>
          <td><code>enabled</code></td>
          <td>Boolean</td>
          <td>Whether this connection syncs. Defaults to <code>true</code>. Both the source and the destination must also be enabled for the connection to sync; <code>validate</code> warns if they are not.</td>
      </tr>
      <tr>
          <td><code>config</code> <br/> Required</td>
          <td>Object</td>
          <td>Sync configuration for the connection. See <a href="#sync-configuration" >Sync configuration</a>.</td>
      </tr>
  </tbody>
</table>
<p>Changing <code>source</code> or <code>destination</code> replaces the connection (delete + create). The backend allows only one connection per source–destination pair. See <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/retl-connections/#apply-and-replace-behavior" >Apply and replace behavior</a>.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="sync-configuration">Sync configuration</h3><p>The <code>config</code> object has these fields:</p>
<table>
  <thead>
      <tr>
          <th>Property</th>
          <th>Type</th>
          <th><div>Description</div></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>sync_behaviour</code> <br/> Required</td>
          <td>String</td>
          <td><a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-modes/" >Sync mode</a> — one of <code>upsert</code>, <code>mirror</code>, or <code>full</code>. <code>mirror</code> needs the <a href="#mapping-flows" >object mapping flow</a>. A table source on <code>s3</code> accepts only <code>upsert</code>. <br /><br />Immutable after the connection is created.</td>
      </tr>
      <tr>
          <td><code>schedule</code> <br/> Required</td>
          <td>Object</td>
          <td>When the connection syncs. See <a href="#schedule" >Schedule</a>.</td>
      </tr>
      <tr>
          <td><code>identifiers</code> <br/> Required</td>
          <td>Array</td>
          <td>One or more <code>from</code>/<code>to</code> pairs that map a warehouse column to a record identifier. In the JSON mapper flow, every <code>to</code> must be <code>user_id</code> or <code>anonymous_id</code>. In the object mapping flow, supply exactly one entry whose <code>to</code> is the destination field that identifies a record. See <a href="#mapping-flows" >Mapping flows</a>.</td>
      </tr>
      <tr>
          <td><code>mappings</code></td>
          <td>Array</td>
          <td><code>from</code>/<code>to</code> pairs that map a warehouse column to a destination field. Required in the JSON mapper flow.</td>
      </tr>
      <tr>
          <td><code>constants</code></td>
          <td>Array</td>
          <td><code>key</code>/<code>value</code> pairs sent with every synced record. Both are required and must be non-empty strings, so quote a number or a boolean. JSON mapper flow only.</td>
      </tr>
      <tr>
          <td><code>event</code></td>
          <td>Object</td>
          <td>The event that RudderStack sends for each record. Defaults to <code>identify</code>. JSON mapper flow only. See <a href="#event" >Event</a>. <br /><br />Immutable after the connection is created.</td>
      </tr>
      <tr>
          <td><code>object</code></td>
          <td>String</td>
          <td>The destination object to sync to. Setting it selects the object mapping flow. It must be non-empty and carry no leading or trailing whitespace — <code>object: &quot;&quot;</code> fails validation rather than falling back to the JSON mapper flow. <br /><br />Immutable after the connection is created.</td>
      </tr>
      <tr>
          <td><code>cursor_column</code></td>
          <td>String</td>
          <td>Warehouse column used for <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/cursor-column-support/" >incremental syncs</a>. Valid only when <code>sync_behaviour</code> is <code>upsert</code>. <br /><br />Immutable after the connection is created.</td>
      </tr>
      <tr>
          <td><code>sync_settings</code></td>
          <td>Object</td>
          <td>Sync log and retry settings. See <a href="#sync-settings" >Sync settings</a>.</td>
      </tr>
  </tbody>
</table>
<p>To change a field marked immutable, see <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/retl-connections/#change-an-immutable-field" >Change an immutable field</a>.</p>
<p>Every <code>from</code> value, <code>cursor_column</code>, and <code>event.name_column</code> is a warehouse column name. It must start with a letter or an underscore, followed by letters, digits, underscores, <code>$</code>, or <code>.</code>.</p>
<p>A <code>constants</code> entry can&rsquo;t use <code>context.mappedToDestination</code> as its <code>key</code>. RudderStack sets that key itself.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="schedule">Schedule</h3><p>The <code>schedule</code> object sets the <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-schedule-settings/" >sync schedule</a>:</p>
<table>
  <thead>
      <tr>
          <th>Property</th>
          <th>Type</th>
          <th><div>Description</div></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>type</code> <br/> Required</td>
          <td>String</td>
          <td>One of <code>basic</code>, <code>cron</code>, or <code>manual</code>.</td>
      </tr>
      <tr>
          <td><code>every_minutes</code></td>
          <td>Integer</td>
          <td>Interval between syncs, in minutes. Required when <code>type</code> is <code>basic</code>, and not allowed otherwise. The minimum is <code>5</code>.</td>
      </tr>
      <tr>
          <td><code>cron_expression</code></td>
          <td>String</td>
          <td>Cron expression for the schedule. Required when <code>type</code> is <code>cron</code>, and not allowed otherwise. See <a href="#cron-expressions" >Cron expressions</a>.</td>
      </tr>
  </tbody>
</table>
<p>A <code>manual</code> schedule takes no other fields. The connection syncs only when you trigger it.</p>
<p>Quote a cron expression. YAML treats a leading <code>*</code> or <code>@</code> as syntax, so an unquoted <code>*/10 * * * *</code> or <code>@hourly</code> fails with a YAML parse error:</p>
<div class="rs-code">
  <div class="rs-code__head">yaml<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml">schedule:
  type: cron
  cron_expression: &#34;*/10 * * * *&#34;</code></pre></div>
</div>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="event">Event</h3><p>The <code>event</code> object applies to the JSON mapper flow:</p>
<table>
  <thead>
      <tr>
          <th>Property</th>
          <th>Type</th>
          <th><div>Description</div></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>type</code> <br/> Required</td>
          <td>String</td>
          <td>One of <code>identify</code> or <code>track</code>.</td>
      </tr>
      <tr>
          <td><code>name</code></td>
          <td>String</td>
          <td>Fixed event name. You can&rsquo;t set it together with <code>name_column</code>.</td>
      </tr>
      <tr>
          <td><code>name_column</code></td>
          <td>String</td>
          <td>Warehouse column that holds the event name. You can&rsquo;t set it together with <code>name</code>.</td>
      </tr>
  </tbody>
</table>
<p>If you omit <code>event</code>, RudderStack sends <code>identify</code> events. Rudder CLI also accepts a <code>track</code> event with neither <code>name</code> nor <code>name_column</code>, but RudderStack has no default event name, so set one. <code>event</code> is immutable, so set it when you create the connection.</p>
<p>This <code>track</code> event reads its name from a column, and a constant adds a fixed value to every record:</p>
<div class="rs-code">
  <div class="rs-code__head">yaml<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml">config:
  event:
    type: track
    name_column: event_name
  constants:
    - key: properties.source
      value: warehouse</code></pre></div>
</div>
<p>See <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/syncing-events/" >Sync Data from Reverse ETL Sources</a> for how RudderStack sends records as <code>identify</code> and <code>track</code> events.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="sync-settings">Sync settings</h3><p>The <code>sync_settings</code> object controls <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-observability/" >sync observability</a>. Every field is optional:</p>
<table>
  <thead>
      <tr>
          <th>Property</th>
          <th>Type</th>
          <th>Default</th>
          <th><div>Description</div></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>sync_logs.enabled</code></td>
          <td>Boolean</td>
          <td><code>true</code></td>
          <td>Whether RudderStack stores the sync logs in your warehouse.</td>
      </tr>
      <tr>
          <td><code>sync_logs.retention_days</code></td>
          <td>Integer</td>
          <td><code>30</code></td>
          <td>Number of days RudderStack keeps the sync logs.</td>
      </tr>
      <tr>
          <td><code>sync_logs.snapshots_to_retain</code></td>
          <td>Integer</td>
          <td><code>5</code></td>
          <td>Number of snapshot tables RudderStack keeps.</td>
      </tr>
      <tr>
          <td><code>failed_keys.retry</code></td>
          <td>Boolean</td>
          <td><code>true</code></td>
          <td>Whether RudderStack retries failed records in later syncs.</td>
      </tr>
  </tbody>
</table>
<div class="rs-code">
  <div class="rs-code__head">yaml<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml">config:
  sync_settings:
    sync_logs:
      enabled: true
      retention_days: 7
      snapshots_to_retain: 3
    failed_keys:
      retry: false</code></pre></div>
</div>
<p>Omitted and removed fields behave as follows:</p>
<ul>
<li>Omitting <code>sync_settings</code> keeps all four defaults.</li>
<li>A partial block takes the default for every field you leave out. For example, setting only <code>retention_days: 7</code> keeps sync logs enabled with five snapshots, and keeps retries on.</li>
<li><code>false</code> and <code>0</code> are real settings, not omissions. Rudder CLI sends them as written.</li>
<li>Removing a block that held non-default values resets the stored settings to the defaults on the next apply.</li>
<li>A table source on <code>s3</code> doesn&rsquo;t support <code>sync_settings</code>.</li>
</ul>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="mapping-flows">Mapping flows</h2><p>A connection maps data in one of two flows. No field names the flow. Rudder CLI derives it from whether <code>object</code> is set.</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th><a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/json-data-mapping/" >JSON mapper</a></th>
          <th><a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/visual-data-mapper/" >Object mapping</a></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Selected when</td>
          <td><code>object</code> is absent</td>
          <td><code>object</code> is set</td>
      </tr>
      <tr>
          <td>Destinations</td>
          <td>Every <a href="#supported-destinations" >supported destination</a></td>
          <td>Destinations that support object mapping</td>
      </tr>
      <tr>
          <td><code>identifiers</code></td>
          <td>One or more, each with <code>to</code> set to <code>user_id</code> or <code>anonymous_id</code></td>
          <td>Exactly one, with <code>to</code> set to the destination field that identifies a record</td>
      </tr>
      <tr>
          <td><code>mappings</code></td>
          <td>Required</td>
          <td>Optional</td>
      </tr>
      <tr>
          <td><code>constants</code></td>
          <td>Allowed</td>
          <td>Not allowed</td>
      </tr>
      <tr>
          <td><code>event</code></td>
          <td>Allowed</td>
          <td>Not allowed</td>
      </tr>
      <tr>
          <td><code>mirror</code> sync mode</td>
          <td>Not available</td>
          <td>Available</td>
      </tr>
  </tbody>
</table>
<p>A <code>mappings</code> entry can&rsquo;t target a field that the flow reserves for identifiers:</p>
<table>
  <thead>
      <tr>
          <th>Flow</th>
          <th>Reserved <code>to</code> values</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>JSON mapper</td>
          <td><code>user_id</code>, <code>anonymous_id</code></td>
      </tr>
      <tr>
          <td>Object mapping</td>
          <td><code>user_id</code>, <code>context.externalId[0].id</code></td>
      </tr>
  </tbody>
</table>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="supported-sources">Supported sources</h2><table>
  <thead>
      <tr>
          <th>Source</th>
          <th>Reference</th>
          <th>Notes</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/yaml-sql-models/" >SQL model</a></td>
          <td><code>#retl-source-sql-model:[source.id]</code></td>
          <td>Any <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/yaml-sql-models/#supported-warehouse-types" >warehouse type</a> a SQL model supports.</td>
      </tr>
      <tr>
          <td>Table source</td>
          <td><code>#retl-source-table:[source.id]</code></td>
          <td>Experimental. Also set <code>RUDDERSTACK_X_RETL_TABLE_SUPPORT=true</code> (config key <code>flags.retlTableSupport</code>). Without it, Rudder CLI doesn&rsquo;t recognize the <code>retl-source-table</code> kind. See <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/validations/" >Rudder CLI Validation Rules</a> for example table source specs.</td>
      </tr>
  </tbody>
</table>
<p>Reverse ETL sources created from <a href="https://www.rudderstack.com/docs/activate/audiences/" >Audiences</a> aren&rsquo;t supported.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="supported-destinations">Supported destinations</h2><p>A Reverse ETL connection can target these <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/" >destination types</a>. All of them support the JSON mapper flow. In Rudder CLI v0.27.0, you can connect only HTTP Webhook. The others need v0.28.0 or later.</p>
<table>
  <thead>
      <tr>
          <th>Destination</th>
          <th><code>type</code></th>
          <th>Object mapping</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/activecampaign/" >ActiveCampaign</a></td>
          <td><code>active_campaign</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/amazon-s3/" >Amazon S3</a></td>
          <td><code>s3</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/amplitude/" >Amplitude</a></td>
          <td><code>am</code></td>
          <td>Yes</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/attentive-tag/" >Attentive Tag</a></td>
          <td><code>attentive_tag</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/bigquery-stream/" >BigQuery Stream</a></td>
          <td><code>bqstream</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/braze/" >Braze</a></td>
          <td><code>braze</code></td>
          <td>Yes</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/facebook-conversions/" >Facebook Conversions</a></td>
          <td><code>facebook_conversions</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/facebook-pixel/" >Facebook Pixel</a></td>
          <td><code>facebook_pixel</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/google-analytics-4/" >Google Analytics 4</a></td>
          <td><code>ga4</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/google-cloud-storage/" >Google Cloud Storage</a></td>
          <td><code>gcs</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/http-webhook/" >HTTP Webhook</a></td>
          <td><code>http</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/hubspot/" >HubSpot</a></td>
          <td><code>hs</code></td>
          <td>Yes</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/iterable/" >Iterable</a></td>
          <td><code>iterable</code></td>
          <td>Yes</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/mixpanel/" >Mixpanel</a></td>
          <td><code>mp</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/posthog/" >PostHog</a></td>
          <td><code>posthog</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/tiktok-ads/" >TikTok Ads</a></td>
          <td><code>tiktok_ads</code></td>
          <td>No</td>
      </tr>
      <tr>
          <td><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/webhook/" >Webhook</a></td>
          <td><code>webhook</code></td>
          <td>No</td>
      </tr>
  </tbody>
</table>
<p>A destination type that doesn&rsquo;t accept Reverse ETL sources fails validation. The error lists the source types the destination does accept:</p>
<div class="rs-code">
  <div class="rs-code__head"><button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-text" data-lang="text">destination &#39;my-googleads-destination&#39; (type &#39;googleads&#39;) does not accept rETL sources: source type &#39;warehouse&#39; is not among supported source types: web</code></pre></div>
</div>
<p><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/customer-io/" >Customer.io</a> isn&rsquo;t supported either. It syncs Reverse ETL data through its own flow, which connection specs can&rsquo;t express.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="destination-requirements">Destination requirements</h3><p>The destination&rsquo;s <code>config</code> must carry a <code>connection_mode</code> entry for the <code>warehouse</code> source type, which is the source type every Reverse ETL source resolves to:</p>
<div class="rs-code">
  <div class="rs-code__head">yaml<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml">config:
  connection_mode:
    warehouse: cloud</code></pre></div>
</div>
<p>Without it, <code>validate</code> reports:</p>
<div class="rs-code">
  <div class="rs-code__head"><button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-text" data-lang="text">destination &#39;my-http-destination&#39; config has no &#39;connection_mode&#39; entry for source type &#39;warehouse&#39;</code></pre></div>
</div>
<p>Some destination types need additional config keys before a Reverse ETL source can connect:</p>
<table>
  <thead>
      <tr>
          <th>Destination</th>
          <th>Required config key</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Braze</td>
          <td><code>rest_api_key</code></td>
      </tr>
      <tr>
          <td>Facebook Pixel</td>
          <td><code>access_token</code></td>
      </tr>
  </tbody>
</table>
<p>If the destination already has a Reverse ETL connection that you set up in the dashboard, check its spec after you upgrade to v0.28.0. See <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/destination-types/#where-the-mode-is-declared" >Where the mode is declared</a>.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="cron-expressions">Cron expressions</h2><p>RudderStack stores a <code>cron_expression</code> as written, without checking it. Rudder CLI checks it locally so that a schedule that can never run, or that runs too often, fails at <code>validate</code> instead of after apply.</p>
<p>Rudder CLI evaluates every expression in UTC. Consecutive syncs must be at least five minutes apart.</p>
<table>
  <thead>
      <tr>
          <th>Construct</th>
          <th>Example</th>
          <th>Result</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Five fields: minute, hour, day of month, month, day of week</td>
          <td><code>0 6 * * *</code></td>
          <td>Supported</td>
      </tr>
      <tr>
          <td>Wildcards, lists, and ranges</td>
          <td><code>0,15 1-5 * * *</code></td>
          <td>Supported</td>
      </tr>
      <tr>
          <td>Steps on a wildcard or a range</td>
          <td><code>*/10</code>, <code>0-30/10</code></td>
          <td>Supported</td>
      </tr>
      <tr>
          <td>Steps from a value</td>
          <td><code>15/20</code></td>
          <td>Supported</td>
      </tr>
      <tr>
          <td>Month and day names, and name ranges</td>
          <td><code>JAN</code>, <code>mon-fri</code></td>
          <td>Supported</td>
      </tr>
      <tr>
          <td><code>?</code> in place of <code>*</code>, in any field</td>
          <td><code>0 0 ? * MON</code></td>
          <td>Supported — read as <code>*</code>, so a <code>?</code> in the minute or hour field widens the schedule and can trip the five-minute floor</td>
      </tr>
      <tr>
          <td>Lowercase descriptors</td>
          <td><code>@hourly</code>, <code>@daily</code>, <code>@midnight</code>, <code>@weekly</code>, <code>@monthly</code>, <code>@yearly</code>, <code>@annually</code></td>
          <td>Supported</td>
      </tr>
      <tr>
          <td>Descriptor in any other case</td>
          <td><code>@DAILY</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td>Day of week <code>7</code></td>
          <td><code>0 0 * * 7</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td>Wrapping range</td>
          <td><code>22-2 * * * *</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td>Wrong field count, value, or step</td>
          <td><code>70 * * * *</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td>Six or seven fields (seconds or year)</td>
          <td><code>0 0 0 * * *</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>L</code>, <code>W</code>, and <code>#</code> on the day fields</td>
          <td><code>0 0 L * *</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td>Duration shortcuts</td>
          <td><code>@every 1h</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>@reboot</code> and unknown descriptors</td>
          <td><code>@reboot</code>, <code>@fortnightly</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td>Date that never occurs</td>
          <td><code>0 0 30 2 *</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td>Syncs less than five minutes apart</td>
          <td><code>*/2 * * * *</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>CRON_TZ=</code> or <code>TZ=</code> prefix</td>
          <td><code>CRON_TZ=UTC 0 6 * * *</code></td>
          <td>Warning</td>
      </tr>
  </tbody>
</table>
<p>An error names the problem:</p>
<div class="rs-code">
  <div class="rs-code__head"><button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-text" data-lang="text">&#39;cron_expression&#39; is not valid: consecutive syncs have a 2-minute gap; the minimum supported interval is 5 minutes</code></pre></div>
</div>
<p>A timezone prefix is a warning because Rudder CLI can&rsquo;t check the schedule it produces. The apply goes through, so verify the resulting schedule yourself.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="validation-rules">Validation rules</h2><p><code>rudder-cli validate</code> runs these checks locally. <strong>Error</strong> blocks validate and apply. <strong>Warning</strong> does not.</p>
<table>
  <thead>
      <tr>
          <th>Check</th>
          <th>Severity</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>The spec has every required field, no unrecognized keys, and valid values</td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>source</code> and <code>destination</code> use <code>#retl-source-sql-model:</code> (or <code>#retl-source-table:</code>) and <code>#destination:</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>source</code> and <code>destination</code> exist in the project</td>
          <td>Error</td>
      </tr>
      <tr>
          <td>The same source–destination pair appears only once in the project</td>
          <td>Error</td>
      </tr>
      <tr>
          <td>The destination receives from only one Reverse ETL source in the project</td>
          <td>Error</td>
      </tr>
      <tr>
          <td>The destination doesn&rsquo;t also receive from an Event Stream source in the project</td>
          <td>Error</td>
      </tr>
      <tr>
          <td>The destination type accepts Reverse ETL sources and doesn&rsquo;t use a destination-specific flow</td>
          <td>Error</td>
      </tr>
      <tr>
          <td>The destination <code>config</code> includes a <code>connection_mode</code> entry for <code>warehouse</code>, and every field its definition requires for that source type</td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>object</code> is set only on a destination that supports object mapping</td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>identifiers</code>, <code>mappings</code>, <code>constants</code>, and <code>event</code> fit the mapping flow</td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>sync_behaviour</code> is supported by the source, the destination, and the mapping flow</td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>cursor_column</code> is set only when <code>sync_behaviour</code> is <code>upsert</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>sync_settings</code> is set only for a source that supports it</td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>every_minutes</code> is at least <code>5</code></td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>cron_expression</code> is valid and syncs no more often than every five minutes</td>
          <td>Error</td>
      </tr>
      <tr>
          <td><code>cron_expression</code> has no timezone prefix</td>
          <td>Warning</td>
      </tr>
      <tr>
          <td>An <code>enabled</code> connection has both endpoints enabled</td>
          <td>Warning</td>
      </tr>
  </tbody>
</table>
<p>Workspace plan gates and permissions aren&rsquo;t checked locally; apply surfaces the server error. Rudder CLI also doesn&rsquo;t check that <code>object</code> or a mapped field exists in the destination.</p>
<p>Topology rules see only connections in the project. Remote-only connections (created in the dashboard and never imported) are invisible at validate time.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="import-existing-connections">Import existing connections</h2><div class="rs-code">
  <div class="rs-code__head">bash<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">rudder-cli import workspace -l &lt;project-directory&gt;</code></pre></div>
</div>
<p><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/retl-connections/#enable-reverse-etl-connection-support" >Enable Reverse ETL connection support</a> before you run the import. Without it, <code>import workspace</code> doesn&rsquo;t import Reverse ETL connections.</p>
<p>Import writes all importable Reverse ETL connections into one file, <code>imported/retl/connections.yaml</code>. Each connection&rsquo;s <code>id</code> is generated from the names of its source and destination, for example <code>users-model-to-users-webhook</code>. Related SQL models land under <code>imported/retl/sql-models/</code>. See <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/import-resources/" >How to Import Workspace Resources</a>.</p>
<p>Import skips a connection that a spec can&rsquo;t express and leaves it unchanged in the workspace. Rudder CLI logs each skipped connection, with the reason, to <code>~/.rudder/cli.log</code>. The most common reasons are:</p>
<ul>
<li>Its source isn&rsquo;t a <a href="#supported-sources" >supported source</a>, or its destination isn&rsquo;t a <a href="#supported-destinations" >supported destination</a>.</li>
<li>Its source is a table source, and table source support isn&rsquo;t enabled.</li>
<li>Its source or destination is neither CLI-managed nor part of the same import.</li>
<li>It has no identifiers.</li>
<li>It carries settings that have no spec equivalent, such as destination-specific configuration.</li>
</ul>
<p>If the destination is already in your project, its spec needs the <code>connection_mode</code> entry for <code>warehouse</code> before the imported connection passes <code>validate</code>. See <a href="#destination-requirements" >Destination requirements</a>.</p>
<p>The first <code>apply</code> after an import can fail with <code>setting external ID for rETL connection during import: …</code>. Run <code>apply</code> again, and the retry adopts the connection.</p>
<p>You can&rsquo;t import a single Reverse ETL connection by ID. <code>import workspace</code> is the only import path.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="see-more">See more</h2><ul>
<li><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/retl-connections/" >Manage Reverse ETL Connections using Rudder CLI</a> for feature overview, and the <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/retl-connections-walkthrough/" >Reverse ETL connections walkthrough</a> for step-by-step setup</li>
<li><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/yaml-destinations/" >Destination YAML Reference</a> and <a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/yaml-sql-models/" >SQL Model Resources YAML Reference</a></li>
<li><a href="https://www.rudderstack.com/docs/dev-tools/rudder-cli/yaml-connections/" >Connection YAML Reference</a> for Event Stream connections</li>
</ul>

