De-creepifying ZoomInfo FormComplete on Marketo forms

Level 10 - Community Moderator
Level 10 - Community Moderator

ZoomInfo’s FormComplete plugin is... potentially pretty cool. 😀 (I take care not to endorse any products here, though I didn’t say anything bad, which is a good sign!)


FormComplete delivers DiscoverOrg enrichment data directly into form fields, so starting from just an email address, you can get most — or even all — of the form fields pre-filled, in B2B at least.


(Note: this is separate from (a) pre-filling form data from Marketo for known leads and from (b) the web browser’s ability to autofill from previously stored data.)


The problem, as one of my clients put it well, is that it can be kind of creepy. Seeing your personal data evidently loading from a mysterious service, especially if you know you don’t have that data saved in the browser, ain’t so good:



(The inverse relationship between creepiness and friction can extend to activity tracking as well. But I digress.)


Much better would be having fields that are populated by FormComplete/DiscoverOrg not show up at all. So all fields other than Email are hidden to start, and only those that remain empty after the lookup, if any, are unhidden:






So I set out to reverse-engineer the ZoomInfo logic (since it’s sparsely documented, to put it kindly) and hook it into the Marketo Forms 2.0 JS API and came up with something pretty good.


I’ll leave the ZoomInfo side to you

The setup within ZoomInfo — you have to be at least a trial customer — involves getting a unique formId for their embed code (that ZoomInfo form ID is not related to the Marketo form ID) and telling them which fields you want to return.


Their pricing, I believe, is based on how much firmographic/technographic info you want, so in this case we’ll start with the most basic tier: First, Last, Phone, Title, and Company.


Start with a regular ol’ Marketo form

No need for anything too fancy in Form Editor. Just sure all the possible ZoomInfo fields are on the form (they won’t be dynamically injected, so they need to be in Form Editor explicitly).


The fields may or may not be marked Required, and potentially some could be Hidden. The assumption in the code is that any non-Hidden field that’s set up in ZI, but for which ZI doesn’t have data, will be shown to the end user.


Ergo, if Company is a Text field on the form, and ZI doesn’t know the person’s Company, then Company is revealed so they can fill it in.


If you want to send ZI’s info to Marketo whether it’s empty or not, and never show it to the end user, then you’d set the field as Hidden in Form Editor, not Text.


Take control of field visibility in JS/CSS

A prerequisite for any such project is pinpoint, client-side control over the visibility of fields that are set to always-on, always-visible in Form Editor — that is, not in the Progressive Profiling block, and not of type Hidden.


And the way to get that control is the FormsPlus-Tag JS library, available from this link: teknkl-formsplus-tag-0.2.3.js. Download the file to your server — please don’t try to serve it from my CDN, it’s not guaranteed to be cross-domain-linkable — and include it as a remote <script> anywhere after the Forms 2.0 embed code and it’ll make you very happy.


FormsPlus-Tag adds a data-wrapper-for attribute to the rows and columns of the form DOM. The value is a space-delimited list of all the fields that are inside the element. (In the example below, it’s a single-column form, but it also works with multi-column forms, where the value would be like data-wrapper-for="Email FirstName LastName", just like a class attribute.)




This simple addition to the markup allows you to hide rows and columns using CSS styles. For a simple, admittedly off-topic example: imagine you want to dynamically hide the Company if some other conditions on the page (not just on the form!) are met.


You could add a class no-company to the <form> element before you load FormsPlus-Tag, and have this CSS:

