Esse artigo está disponível em Português aqui.
In this article, I'll talk very little about Application Integration! Just know that the goal here is to trigger an integration in App Integration when a Google Form is submitted (taking, of course, the Form's content as input for the integration). The bridge here will be made via Pub/Sub. In other words, the objective is to deliver the submitted Form's content to a Pub/Sub topic. Once that's done, you just need to create an integration that uses the PubSub Trigger. Let's get started!
Google Forms is a fantastic tool for collecting data, but what if you need to process these responses in real-time or integrate them with more complex backend systems? A powerful solution is to send the data from each response to a Google Cloud Pub/Sub topic as soon as the form is submitted. This opens doors for real-time data processing, analysis, triggering App Integration, and much more.
In this post, we will detail how to set up a script in Google Apps Script to automatically publish data from a Google Form to a Pub/Sub topic, with a special focus on permission details and environment configuration.
Prerequisites:
Step 1: Google Cloud Project (GCP) Configuration
Before touching the script, we need to prepare the environment in GCP.
form-submission
). Note this Topic ID.Step 2: Google Apps Script Configuration
Now, let's connect our Google Form to the script and our GCP project.
ScriptApp.getOAuthToken()
will request a token with permission to interact with your configured GCP project.Step 3: The Apps Script Code
Copy and paste the following code into the script editor (usually in the Code.gs
file).
/**
* Configuration: Replace with your values!
*/
const GcpProjectId = 'YOUR_GCP_PROJECT_ID'; // <-- Replace with your GCP project ID
const PubSubTopicName = 'YOUR_PUBSUB_TOPIC_ID'; // <-- Replace with your Pub/Sub topic ID
/**
* This function is triggered when a form is submitted.
* @param {GoogleAppsScript.Events.FormsOnSubmit} e The form submission event object.
*/
function onFormSubmit(e) {
try {
// Get the form response from the event object
const formResponse = e.response;
if (!formResponse) {
Logger.log('Event object does not contain a form response.');
return;
}
// Get all item responses from the submission
const itemResponses = formResponse.getItemResponses();
// Create a data object to store the form values
const formData = {
timestamp: formResponse.getTimestamp().toISOString(), // Submission timestamp in ISO format
respondentEmail: formResponse.getRespondentEmail() || 'anonymous', // Respondent's email (if collected and allowed)
responses: {} // Object to map questions and answers
};
// Map form questions to answers
itemResponses.forEach(itemResponse => {
const question = itemResponse.getItem().getTitle(); // Get the question text
const answer = itemResponse.getResponse(); // Get the answer
formData.responses[question] = answer; // Add to the responses object
});
// Send to Pub/Sub using direct HTTP call with OAuth token
const result = publishToPubSub(formData);
Logger.log('Message published to Pub/Sub: ' + result);
} catch (error) {
// Log any errors that occur during the process
Logger.log('Error in onFormSubmit: ' + error.toString() + '\nStack: ' + error.stack);
}
}
/**
* Publishes a message to Pub/Sub using the Apps Script OAuth token.
* @param {Object} data The data object to be published.
* @return {string} The response body from the Pub/Sub API on success.
* @throws {Error} If the publication fails.
*/
function publishToPubSub(data) {
// Basic validation of constants
if (!GcpProjectId || GcpProjectId === 'YOUR_GCP_PROJECT_ID') {
throw new Error('GcpProjectId is not configured. Edit the script and replace the placeholder.');
}
if (!PubSubTopicName || PubSubTopicName === 'YOUR_PUBSUB_TOPIC_ID') {
throw new Error('PubSubTopicName is not configured. Edit the script and replace the placeholder.');
}
// Prepare the message for the Pub/Sub API
const pubsubData = {
messages: [
{
// Data needs to be Base64 encoded
data: Utilities.base64Encode(JSON.stringify(data))
}
]
};
// Get the OAuth token from the Apps Script environment.
// THIS IS CRUCIAL: The token belongs to the USER authorizing the script.
// This user MUST have permission to publish to the specified Pub/Sub topic
// in the GCP project associated with the script.
// The required IAM permission is typically 'roles/pubsub.publisher'.
const token = ScriptApp.getOAuthToken();
// Build the Pub/Sub API endpoint URL
const pubsubUrl = `https://pubsub.googleapis.com/v1/projects/${GcpProjectId}/topics/${PubSubTopicName}:publish`;
// Configure the HTTP request options
const options = {
method: 'post', // HTTP POST method
contentType: 'application/json', // Payload content type
// Payload must be a JSON string
payload: JSON.stringify(pubsubData),
headers: {
// Authentication using the obtained OAuth token
Authorization: 'Bearer ' + token
},
muteHttpExceptions: true // Important: Catches HTTP errors for manual handling
};
// Make the call to the Pub/Sub API using UrlFetchApp
const response = UrlFetchApp.fetch(pubsubUrl, options);
// Check the HTTP response status code
const responseCode = response.getResponseCode();
const responseText = response.getContentText();
// If the response is successful (2xx codes)
if (responseCode >= 200 && responseCode < 300) {
Logger.log('Pub/Sub API Response (Success): ' + responseText);
return responseText; // Return the response body (usually contains messageIds)
} else {
// If there's an error, log details and throw an exception
Logger.log(`Error publishing to Pub/Sub. Code: ${responseCode}. Response: ${responseText}`);
throw new Error(`Failed to publish to Pub/Sub: ${responseText} (Code: ${responseCode})`);
}
}
/**
* Sets up the 'onFormSubmit' trigger for the active form.
* This function must be run manually ONCE from the script editor.
*/
function createFormSubmitTrigger() {
// Remove old triggers to avoid duplicates (optional, but recommended)
const currentTriggers = ScriptApp.getProjectTriggers();
currentTriggers.forEach(trigger => {
if (trigger.getHandlerFunction() === 'onFormSubmit' &&
trigger.getEventType() === ScriptApp.EventType.ON_FORM_SUBMIT) {
ScriptApp.deleteTrigger(trigger);
Logger.log('Existing onFormSubmit trigger removed.');
}
});
// Get the form the script is bound to
const form = FormApp.getActiveForm();
// Create a new trigger that calls 'onFormSubmit' when the form is submitted
ScriptApp.newTrigger('onFormSubmit')
.forForm(form)
.onFormSubmit()
.create();
Logger.log('onFormSubmit trigger created successfully.');
}
Detailed Code Explanation:
GcpProjectId
, PubSubTopicName
): It is essential that you replace the placeholder values 'YOUR_GCP_PROJECT_ID'
and 'YOUR_PUBSUB_TOPIC_ID'
with your actual IDs obtained in Step 1.onFormSubmit(e)
:
e
: The event object containing information about the submission, including e.response
.formResponse.getItemResponses()
: Gets an array containing each question and its corresponding answer.formData
: We create a structured JSON object to send to Pub/Sub. It includes:
timestamp
: Submission date and time in standard ISO 8601 format.respondentEmail
: The email of the user who responded (if email collection is enabled in the form and the user allows it). Otherwise, 'anonymous'
.responses
: An object where each key is the question title and the value is the given answer.forEach
loop iterates over the responses, extracting the question title (item.getTitle()
) and the answer (itemResponse.getResponse()
) and populating the formData.responses
object.publishToPubSub
function to send the collected data.try...catch
: Captures and logs any errors that might occur during response processing.publishToPubSub(data)
:
pubsubData
) in the format expected by the Pub/Sub API: an object with a messages
key, which is an array containing message objects. Each message has a data
key whose value must be the actual payload encoded in Base64. We use Utilities.base64Encode(JSON.stringify(data))
for this.ScriptApp.getOAuthToken()
: This is the key point for authentication. It returns an OAuth 2.0 token representing the user who authorized the script. When you (or whoever sets up the trigger) run createFormSubmitTrigger
for the first time, Google will request permission for the script to access external services (like Pub/Sub) on behalf of that user. Therefore, the user authorizing the script MUST have the necessary IAM permissions in the associated GCP project to publish messages to the specified Pub/Sub topic (typically, the roles/pubsub.publisher
role or a broader role that includes it, like roles/editor
or roles/owner
).UrlFetchApp.fetch()
: Makes the HTTP POST request to the Pub/Sub API endpoint.
GcpProjectId
and PubSubTopicName
.headers
: Includes the Authorization: Bearer <token>
header to authenticate the call using the obtained token.payload
: The Pub/Sub message JSON.muteHttpExceptions: true
: Prevents HTTP errors (like 403 Forbidden, 404 Not Found) from stopping script execution immediately. This allows us to check response.getResponseCode()
manually.responseCode
) is in the success range (200-299). If so, logs and returns the API response. If not, logs the error and throws an exception to indicate failure, which will be caught by the try...catch
in onFormSubmit
.createFormSubmitTrigger()
:
onFormSubmit
triggers to prevent duplicate executions if you run it more than once.onFormSubmit
event of the active form to the execution of the onFormSubmit
function in your script.Step 4: Authorization and Testing
createFormSubmitTrigger
:
onFormSubmit
is likely selected), choose createFormSubmitTrigger
.onFormSubmit
. Click on it to see the logs (Logger.log
). You should see the message "Message published to Pub/Sub: ..." if everything went well, or error messages otherwise.form-submission
). Give it a name (e.g., form-submission-test-sub
), select the correct topic, and keep the rest as default (Delivery type: Pull).Important Considerations
roles/pubsub.publisher
role (or equivalent) in the associated GCP project for the publishToPubSub
call to work. If you receive 403 Forbidden
errors, check this user's IAM permissions in GCP.try...catch
and HTTP response code checking). For production, you might want to add more robust handling, such as failure notifications (e.g., via email or another alert system).UrlFetchApp
calls per day) and Pub/Sub quotas (e.g., throughput, message size). For very high-volume forms, you might need to consider other architectures (e.g., using a Cloud Function triggered directly by the Form, though this requires more setup).Conclusion
Integrating Google Forms with Pub/Sub using Apps Script opens up a range of possibilities for automating workflows and processing data in real-time. By following the GCP setup steps, correctly associating the Apps Script project with GCP, understanding the authentication flow via ScriptApp.getOAuthToken()
, and implementing the provided code, you can get this integration working quickly. Always remember to check permissions and test the complete flow!