<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>OpenFlow on Ghost in the data</title><link>https://ghostinthedata.info/tags/openflow/</link><description>Ghost in the data</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Ghost in the data</copyright><lastBuildDate>Sat, 04 Apr 2026 09:00:00 +1100</lastBuildDate><atom:link href="https://ghostinthedata.info/tags/openflow/index.xml" rel="self" type="application/rss+xml"/><item><title>Stop Building Salesforce Integrations From Scratch</title><link>https://ghostinthedata.info/posts/2026/2026-04-04-snowflake-openflow/</link><pubDate>Sat, 04 Apr 2026 09:00:00 +1100</pubDate><guid>https://ghostinthedata.info/posts/2026/2026-04-04-snowflake-openflow/</guid><author>Chris Hillman</author><description>A hands-on guide to Snowflake's OpenFlow Salesforce connector — why managed connectors beat custom code, how to set one up step by step, and the schema evolution feature that makes it all worth it.</description><content:encoded>&lt;p>Let me tell you about Marcus.&lt;/p>
&lt;/br>
&lt;p>Marcus was on a team I led a few years back. Sharp, motivated, the kind of engineer who actually read documentation before writing code. When the business asked us to get Salesforce data into our warehouse, Marcus volunteered. He&amp;rsquo;d done API work before. He figured a few weeks, tops.&lt;/p>
&lt;p>He scoped it carefully. Built a Python service that authenticated via OAuth, pulled Account, Contact, and Opportunity objects through the Bulk API, flattened the nested JSON into relational tables, handled pagination, managed rate limits. Wrote solid tests. Documented everything. The kind of work you&amp;rsquo;d point to in a code review and say &lt;em>this is how it&amp;rsquo;s done&lt;/em>.&lt;/p>
&lt;p>It worked beautifully. For about three months.&lt;/p>
&lt;/br>
&lt;p>Then our Salesforce admin added a custom field to the Opportunity object — &lt;code>Renewal_Likelihood__c&lt;/code> — and nobody told the data team. The pipeline didn&amp;rsquo;t fail. That&amp;rsquo;s the insidious part. It kept running, kept landing data. It just quietly dropped the new field on the floor.&lt;/p>
&lt;p>When Marcus tracked it down, he added the field. Then he realised there were &lt;em>eleven&lt;/em> other custom fields that had been added since go-live that the pipeline was silently ignoring. And the compound address fields — &lt;code>BillingAddress&lt;/code>, &lt;code>ShippingAddress&lt;/code> — had never worked with the Bulk API in the first place. He&amp;rsquo;d been extracting the component parts (&lt;code>BillingStreet&lt;/code>, &lt;code>BillingCity&lt;/code>) as a workaround, but the workaround had a bug that truncated postal codes for international addresses.&lt;/p>
&lt;p>Marcus spent three weeks patching all of it. Three weeks of a talented engineer doing work that a managed connector handles automatically.&lt;/p>
&lt;/br>
&lt;p>When I watched a good engineer spend the best part of a month on a problem that shouldn&amp;rsquo;t exist. Marcus wasn&amp;rsquo;t learning anything. He wasn&amp;rsquo;t growing. He was hand-coding schema detection logic for the fourth time because Salesforce ships three major releases a year and our sales ops team adds custom fields like they&amp;rsquo;re decorating a Christmas tree.&lt;/p>
&lt;p>I&amp;rsquo;m telling you this because &lt;strong>Snowflake OpenFlow&lt;/strong> is one of those things that sounds like &amp;ldquo;just another integration tool&amp;rdquo; until you&amp;rsquo;ve lived through the alternative. What I want to show you today is how to set it up with Salesforce — step by step, with enough detail that you could do it tomorrow — and more importantly, &lt;em>why&lt;/em> the schema evolution feature alone justifies the switch from custom code.&lt;/p>
&lt;p>If you&amp;rsquo;ve ever maintained a custom Salesforce integration, you already know why this matters.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-problem-nobody-warns-you-about">The Problem Nobody Warns You About&lt;/h3>
&lt;/br>
&lt;p>Here&amp;rsquo;s what the &amp;ldquo;just build it yourself&amp;rdquo; crowd doesn&amp;rsquo;t tell you: the Salesforce API isn&amp;rsquo;t one API. It&amp;rsquo;s an ecosystem of overlapping interfaces, each with its own quirks, limits, and failure modes.&lt;/p>
&lt;p>The REST API handles small, synchronous requests well — but try pulling a million Opportunity records through it and you&amp;rsquo;ll burn through your rate limits before lunch. The Bulk API 2.0 is designed for volume — it can handle up to 150 million records in a 24-hour window — but it doesn&amp;rsquo;t support compound fields like &lt;code>BillingAddress&lt;/code> or &lt;code>MailingAddress&lt;/code>. Those silently return nothing. Not an error. Nothing.&lt;/p>
&lt;p>Then there&amp;rsquo;s SOQL, Salesforce&amp;rsquo;s proprietary query language, which looks enough like SQL to trick you into thinking you understand it. Until you hit the 2,000-record offset limit, or try to resolve a polymorphic relationship where a &lt;code>WhoId&lt;/code> field on a Task could reference either a Contact or a Lead depending on the record. That&amp;rsquo;s not a data modelling problem — that&amp;rsquo;s a &amp;ldquo;your pipeline logic needs to branch based on the referenced object type&amp;rdquo; problem.&lt;/p>
&lt;p>And rate limits. Enterprise Edition gives you 100,000 API calls per 24 hours, plus 1,000 per user licence. Sounds generous until you remember that your BI tool, marketing automation platform, customer support system, and your data pipeline are all drinking from the same well.&lt;/p>
&lt;/br>
&lt;p>But schema evolution is the silent killer.&lt;/p>
&lt;p>Every Salesforce org is different because every business is different. Your Salesforce admin creates custom objects and fields to match how your company actually works. That&amp;rsquo;s the point — it&amp;rsquo;s a customisation platform. But every custom field added to Salesforce is a potential break point for any integration that doesn&amp;rsquo;t handle schema changes automatically.&lt;/p>
&lt;p>And those changes happen constantly. Research suggests schemas in enterprise SaaS tools change roughly every three days on average. In Salesforce specifically, between admin-driven customisation and Salesforce&amp;rsquo;s own three-times-a-year release cycle (Spring, Summer, Winter), the schema you built against last quarter may not be the schema you&amp;rsquo;re ingesting from today.&lt;/p>
&lt;p>This is the trap. Building the initial integration isn&amp;rsquo;t hard. Any competent engineer can get Salesforce data into a warehouse. &lt;strong>Keeping it working&lt;/strong> — across schema changes, API version deprecations, authentication token rotations, and rate limit adjustments — is where it eats your life.&lt;/p>
&lt;p>I&amp;rsquo;ve seen teams eliminate two full days of monthly engineering maintenance by migrating off custom connectors. Two days, every month, spent on work that a managed tool does automatically. That&amp;rsquo;s not engineering — it&amp;rsquo;s janitorial work wearing an engineering costume.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="what-is-snowflake-openflow">What Is Snowflake OpenFlow?&lt;/h3>
&lt;/br>
&lt;p>OpenFlow is Snowflake&amp;rsquo;s native, fully managed data integration service. It&amp;rsquo;s not a partnership or a marketplace app — it&amp;rsquo;s a first-party feature built into the platform. If you&amp;rsquo;re already running Snowflake, OpenFlow runs inside your existing environment.&lt;/p>
&lt;p>The backstory matters. In November 2024, Snowflake acquired a company called &lt;strong>Datavolo&lt;/strong> for roughly $110 million. Datavolo was founded by Joe Witt, who co-created Apache NiFi back when it was an NSA project. That NiFi heritage is the foundation of OpenFlow — curated, versioned connector definitions powered by NiFi&amp;rsquo;s flow engine, wrapped in a managed Snowflake experience.&lt;/p>
&lt;p>Architecturally, it splits into two pieces. The &lt;strong>control plane&lt;/strong> lives inside Snowflake and gives you pipeline management, scheduling, and monitoring through the Snowsight UI. The &lt;strong>data plane&lt;/strong> handles the actual work and can run in two modes: &lt;strong>Snowflake Deployments&lt;/strong> (running on Snowpark Container Services within Snowflake&amp;rsquo;s own infrastructure) or &lt;strong>BYOC&lt;/strong> (Bring Your Own Cloud, running as a Kubernetes cluster in your VPC). For most teams starting out, the SPCS option is simpler — it went generally available in November 2025 on AWS and Azure.&lt;/p>
&lt;p>Data flows into Snowflake primarily through Snowpipe Streaming for the initial load, with merge queries handling incremental updates. The result is structured tables in your Snowflake database — not staged JSON files, not semi-structured VARIANT columns. Actual, queryable tables with proper column types.&lt;/p>
&lt;/br>
&lt;p>The connector catalogue currently covers about 20 curated sources: databases (PostgreSQL, MySQL, SQL Server, Oracle), SaaS platforms (Salesforce, Workday, Jira, LinkedIn Ads, Meta Ads, Google Ads), cloud storage (Google Drive, SharePoint, Box), and streaming sources (Kafka, Kinesis). It&amp;rsquo;s not Fivetran&amp;rsquo;s 500+ connector library, and nobody&amp;rsquo;s pretending it is. But the sources it does cover are the ones that account for the vast majority of enterprise data movement.&lt;/p>
&lt;p>One important caveat up front: the &lt;strong>Salesforce Bulk API connector is still in preview&lt;/strong> as of March 2026. It works. Preview in Snowflake terms means the feature is functional but not yet covered by production support SLAs. Keep that in your mental model as we walk through the setup.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="setting-up-the-salesforce-connector-a-walkthrough">Setting Up the Salesforce Connector: A Walkthrough&lt;/h3>
&lt;/br>
&lt;p>This section is the step-by-step. I&amp;rsquo;m going to walk through the full setup — Salesforce configuration, Snowflake preparation, and connector deployment — with enough detail that you could follow along on your own org.&lt;/p>
&lt;/br>
&lt;h4 id="phase-1-salesforce-configuration">Phase 1: Salesforce Configuration&lt;/h4>
&lt;p>The connector uses JWT Bearer Flow for authentication, which means you need an RSA key pair and a Connected App configured in Salesforce.&lt;/p>
&lt;p>&lt;strong>Generate your RSA key pair.&lt;/strong> Open a terminal and run:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Generate 2048-bit RSA private key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>openssl genrsa -out salesforce_private_key.pem &lt;span style="color:#ae81ff">2048&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Generate the corresponding public certificate (valid 1 year)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>openssl req -new -x509 -key salesforce_private_key.pem &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -out salesforce_certificate.pem -days &lt;span style="color:#ae81ff">365&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Keep that private key safe. You&amp;rsquo;ll need it when configuring the connector in Snowflake.&lt;/p>
&lt;p>&lt;strong>Create the External Client App in Salesforce.&lt;/strong> Navigate to Setup → Apps → App Manager → New External Client App. Enable OAuth and add two scopes: &lt;code>api&lt;/code> and &lt;code>refresh_token&lt;/code> (sometimes labelled &lt;code>offline_access&lt;/code>). Enable the JWT Bearer Flow toggle and upload your &lt;code>salesforce_certificate.pem&lt;/code> public certificate. Save the app and record the &lt;strong>Consumer Key&lt;/strong> and &lt;strong>Consumer Secret&lt;/strong> that Salesforce generates.&lt;/p>
&lt;p>&lt;strong>Configure access.&lt;/strong> On the app&amp;rsquo;s Manage settings, set the Permitted Users policy to &amp;ldquo;Admin approved users are pre-authorized.&amp;rdquo; Then assign the appropriate user profiles or permission sets. The user account the connector will authenticate as needs read access to every object you intend to sync.&lt;/p>
&lt;/br>
&lt;h4 id="phase-2-snowflake-preparation">Phase 2: Snowflake Preparation&lt;/h4>
&lt;p>Before deploying the connector, you need a database, schema, role, and warehouse ready for it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a dedicated database for Salesforce data
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">DATABASE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> SALESFORCE_RAW;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">SCHEMA&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> SALESFORCE_RAW.OPENFLOW;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a connector-specific role
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Grant the role permissions to write to the destination
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">USAGE&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#66d9ef">DATABASE&lt;/span> SALESFORCE_RAW &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">USAGE&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#66d9ef">SCHEMA&lt;/span> SALESFORCE_RAW.OPENFLOW &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> &lt;span style="color:#66d9ef">SCHEMA&lt;/span> SALESFORCE_RAW.OPENFLOW &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a warehouse for merge operations
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> WAREHOUSE &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> OPENFLOW_WH
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WAREHOUSE_SIZE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;SMALL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUTO_SUSPEND &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">60&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUTO_RESUME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">USAGE&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> WAREHOUSE OPENFLOW_WH &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> OPERATE &lt;span style="color:#66d9ef">ON&lt;/span> WAREHOUSE OPENFLOW_WH &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#66d9ef">ROLE&lt;/span> OPENFLOW_SF_CONNECTOR;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For SPCS deployments, you also need a &lt;strong>network rule&lt;/strong> allowing the connector to reach your Salesforce instance. OpenFlow runs inside Snowflake&amp;rsquo;s container environment, and by default it can&amp;rsquo;t reach external endpoints without explicit permission:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Allow egress to your Salesforce instance
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> NETWORK &lt;span style="color:#66d9ef">RULE&lt;/span> &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> OPENFLOW_SF_EGRESS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">TYPE&lt;/span> &lt;span style="color:#f92672">=&lt;/span> HOST_PORT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">MODE&lt;/span> &lt;span style="color:#f92672">=&lt;/span> EGRESS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> VALUE_LIST &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#e6db74">&amp;#39;yourinstance.salesforce.com:443&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Wrap it in an external access integration
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">EXTERNAL&lt;/span> &lt;span style="color:#66d9ef">ACCESS&lt;/span> INTEGRATION &lt;span style="color:#66d9ef">IF&lt;/span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> OPENFLOW_SF_ACCESS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ALLOWED_NETWORK_RULES &lt;span style="color:#f92672">=&lt;/span> (OPENFLOW_SF_EGRESS)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ENABLED &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">TRUE&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Replace &lt;code>yourinstance.salesforce.com&lt;/code> with your actual Salesforce instance hostname. If you&amp;rsquo;re not sure what that is, log in to Salesforce and look at the URL bar — it&amp;rsquo;s the domain before &lt;code>.salesforce.com&lt;/code>.&lt;/p>
&lt;/br>
&lt;h4 id="phase-3-deploying-the-connector">Phase 3: Deploying the Connector&lt;/h4>
&lt;p>Now the fun part. Navigate to &lt;strong>Snowsight → Data → Openflow&lt;/strong> in the left sidebar. Find the &amp;ldquo;Openflow connector for Salesforce Bulk API&amp;rdquo; tile and add it to your runtime.&lt;/p>
&lt;p>This opens the NiFi canvas — a visual flow editor where the connector&amp;rsquo;s pre-built process groups appear. Right-click the Salesforce process group and select &lt;strong>Configure Parameters&lt;/strong>. Here&amp;rsquo;s where you&amp;rsquo;ll enter:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Salesforce Instance URL&lt;/strong> — e.g., &lt;code>https://yourinstance.salesforce.com&lt;/code>&lt;/li>
&lt;li>&lt;strong>Consumer Key&lt;/strong> — from the Connected App you created&lt;/li>
&lt;li>&lt;strong>Consumer Secret&lt;/strong> — same source&lt;/li>
&lt;li>&lt;strong>Private Key&lt;/strong> — the contents of &lt;code>salesforce_private_key.pem&lt;/code> (the full PEM text, headers included)&lt;/li>
&lt;li>&lt;strong>Username&lt;/strong> — the Salesforce user the connector authenticates as&lt;/li>
&lt;li>&lt;strong>Filter&lt;/strong> — the objects to sync, by API name&lt;/li>
&lt;/ul>
&lt;p>The &lt;strong>Filter&lt;/strong> parameter is where you define what to pull. List standard objects by name (&lt;code>Account&lt;/code>, &lt;code>Contact&lt;/code>, &lt;code>Opportunity&lt;/code>, &lt;code>Lead&lt;/code>, &lt;code>Task&lt;/code>, &lt;code>Event&lt;/code>) and custom objects using their API name with the &lt;code>__c&lt;/code> suffix:&lt;/p>



