Logging Marketo form fills to a Google Sheet

Newbie Q: “Where do I click to see X?”

Oldie A: “You don't.”

— the least happy type of Community thread

New users may search in vain for a log of all Filled Out Form events plus point-in-time form data. (That is, even if field updates were blocked or values were later overwritten, you see the values that were on the form when each person clicked Submit).

First: it's not correct to say that Marketo doesn't store the original data from form posts: in fact, the REST Activity API includes historical POST payloads, including since-overwritten values. But it's true that it's not easy for a non-developer to see form data history across leads.

When seeking such a report outside of Marketo, users tend to say “a Google Sheet” (as opposed to XLS/CSV or other onine workbook). I'm not a Sheets user myself unless forced, but I know how it's done, and it's really easy. So that's what we'll use today.

Prereqs: A Google Form and backing Sheet

I'm going to assume you know how to set up a Google Form that saves responses to a Sheet. (If not, learn how — it's easy — then come back!)

Set up a placeholder form that includes email address and the fields you want to record from Marketo (it can be a subset of the fields on the Marketo form). Don't worry, no one is going see this form: we're going to call its post endpoint in the background to store the Marketo data.

Get the auto-generated field names

Google generates random names for each of your fields. (Other form builders do this, too, and I absolutely hate it. Marketo forms do not mangle the <input> names, and that’s a godsend.)

There’s a trick to get the field names, each of which starts with the string entry. followed by a number.

Go to the 3-dots menu and select Get pre-filled link:

2020-07-28-19_37_08-Untitled-form---Google-Forms

Fill in the form with placeholder data, then click Copy Link:

2020-07-28-19_38_01-Untitled-form

The resulting URL will have all the field names as query params, like:

.../viewform?usp=pp_url&entry.1413462437=Option+1&entry.129283687=Sandy&entry.878123412=Whiteman

Those entry.nnnnnnnnn params are your field names, in the order they’re displayed.

Get the form’s POST endpoint

Now go to the the Send button and copy the link to the standalone form.

ss

That'll take you to a standard page like this:

ss

Open up F12 Dev Tools Console and paste this one-liner (you could poke around in the HTML if you prefer):

console.log( 'Endpoint: ' + document.forms[0].action );

You'll see an Endpoint value:

2020-07-28-19_56_47-Marketo-Data

Leave this tab open. You're going to paste the Endpoint into the Marketo UI when we set up a webhook. After that, you're going to take the field names and plug them into some Forms 2.0 API JavaScript code.

But let's first make sure your Marketo form is ready to go.

Create a Marketo “superfield” and get its form field name

You need to create a single Text type field (on the lead/person, no need for CRM sync). Here I'm using one of my multipurpose fields, testTextArea01 (c’mon, I can't be creating a new field for every blog post!):

ss

Go into Form Editor, where you should already have your form set up. Temporarily add your new “superfield” as a hidden field. You can delete the field from the form afterward, but adding and inspecting it (in Dev Tools) is a good shortcut/sanity check to know what the exact field name is. Copy out the for value of the <label>. Here it's testTextArea01:

Like this:

ss

The Webhook Side

Go to Admin » Webhooks. Time to create your (first?) webhook.

ss

The webhook URL is the Endpoint you discovered above. In this example, the URL is

https://docs.google.com/forms/d/e/1FAIpQLSclNww40BK2oUeeOZplB0bqtQHbDFPH6OwUcfIohWSo78f-xw/formResponse

The webhook Template is the {{lead.token}} for the superfield. That's it, that's the entire Template. You can browse for it by clicking Insert Token.

Leave the Request Token Encoding and Response type at None.

And one more thing, annoying but simple enough. You have to add a custom header to the webhook definition.

ss
ss

That's Header Content-Type with Value application/x-www-form-urlencoded. (If you're curious, you have to add this because we're pre-URL-encoding the form data in the browser, so even though you selected None above for encoding, the data actually is encoded.)

The Forms 2.0 JS Side

Finally, the form side.

We're going to map the Google Sheet field entries to our underlying Marketo Form field names. Aside from the hard-coded emailAddress field, Google generates random field names (entry.{random}) per form (as do a lot of form builders) but with that little script you ran above, you showed the real name that corresponds to each random field.

  • Enter the translation between Marketo fields and Google fields in the mktoSheetsTranslateMap in the code that follows. The left-hand-side (keys) are the Marketo field names, the right-hand-side (values) are the Google field names. (It'll make sense when you look at it!)

  • Set the mktoPostDataTransport variable to the name of your Marketo superfield.

That's it for the form code. You don't need to edit anything below the usual --- LINE! ---

(function() {

	var mktoSheetsTranslateMap = {
			'Email': 'emailAddress',
			'FirstName': 'entry.684262398',
			'LastName': 'entry.213821674'
		},
		mktoPostDataTransport = 'testTextArea01';

	/* --- NO NEED TO EDIT BELOW THIS LINE! --- */

	MktoForms2.whenReady(function(form) {
		form.onSubmit(function(form) {
			var currentValues = form.getValues(),
				hiddenFields = {};

			/* accumulate Mkto field values, translate field names w/Sheets map, URL-encode */
			hiddenFields[mktoPostDataTransport] =
				Object.keys(mktoSheetsTranslateMap)
				.reduce(function(prev, next) {
					return ( prev ? prev + '&' : prev ) +
						[mktoSheetsTranslateMap[next],encodeURIComponent(currentValues[next])].join('=');
				}, '');

			form.addHiddenFields(hiddenFields);

		});
	});
})();

And the Smart Campaign

You might have figured this out already, but you also need a trigger SC:

ss
ss

The results

I think you'll admit this screencast is pretty darn cool:

Unable to display content. Adobe Flash is required.

What about rate limits?

You tell me, I guess? :) I haven't been able to find information on the posts-per-minute or posts-per-minute-per-source-IP allowed by Google Forms. I know Google Sheets has a limit of 2 million cells in a sheet, which should be… just fine!

Add’l note from a friend

In the comments, reader Kat mentioned an interesting case they’d solved for:

I ended up having a special use-case where the Google Form in place used section breaks, which left me only able to fill the first page’s fields. Because of the difficulty I had finding the solution, I wanted to share it here. This can be overcome by adding a hidden Marketo field with the number of sections in the Google form, starting from 0 - so if there are 4 pages, then the value would be “0,1,2,3” — and then mapping it in the mktoSheetsTranslateMap to pageHistory.