.mktoForm {
   visibility: hidden;
   position: absolute;
.mktoForm[data-initial-wrapper-tagging-complete="true"] {
   visibility: visible;
   position: static;
.mktoForm.no-company > .mktoFormRow[data-wrapper-for~=Company] {
   display: none;


Note the .mktoForm as a whole is hidden until FormsPlus-Tag has added its special “I’m done tagging” flag, data-initial-wrapper-tagging-complete="true", to the form element. Otherwise, people would see Company for a moment before it disappears. You don’t want that!


FormsPlus-Tag also does a lot more than this, but I don’t want to get bogged down today. The important thing is we do have a way to selectively unhide fields. Now over to ZoomInfo.


Take control of the ZoomInfo field-filling logic

Turned out there were 2 different things to hack away at on the ZoomInfo side:

  1. Firing a JS callback when ZoomInfo server returns a set of found values.
  2. Stopping ZoomInfo from also filling <input>s with the values, even though it had fired the callback and seemingly passed control over to JS.

I’ll just dip a bit into each area.


Figuring out the callback logic was pretty easy: it’s not publicly documented, but it is built-in. So I didn’t need to hack the Ajax call or anything. You can add a callbacks.onMatch function to the global window._zi object, before injecting the ZoomInfo script:

window._zi = { 
  callbacks: {
    onMatch: function(ziFieldsObj){
      // do something with the fields in ziFieldsObj (a JS Object)


The harder part, surprisingly, was stopping ZI from filling fields. Normally, you assume a callback function would let you return false; or set dontDoYourDefaultThing = true; or something like that to stop the out-of-the-box behavior from happening. But there’s no such function here, it just assumes you want it to fill the fields anyway!


A little more peering at their code showed a pretty easy workaround. If you set the attribute data-hasusertyped to "true" on an element, ZoomInfo thinks the person has overridden the ZoomInfo-supplied data with their own. So you can just add that attribute to all fields to start out. That gives you complete control from JS.


Putting it together

You’ll need the teknkl-formsplus-tag-0.2.3.js file provided above, and these 2 ZoomInfo-specific files:





As with the FormsPlus-tag file, please download and re-upload to your own server.


The CSS is very simple. I wanted a smooth reveal of the empty fields instead of just blasting them into view, so it transition-s the height. You’ll be able to tweak/remove that effect if you know CSS.


The JS is quite precise and I don’t recommend modifying it on your own. If something doesn’t make sense or seems to be missing just ask me. It also has a number of advanced, optional behaviors that aren’t likely to be necessary. (I’d thought during development that the UX might be weird in some edge cases. Turned out those cases were covered but I left the advanced code in there anyway.)


To deploy the solution, include the 3 remote files in the following order, after a li’l config block:

window.smartZIRevealUserConfig = {
  ziManagedFields : [
  ziLookupField : "Email",
  ziFormId : "s8YcuR7L0CVxStvxZ2wH"
<link id="teknkl-ZoomInfo-CSS-1.0.1" rel="stylesheet" href="https://yourserver.example.com/teknkl-zoominfo-smartreveal-1.0.1.css">
<script id="teknklFormsPlus-Tag-0.2.3" src="https://yourserver.example.com/teknkl-formsplus-tag-0.2.3.js"></script>
<script id="teknklZoomInfo-JS-1.0.1" src="https://yourserver.example.com/teknkl-zoominfo-smartreveal-1.0.1.js"></script>


(This above completely replaces the default ZoomInfo-provided embed code.)


The config block is simple. ziManagedFields are all the same fields you set up in ZoomInfo’s app. ziLookupField is almost certainly going to be capital-E Email, but hey, there might be some weird exception, so it’s configurable. ziformId is the ZoomInfo-assigned form ID (remember, nothing to do with the Marketo form ID).


There’s also a special “developer-only” mode (not my creation — it’s a ZoomInfo feature, but the code lets you enable it easily). Enter dev mode by adding the special hash #dev to your page URL. In this mode, you can use 1 of 3 test addresses:



Each of those addresses sends back a selection of hard-coded fields, so you can see what happens when/some/none of the fields are filled. Dev mode doesn't count any API calls/rate limits against your ZoomInfo subscription, which is great!

Level 3

Hi Sandford, hope all has been well for you. We recently got FormComplete and I wanted to implement this wonderful solution you've posted here. However, after putting everything together it looks like we've lost visibility to the entire form. 


I'm seeing this issue here in the developer tool: Uncaught ReferenceError: MktoForms2 is not defined I realized I was supposed to insert the js after the form embed. This resolved the issue. 


Great stuff as usual, Sanford. I truly appreciate all the awesome posts on your blog and on this community. Thank you. 

Level 3

@SanfordWhiteman, so I actually have run into an issue. Everything is working smoothly except I've noticed that the form stays hidden in editing mode. It wouldn't be an issue for our MOPs team to handle, but I can see it being a problem for Program Managers to deal with. Can you provide some guidance on how to disable the 'hidden' attribute in the Editing View?



Actually not a huge deal... I'll just request that they use the form selector in the Elements section. Thanks again!


Level 10 - Community Moderator

You could add some JS that will detect if the page is in the Editor IFRAME and then add special classes/attributes accordingly, so the form could still be shown. I don't have that code close at hand but let me look for it...

Level 3

@SanfordWhiteman, I tried my best to figure out how to incorporate this solution into a two column form, but I just can't figure out how to apply this to some of our lengthier forms with multiple columns. Are there adjustments to the FormPlus tag that I need to make? Sorry to bug you about this. 

Level 10 - Community Moderator

Nothing changes in FormsPlus::Tag nor in the other ZoomInfo-specific JS. It would be in the CSS. If you point to your form I can take a look.


Level 3

@SanfordWhiteman, I have no idea if I was working in my sleep or not, but it looks like I woke up to the page working as I was hoping for. Very odd. Thanks so much for your offer to help. 

Level 2

Hey Sandy,


I've discovered an issue with this if one of the fields is managed by Visibility Rules (e.g. State). If I include "State" in one of the ziManagedFields, I get this error:image.png

If I remove State from that array, but still have it on the form, I this error:


If I remove the Visibility Rule, everything works just fine of course.


I'm also curious if it's by design that this only reveals for the first match and not on subsequent matches. I suppose it's unlikely in the real world that someone types in an email that DOES match and then changes to something that DOESN'T match (and thus would have hidden fields filled in that, based on the current email, should be revealed and entered in by the User), but curious to know your thought process there.

Level 10 - Community Moderator

Thanks for pointing that out. Yes, the solution expects ZoomInfo show/hide is being used in place of VRs.   Also, the reason the first match is used was based on client feedback. It's hard to cover all the bases of something that's both "magical" and "real" if you know what I mean!

Level 2

I am hoping someone can point me in the right direction.  I have been working with our web developer and ZoomInfo to try to implement FormComplete using this guide but we just cannot get it to work.  When he implements to the second js file teknkl-zoominfo-smartreveal-1.0.1.js the zoominfo stops working as shown here:



For troubleshooting he has removed the CSS (teknkl-zoominfo-smartreveal-1.0.1.css) for now.  The page we are using to test this on is here: https://go.theeap.com/Web-Leads_Get-a-Quote-2021---Form-Complete-Test.html


Our web developer is not extremely familiar with Marketo or ZoomInfo but has done a great job for us on other projects in the past.  Any suggestions are greatly appreciated.



Level 1


Great post, thank you. We're trying to figure out how to prevent Zoominfo from pushing data to fields if they are already populated. We dont want Zoominfo to overwrite phone numbers and titles if they are already provided. What would you recommend?


Thanks in advance!