&lt;div class="goat svg-container ">
 
 &lt;svg
 xmlns="http://www.w3.org/2000/svg"
 font-family="Menlo,Lucida Console,monospace"
 
 viewBox="0 0 720 25"
 >
 &lt;g transform='translate(8,16)'>
&lt;text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>A&lt;/text>
&lt;text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>O&lt;/text>
&lt;text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>y&lt;/text>
&lt;text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>L&lt;/text>
&lt;text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>d&lt;/text>
&lt;text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='296' y='4' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='344' y='4' fill='currentColor' style='font-size:1em'>E&lt;/text>
&lt;text text-anchor='middle' x='352' y='4' fill='currentColor' style='font-size:1em'>v&lt;/text>
&lt;text text-anchor='middle' x='360' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='368' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='376' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='384' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='400' y='4' fill='currentColor' style='font-size:1em'>C&lt;/text>
&lt;text text-anchor='middle' x='408' y='4' fill='currentColor' style='font-size:1em'>u&lt;/text>
&lt;text text-anchor='middle' x='416' y='4' fill='currentColor' style='font-size:1em'>s&lt;/text>
&lt;text text-anchor='middle' x='424' y='4' fill='currentColor' style='font-size:1em'>t&lt;/text>
&lt;text text-anchor='middle' x='432' y='4' fill='currentColor' style='font-size:1em'>o&lt;/text>
&lt;text text-anchor='middle' x='440' y='4' fill='currentColor' style='font-size:1em'>m&lt;/text>
&lt;text text-anchor='middle' x='448' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='456' y='4' fill='currentColor' style='font-size:1em'>P&lt;/text>
&lt;text text-anchor='middle' x='464' y='4' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='472' y='4' fill='currentColor' style='font-size:1em'>p&lt;/text>
&lt;text text-anchor='middle' x='480' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='488' y='4' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='496' y='4' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='504' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='512' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='520' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='528' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='536' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='544' y='4' fill='currentColor' style='font-size:1em'>,&lt;/text>
&lt;text text-anchor='middle' x='560' y='4' fill='currentColor' style='font-size:1em'>R&lt;/text>
&lt;text text-anchor='middle' x='568' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='576' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='584' y='4' fill='currentColor' style='font-size:1em'>e&lt;/text>
&lt;text text-anchor='middle' x='592' y='4' fill='currentColor' style='font-size:1em'>w&lt;/text>
&lt;text text-anchor='middle' x='600' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='608' y='4' fill='currentColor' style='font-size:1em'>l&lt;/text>
&lt;text text-anchor='middle' x='616' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='624' y='4' fill='currentColor' style='font-size:1em'>T&lt;/text>
&lt;text text-anchor='middle' x='632' y='4' fill='currentColor' style='font-size:1em'>r&lt;/text>
&lt;text text-anchor='middle' x='640' y='4' fill='currentColor' style='font-size:1em'>a&lt;/text>
&lt;text text-anchor='middle' x='648' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;text text-anchor='middle' x='656' y='4' fill='currentColor' style='font-size:1em'>k&lt;/text>
&lt;text text-anchor='middle' x='664' y='4' fill='currentColor' style='font-size:1em'>i&lt;/text>
&lt;text text-anchor='middle' x='672' y='4' fill='currentColor' style='font-size:1em'>n&lt;/text>
&lt;text text-anchor='middle' x='680' y='4' fill='currentColor' style='font-size:1em'>g&lt;/text>
&lt;text text-anchor='middle' x='688' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='696' y='4' fill='currentColor' style='font-size:1em'>_&lt;/text>
&lt;text text-anchor='middle' x='704' y='4' fill='currentColor' style='font-size:1em'>c&lt;/text>
&lt;/g>

 &lt;/svg>
 
&lt;/div>
&lt;p>There&amp;rsquo;s also a &lt;strong>Special Objects Filter&lt;/strong> for objects that require different handling (like &lt;code>User&lt;/code> or &lt;code>RecordType&lt;/code>).&lt;/p>
&lt;p>Once the parameters are configured, enable all controller services in the process group, then start the connector. The initial sync will pull a full snapshot of every object in your filter list. Subsequent runs perform incremental syncs using Salesforce&amp;rsquo;s &lt;code>SystemModstamp&lt;/code> field to identify changed records.&lt;/p>
&lt;/br>
&lt;h4 id="what-you-get">What You Get&lt;/h4>
&lt;p>Once the connector runs, you&amp;rsquo;ll find one table per Salesforce object in &lt;code>SALESFORCE_RAW.OPENFLOW&lt;/code>. The tables are &lt;strong>flattened and structured&lt;/strong> — every Salesforce field becomes a column with an appropriate Snowflake data type. No VARIANT columns to parse. No nested JSON to unpack.&lt;/p>
&lt;p>An &lt;code>Account&lt;/code> table will have columns like &lt;code>ID&lt;/code>, &lt;code>NAME&lt;/code>, &lt;code>INDUSTRY&lt;/code>, &lt;code>ANNUALREVENUE&lt;/code>, &lt;code>BILLINGSTREET&lt;/code>, &lt;code>BILLINGCITY&lt;/code>, &lt;code>BILLINGSTATE&lt;/code>, &lt;code>BILLINGPOSTALCODE&lt;/code>, &lt;code>CREATEDDATE&lt;/code>, &lt;code>LASTMODIFIEDDATE&lt;/code>, and so on. Custom fields like &lt;code>Renewal_Likelihood__c&lt;/code> appear as &lt;code>RENEWAL_LIKELIHOOD__C&lt;/code>.&lt;/p>
&lt;p>Two metadata columns are added automatically: &lt;code>ISDELETED&lt;/code> (tracking Salesforce soft deletes) and the system timestamp fields. This is important — &lt;strong>hard deletes in Salesforce are not reflected&lt;/strong>. If a record is permanently deleted in Salesforce, it won&amp;rsquo;t disappear from your Snowflake table. You&amp;rsquo;ll need to handle that in your downstream dbt models if it matters for your use case.&lt;/p>
&lt;/br>
&lt;h4 id="a-note-on-multiple-sync-frequencies">A Note on Multiple Sync Frequencies&lt;/h4>
&lt;p>Not every Salesforce object changes at the same pace. Your &lt;code>Opportunity&lt;/code> table might need hourly syncs while &lt;code>Account&lt;/code> is fine with daily. OpenFlow handles this by letting you deploy &lt;strong>multiple connector instances in the same runtime&lt;/strong> — each with its own filter list and schedule — at no additional compute cost beyond the shared runtime.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="schema-evolution-where-openflow-earns-its-keep">Schema Evolution: Where OpenFlow Earns Its Keep&lt;/h3>
&lt;/br>
&lt;p>This is the feature that matters most, and the one that would have saved Marcus three weeks.&lt;/p>
&lt;p>When someone adds a new custom field to a Salesforce object — say the sales ops team creates &lt;code>Deal_Confidence_Score__c&lt;/code> on the Opportunity object — the OpenFlow connector &lt;strong>automatically detects the new field and adds a corresponding column&lt;/strong> to the Snowflake destination table on the next sync. No configuration change. No redeployment. No Teams message at 7am asking the data team to &amp;ldquo;add that new field we made yesterday.&amp;rdquo;&lt;/p>
&lt;p>The column appears, data starts flowing into it, and your dbt models can reference it whenever you&amp;rsquo;re ready.&lt;/p>
&lt;/br>
&lt;p>For field renames, OpenFlow takes a conservative approach: the old column stays in place (with stale data from the last sync before the rename), and a new column is created under the new field name. This means you&amp;rsquo;ll have both &lt;code>Old_Field_Name__c&lt;/code> and &lt;code>New_Field_Name__c&lt;/code> in your table for a period. That&amp;rsquo;s actually useful for audit purposes — you can see exactly when the rename happened by comparing timestamps — but it does mean your downstream queries need updating.&lt;/p>
&lt;p>Here&amp;rsquo;s the honest gap: &lt;strong>new custom objects are not auto-discovered&lt;/strong>. If someone creates an entirely new custom object in Salesforce, you have to manually add it to the connector&amp;rsquo;s Filter parameter. Schema evolution handles field-level changes automatically, but object-level discovery is still a manual step.&lt;/p>
&lt;p>And field type changes — say someone changes a text field to a picklist, or a number to a formula — aren&amp;rsquo;t comprehensively documented in the current OpenFlow materials. In practice, this is a rare enough occurrence that it hasn&amp;rsquo;t been a problem in my experience, but it&amp;rsquo;s worth knowing the edge case exists.&lt;/p>
&lt;/br>
&lt;p>Compare this to what Marcus had to maintain. His custom integration used a hardcoded list of fields per object. Every time a field was added, renamed, or retyped, someone had to update the Python code, test it, deploy it, and verify the data. Usually that &amp;ldquo;someone&amp;rdquo; was Marcus, usually at an inconvenient time, and usually because nobody told him the change was coming.&lt;/p>
&lt;p>The value of automatic schema evolution isn&amp;rsquo;t technical elegance. It&amp;rsquo;s that &lt;strong>your engineers stop spending time on schema babysitting and start spending it on work that actually matters&lt;/strong> — building models, improving data quality, answering the questions the business is actually asking.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="a-word-about-fivetran">A Word About Fivetran&lt;/h3>
&lt;/br>
&lt;p>I&amp;rsquo;d be doing you a disservice if I wrote about managed Salesforce connectors without mentioning Fivetran. For most of the last decade, Fivetran has been the gold standard here, and their Salesforce connector is genuinely excellent.&lt;/p>
&lt;p>Fivetran&amp;rsquo;s schema evolution handling is more mature and more configurable than OpenFlow&amp;rsquo;s current offering. They offer three schema change policies: &lt;code>ALLOW_ALL&lt;/code> (sync everything automatically, including new tables), &lt;code>ALLOW_COLUMNS&lt;/code> (auto-add new columns but not new tables), and &lt;code>BLOCK_ALL&lt;/code> (manual opt-in for everything). They also generate a &lt;code>LOG&lt;/code> table that records every schema change event — &lt;code>create_table&lt;/code>, &lt;code>alter_table&lt;/code>, &lt;code>drop_table&lt;/code> — giving you an audit trail that&amp;rsquo;s useful for debugging and compliance. Field type widening (like &lt;code>INT&lt;/code> to &lt;code>LONG&lt;/code>) happens automatically; narrowing changes are blocked to prevent data loss.&lt;/p>
&lt;p>Their pricing model is fundamentally different from OpenFlow. Fivetran charges per Monthly Active Row (MAR) — the count of distinct rows synced per connection per month. A unique primary key counts once regardless of how many times it&amp;rsquo;s updated. Approximate pricing clusters around $500 per million MAR on the Standard plan, with a $12,000 annual minimum commitment. Since early 2025, MAR is calculated per connection rather than account-wide, which eliminated the bulk discount that previously benefited multi-connector setups.&lt;/p>
&lt;/br>
&lt;p>OpenFlow, by contrast, charges for compute — SPCS credits for the container runtime, Snowpipe Streaming costs for ingestion, and warehouse costs for merge operations. There&amp;rsquo;s no per-row fee and no separate licence. It&amp;rsquo;s included with Snowflake. The trade-off is that your management compute pool runs continuously while a deployment exists, which means you&amp;rsquo;re paying a base cost even when no data is flowing. One estimate I&amp;rsquo;ve seen puts idle costs at roughly $10 per day, though this varies with configuration.&lt;/p>
&lt;p>For a single large Salesforce org, OpenFlow&amp;rsquo;s compute-based model may end up cheaper than Fivetran&amp;rsquo;s per-row pricing — especially if your Salesforce data doesn&amp;rsquo;t churn heavily. For environments where you need 30+ connectors across different source types, Fivetran&amp;rsquo;s breadth and maturity is hard to argue with. They have 500+ connectors. OpenFlow has about 20.&lt;/p>
&lt;p>The honest framing: &lt;strong>OpenFlow is the better choice if you&amp;rsquo;re already deeply invested in Snowflake, value platform consolidation, and your primary sources are in the current connector catalogue.&lt;/strong> Fivetran is the safer choice if you need broad connector coverage, battle-tested reliability, and you&amp;rsquo;d rather pay a premium for someone else to handle the ops.&lt;/p>
&lt;p>They&amp;rsquo;re not mutually exclusive, either. I&amp;rsquo;ve seen teams run Fivetran for the long tail of small connectors while using OpenFlow for their highest-volume sources where compute-based pricing wins.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-zero-copy-alternative">The Zero-Copy Alternative&lt;/h3>
&lt;/br>
&lt;p>There&amp;rsquo;s a third path I haven&amp;rsquo;t mentioned yet, and it&amp;rsquo;s worth understanding even if it&amp;rsquo;s not right for every team: &lt;strong>zero-copy data federation&lt;/strong>.&lt;/p>
&lt;p>Salesforce Data Cloud offers a zero-copy integration with Snowflake that flips the entire model on its head. Instead of extracting data from Salesforce and loading it into your warehouse, zero-copy gives you direct read access to Salesforce data &lt;em>without moving it at all&lt;/em>. Salesforce&amp;rsquo;s query pushdown engine handles the work — when you query the federated data, Salesforce pushes the query to the source, filters and aggregates there, and returns only the results you need. No pipelines to build. No schemas to manage. No sync schedules to configure.&lt;/p>
&lt;p>It works bidirectionally, too. You can share data from Snowflake back into Salesforce Data Cloud, letting sales reps see warehouse-enriched signals directly on Account and Opportunity pages without anyone exporting a CSV or building a reverse ETL pipeline. The integration runs on Apache Iceberg under the hood, which at least means the underlying format is open.&lt;/p>
&lt;/br>
&lt;p>For handling new custom objects — the gap I flagged with OpenFlow — zero-copy sidesteps the problem entirely. There&amp;rsquo;s nothing to discover because there&amp;rsquo;s nothing to sync. The data stays where it is, and you access it in place. As your Salesforce org evolves, the federated view evolves with it.&lt;/p>
&lt;p>That&amp;rsquo;s genuinely appealing. For proof-of-concept work, rapid prototyping, or use cases where you need real-time access to Salesforce data without building infrastructure, zero-copy is fast and effective.&lt;/p>
&lt;/br>
&lt;p>But here&amp;rsquo;s where I get cautious.&lt;/p>
&lt;p>&lt;strong>It comes at a premium.&lt;/strong> Salesforce Data Cloud runs on a consumption credit model — credits are purchased in bundles (roughly $500 per 100,000 credits at list price), and every action burns them. Zero-copy federation queries consume about 70 credits per million records, which sounds reasonable until you&amp;rsquo;re running it at scale across multiple business units. The pricing model has simplified since late 2025 — Salesforce consolidated multiple credit types into a single fungible credit and made ingestion from core Salesforce products free — but it&amp;rsquo;s still a consumption model with real costs that compound quickly if you&amp;rsquo;re not watching.&lt;/p>
&lt;p>More importantly, &lt;strong>zero-copy creates a deep platform dependency&lt;/strong>. Your data access is mediated entirely through Salesforce and Snowflake&amp;rsquo;s partnership. If Salesforce changes their terms, adjusts their pricing (which they&amp;rsquo;ve done repeatedly), or if you decide to move off Snowflake to Databricks or BigQuery, that zero-copy integration doesn&amp;rsquo;t come with you. You&amp;rsquo;d need to build actual data movement infrastructure — the thing you avoided — under time pressure and with no existing pipeline to fall back on.&lt;/p>
&lt;/br>
&lt;p>I value keeping infrastructure portable. Not because I&amp;rsquo;m paranoid about vendor lock-in — I&amp;rsquo;m pragmatic about it. I&amp;rsquo;ve been in this industry long enough to see what happens when a new CEO arrives and wants to renegotiate every vendor contract, or when a cloud provider changes their pricing structure overnight, or when the company you depend on gets acquired and the roadmap shifts. Having your data physically in your own warehouse, in formats you control, with pipelines you can redirect to a different destination — that&amp;rsquo;s insurance. Not glamorous insurance, but the kind you&amp;rsquo;re grateful for when you need it.&lt;/p>
&lt;p>The balance is real, though. You always have to weigh portability against the time you spend building and maintaining pipelines. If your team is drowning in pipeline maintenance, zero-copy might buy you breathing room while you stand up proper infrastructure. Just go in with your eyes open about what you&amp;rsquo;re trading for that convenience.&lt;/p>
&lt;/br>
&lt;p>There&amp;rsquo;s a more fundamental concern, though, and it&amp;rsquo;s the one that won&amp;rsquo;t appear in any vendor comparison chart.&lt;/p>
&lt;p>&lt;strong>Zero-copy makes it dangerously easy to skip the data warehouse entirely.&lt;/strong> When you give analysts and report builders direct read access to raw Salesforce data, the temptation to build dashboards straight from it is enormous. And for a quick proof of concept or a one-off analysis, fine. But the moment those PoC dashboards become &amp;ldquo;the dashboard the VP checks every Monday,&amp;rdquo; you&amp;rsquo;ve got a problem.&lt;/p>
&lt;p>Raw source data doesn&amp;rsquo;t tell you which &lt;code>CustomerNo&lt;/code> field to use — the one from the legacy migration, the one from the 2023 CRM consolidation, or the one from the current integration. It doesn&amp;rsquo;t encode the business rule that says &amp;ldquo;Closed Won&amp;rdquo; opportunities in APAC exclude training deals under $5,000 because those are tracked separately. It doesn&amp;rsquo;t flag that the &lt;code>Last_Activity_Date&lt;/code> field is unreliable for accounts owned by the partner team because they log activities in a different system.&lt;/p>
&lt;/br>
&lt;p>That knowledge lives in your data warehouse layer. It lives in the dbt models that your team built over months of conversations with stakeholders, through change management cycles, through debugging sessions where someone finally said &amp;ldquo;oh, we stopped using that field in Q3 because&amp;hellip;&amp;rdquo; That&amp;rsquo;s not transformation logic — it&amp;rsquo;s &lt;em>institutional memory encoded as code&lt;/em>. It connects raw data across domains and gives the business a full landscape picture instead of a narrow, single-source view. It catches the behavioural anomalies unique to your organisation — the sales rep who bulk-updates 500 records every Friday afternoon, the integration that occasionally double-fires during DST transitions, the custom object that gets repurposed every time the business restructures.&lt;/p>
&lt;p>So by all means, evaluate zero-copy for the use cases where it shines. Real-time signals on Account pages for sales reps? Great fit. Federated access for a specific analytics team that knows the source data intimately? Reasonable. But don&amp;rsquo;t let it replace the warehouse layer. That layer is where the hard-won understanding of your data lives, and it&amp;rsquo;s harder to recreate than any pipeline.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-honest-trade-offs">The Honest Trade-Offs&lt;/h3>
&lt;/br>
&lt;p>I&amp;rsquo;ve been positive about OpenFlow because I think it solves a real problem well. But I&amp;rsquo;d be violating my own writing principles if I didn&amp;rsquo;t lay out the limitations clearly.&lt;/p>
&lt;p>&lt;strong>The Salesforce connector is still in preview.&lt;/strong> It works, but it&amp;rsquo;s not covered by production support SLAs. If you&amp;rsquo;re running a mission-critical pipeline that your CFO stares at every Monday morning, that matters.&lt;/p>
&lt;p>&lt;strong>Custom Salesforce domains aren&amp;rsquo;t supported.&lt;/strong> If your org uses a vanity URL (like &lt;code>mycompany.my.salesforce.com&lt;/code> with a custom domain), check the documentation carefully before committing.&lt;/p>
&lt;p>&lt;strong>Hard deletes aren&amp;rsquo;t tracked.&lt;/strong> The &lt;code>ISDELETED&lt;/code> column catches soft deletes, but records that are permanently purged from Salesforce will persist in your Snowflake tables indefinitely. You&amp;rsquo;ll need a reconciliation process if that matters for your use case.&lt;/p>
&lt;p>&lt;strong>Certain field types are silently dropped.&lt;/strong> &lt;code>location&lt;/code>, &lt;code>address&lt;/code> (compound), and &lt;code>base64&lt;/code> fields are not synced. The connector doesn&amp;rsquo;t error on these — it just ignores them. This is the kind of thing that bites you three months in when someone asks why geographic data isn&amp;rsquo;t in the warehouse.&lt;/p>
&lt;p>&lt;strong>Formula fields require full refresh.&lt;/strong> Because formula field values are computed server-side and don&amp;rsquo;t update &lt;code>SystemModstamp&lt;/code>, they can&amp;rsquo;t be synced incrementally. You need a separate connector instance running full refreshes to capture formula field changes. That&amp;rsquo;s not a bug — it&amp;rsquo;s how Salesforce works — but it&amp;rsquo;s a gotcha if you&amp;rsquo;re not expecting it.&lt;/p>
&lt;p>&lt;strong>No relationship traversal.&lt;/strong> You can&amp;rsquo;t configure the connector to follow lookups and pull related objects automatically. Each object is pulled independently. Joining them is your job in the transformation layer, which is where it belongs anyway if you&amp;rsquo;re running dbt.&lt;/p>
&lt;/br>
&lt;p>None of these are dealbreakers. But every one of them is the kind of thing that bites an engineer at 4pm on a Friday if they weren&amp;rsquo;t expecting it. Now you&amp;rsquo;re expecting it.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="getting-started">Getting Started&lt;/h3>
&lt;/br>
&lt;p>If you want to try this on your own Salesforce org, here&amp;rsquo;s my recommended order of operations:&lt;/p>
&lt;p>Start with a &lt;strong>non-production Salesforce sandbox&lt;/strong> and a Snowflake trial account if you don&amp;rsquo;t have a dev environment handy. Pick two or three standard objects — &lt;code>Account&lt;/code>, &lt;code>Contact&lt;/code>, &lt;code>Opportunity&lt;/code> — and one custom object if your org has them. Run through the three-phase setup I described above. Get data flowing, verify the table structures, and then add a custom field to one of the objects in Salesforce. Wait for the next sync. Watch the column appear automatically in Snowflake.&lt;/p>
&lt;p>That&amp;rsquo;s the moment it clicks. That&amp;rsquo;s the moment you stop thinking about managed connectors as a luxury and start thinking about custom API code as technical debt you&amp;rsquo;re choosing to carry.&lt;/p>
&lt;p>Once you&amp;rsquo;ve validated the core flow, layer on your dbt models. OpenFlow lands raw data — it doesn&amp;rsquo;t transform it. Your staging models handle naming conventions, type casting, soft delete filtering, and the join logic that stitches related objects together. This is the same pattern you&amp;rsquo;d use with Fivetran or any other EL tool: land it raw, transform it in the warehouse.&lt;/p>
&lt;/br>
&lt;p>For teams already running Fivetran for Salesforce, I&amp;rsquo;m not suggesting you rip it out tomorrow. If it&amp;rsquo;s working and the cost is acceptable, that&amp;rsquo;s a solved problem. But the next time you&amp;rsquo;re adding a new high-volume source that&amp;rsquo;s in OpenFlow&amp;rsquo;s catalogue — especially if you&amp;rsquo;re already paying for Snowflake — it&amp;rsquo;s worth running the numbers. You might find that native integration with compute-based pricing is the better deal.&lt;/p>
&lt;hr>
&lt;/br>
&lt;/br>
&lt;h3 id="the-real-why">The Real Why&lt;/h3>
&lt;/br>
&lt;p>I started this article with Marcus&amp;rsquo;s story because it illustrates something I feel strongly about: &lt;strong>your engineers&amp;rsquo; time is the most expensive resource in your data organisation, and spending it on solved problems is a leadership failure&lt;/strong>.&lt;/p>
&lt;p>Building a custom Salesforce integration isn&amp;rsquo;t impressive. It was impressive in 2016, when the tooling didn&amp;rsquo;t exist. Today, it&amp;rsquo;s a choice to take on maintenance burden that a managed service handles automatically. It&amp;rsquo;s choosing to babysit schema changes instead of building the models and analyses that actually move the business forward.&lt;/p>
&lt;p>Marcus is a senior engineer now. He builds dimensional models and designs data products that directly influence how the sales team allocates resources. He doesn&amp;rsquo;t write API integration code anymore. Not because he can&amp;rsquo;t — because his time is worth more than that.&lt;/p>
&lt;/br>
&lt;p>But here&amp;rsquo;s what I keep coming back to. The tools keep getting better — OpenFlow, Fivetran, zero-copy, and yes, AI-assisted pipeline generation. Every year there&amp;rsquo;s a new thing that promises to automate another piece of the data engineering workflow. And many of them genuinely do.&lt;/p>
&lt;p>What they don&amp;rsquo;t automate is the part that actually matters.&lt;/p>
&lt;p>No tool — not OpenFlow, not Fivetran, not an AI agent — is going to sit in a room with your head of sales and ask &amp;ldquo;what keeps you up at night?&amp;rdquo; No connector is going to notice that the way your company defines &amp;ldquo;active customer&amp;rdquo; has quietly drifted from what the CRM tracks. No pipeline is going to push back and say &amp;ldquo;we could build that dashboard, but the metric you&amp;rsquo;re asking for doesn&amp;rsquo;t answer the question you actually have.&amp;rdquo;&lt;/p>
&lt;/br>
&lt;p>That&amp;rsquo;s the connective tissue between raw data and business value. It&amp;rsquo;s a person who understands the domain, who&amp;rsquo;s been present through the change management cycles, who&amp;rsquo;s built relationships with the stakeholders and knows which &lt;code>CustomerNo&lt;/code> field to use because they were in the room when the decision was made three years ago. It&amp;rsquo;s someone who asks clarifying questions instead of presuming they already know the answer.&lt;/p>
&lt;p>AI will keep getting better at the mechanical parts of data engineering. Schema detection, pipeline code generation, anomaly detection — those are well-defined problems that automation is suited for. But the assumption that any tool can skip the human step — the clarification, the context, the judgment about what&amp;rsquo;s actually worth building — that&amp;rsquo;s where I&amp;rsquo;ve seen projects go sideways. Not because the technology failed, but because nobody asked the right questions before building.&lt;/p>
&lt;/br>
&lt;p>OpenFlow isn&amp;rsquo;t perfect. The connector catalogue is young, the Salesforce connector is in preview, and Fivetran still wins on breadth and battle-hardened maturity. But OpenFlow represents something important: the data platform taking responsibility for data movement, not just data storage and compute. It&amp;rsquo;s Snowflake saying &amp;ldquo;we&amp;rsquo;ll handle getting the data in — you focus on making it useful.&amp;rdquo;&lt;/p>
&lt;p>That last part — &lt;em>making it useful&lt;/em> — is still your job. And it&amp;rsquo;s the part that no tool can do for you. The best thing a managed connector gives you isn&amp;rsquo;t fewer lines of code. It&amp;rsquo;s time back. Time to spend on the work that actually requires a human: understanding the business, building the right models, and making sure the data tells an accurate story.&lt;/p>
&lt;p>The next time someone on your team says &amp;ldquo;I&amp;rsquo;ll just build a quick Salesforce integration&amp;rdquo; — send them this article. And the next time someone says &amp;ldquo;AI can just handle the data pipeline&amp;rdquo; — ask them who&amp;rsquo;s going to sit with the stakeholders and figure out what the pipeline should actually deliver.&lt;/p>
&lt;p>That&amp;rsquo;s still you. Make sure you have the time for it.&lt;/p>
&lt;p>&lt;br>&lt;br>&lt;/p></content:encoded><category>Data Engineering</category><category>Cloud Architecture</category><category>Data Engineering</category><category>Snowflake</category><category>OpenFlow</category><category>Salesforce</category><category>API Integration</category><category>Schema Evolution</category><category>Fivetran</category><category>Data Pipelines</category></item></channel></rss>