Google Forms Setup Guide
Complete Walkthrough for Google Forms API & Service Account Cloud Synchronization
Finals Management features a native, automated Google Forms API integration powered by server/google_auth.js. This allows FiveM servers to accept job applications through standard web-based Google Forms and automatically synchronize submissions directly into candidate dossiers within the in-game command tablet.
⚙️ How It Works Under The Hood
- Automated OAuth2 Token Manager (
server/google_auth.js): When your server boots,google_auth.jsreads yourservice_account.json, signs a cryptographically secure RS256 JWT assertion, and requests a scoped Bearer token fromhttps://oauth2.googleapis.com/token. - API Scopes Used:
https://www.googleapis.com/auth/forms.responses.readonlyhttps://www.googleapis.com/auth/forms.body.readonly
- Zero-Touch Token Renewal: Tokens are cached in server memory and automatically renewed every 45 minutes in the background. No manual token regeneration or server restarts required.
- Dynamic Question Schema Fetching: The script queries the Google Forms metadata API to dynamically pull your actual question titles and maps them cleanly into applicant answers.
- Smart Keyword Mapping: Answers for questions containing
nameorapplicantare mapped to the applicant name; questions containingcitizen,cid, orstate idare mapped to the player's Citizen ID.
📋 Step-by-Step Setup Guide
Step 1: Create a Google Cloud Project & Enable the Forms API
- Navigate to the Google Cloud Console.
- Log in with your Google account.
- In the top navigation bar, click the project dropdown and select New Project.
- Name your project (e.g.,
FiveM-Department-Recruitment) and click Create. - Ensure your newly created project is selected in the top bar.
- Open the left sidebar menu (☰) and navigate to APIs & Services > Library.
- In the search box, search for Google Forms API.
- Click on Google Forms API and click the blue Enable button.
Step 2: Create a Service Account & Download service_account.json
-
In the left navigation menu, go to IAM & Admin > Service Accounts.
-
Click + Create Service Account at the top.
-
Enter a Service account name (e.g.,
finals-management). -
Click Create and Continue, then click Done (granting project roles is optional).
-
In the Service Accounts list, locate the account you just created and click on it.
-
Navigate to the Keys tab at the top.
-
Click Add Key > Create new key.
-
Select JSON as the key type and click Create.
-
A
.jsonfile will immediately download to your computer. -
Rename this downloaded file to:
Plain textservice_account.json -
Move
service_account.jsondirectly into your server's resource root folder:Plain textresources/[finals]/finals_management/service_account.json
[!IMPORTANT] The file name must be exactly
service_account.jsonand placed in the root directory offinals_managementalongsidefxmanifest.luaandconfig.lua.
Step 3: CRITICAL — Share Your Google Form with the Service Account
[!WARNING] This is the #1 most common installation mistake! A Google Service Account cannot read private Google Forms by default. You MUST grant the service account permission to view your Google Form.
-
Open your
service_account.jsonfile in a text editor (e.g., VS Code or Notepad). -
Locate the line with
client_email:JSON"client_email": "finals-management@your-project-id.iam.gserviceaccount.com" -
Copy this entire email address.
-
Open the Google Form you want to link in your web browser.
-
In the top-right corner of the Google Form editor, click the Three Dots menu (⋮) next to the purple "Send" button.
-
Click Add collaborators (or share settings).
-
Paste the copied service account email address into the "Add people and groups" field.
-
Ensure the permission is set to Editor (or Viewer).
-
Uncheck "Notify people" (since the service account has no inbox) and click Send / Share.
Step 4: Design Your Form & Utilize Smart Field Mapping
When civilians submit the form, finals_management automatically maps responses into the candidate dossier using smart keyword detection. Structure your questions as follows:
| Field in Tablet Dossier | Required Question Title Keyword | Example Question Titles in Google Form |
|---|---|---|
| Applicant Name | Contains name, candidate, or applicant | "Full Character Name", "Applicant First & Last Name", "Candidate Name" |
| Citizen ID / CID | Contains citizen, cid, state id, or passport | "Citizen ID / State ID", "Character CitizenID", "Passport / CID Number" |
| All Other Questions | Any custom title | "Previous Department Experience", "Weekly Availability", "Why join LSPD?", "Discord Handle" |
[!TIP] If an applicant leaves the Citizen ID field blank, the script automatically assigns a temporary reference identifier (
GFORM-XXXXXX) so the application can still be reviewed, interviewed, and promoted in-game.
Step 5: Configure config.lua
Open config.lua and configure your department's Google Form URL:
Config.Application = {
enabled = true,
--- Set global mode to 'google_forms'
mode = 'google_forms', -- 'in_game' | 'google_forms'
--- Submissions retention (older filings automatically purged from database)
filingRetentionDays = 7,
filingRetentionCheckMinutes = 60,
jobs = {
police = {
label = 'Los Santos Police Department',
--- Official Google Form URL (supports both viewform and edit URLs)
googleFormUrl = 'https://docs.google.com/forms/d/1rtPTMm7Rs4WgUweuGFaavEwcS3yU0eDVT6AiEstm3_Q/viewform',
--- In-world recruitment desk NPC
ped = {
model = 's_f_y_cop_01',
coords = { x = 440.6752, y = -978.9439, z = 30.6896, w = 176.7222 },
scenario = 'WORLD_HUMAN_GUARD_STAND',
},
--- Default interview room waypoint set when scheduling interviews
defaultInterviewLocation = 'Mission Row PD — Interview Room 2',
},
ambulance = {
label = 'Emergency Medical Services',
googleFormUrl = 'https://docs.google.com/forms/d/YOUR_EMS_FORM_ID/viewform',
ped = {
model = 's_m_m_doctor_01',
coords = { x = -247.1, y = 6331.4, z = 32.4, w = 220.0 },
},
defaultInterviewLocation = 'Pillbox Hill Medical Center — Reception',
},
},
}Step 6: In-Game Management & Cloud Synchronization
- In-game, open the management tablet using
/management(as an authorized supervisor or boss). - Navigate to the Recruitment tab.
- In Google Forms mode, the tablet displays:
- Connected Google Form ID and link.
- Sync Cloud Responses button.
- Live list of candidate dossiers pulled directly from Google Forms.
- Supervisors can:
- Inspect Dossier: Read full candidate answers, timestamp, and Citizen ID.
- Schedule Interview: Sets an in-game GPS waypoint to the department's interview location and notifies the candidate.
- Approve / Accept: Prepares the digital employment offer contract for signing.
- Decline: Closes the application dossier and logs the decision to Discord.
Step 7 (Optional): Automated Form Response Deletion via Apps Script
When a supervisor declines or deletes a candidate submission in-game, you can optionally configure Google Forms to automatically delete that response from the Google Form so it does not clutter your spreadsheet.
- Open your Google Form in your browser.
- Click the Three Dots menu (⋮) in the top right > < > Script editor.
- Delete any default code in
Code.gsand paste the following snippet:
function doPost(e) {
try {
var data = JSON.parse(e.postData.contents);
if (data.action === "deleteResponse" && data.formId && data.responseId) {
var form = FormApp.openById(data.formId);
form.deleteResponse(data.responseId);
return ContentService.createTextOutput(JSON.stringify({ ok: true }))
.setMimeType(ContentService.MimeType.JSON);
}
} catch (err) {
return ContentService.createTextOutput(JSON.stringify({ ok: false, error: err.message }))
.setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(JSON.stringify({ ok: false }))
.setMimeType(ContentService.MimeType.JSON);
}-
Click Deploy > New deployment.
-
Select type: Web app.
-
Set:
- Execute as:
Me (your Google email) - Who has access:
Anyone
- Execute as:
-
Click Deploy and authorize the script permissions.
-
Copy the generated Web App URL (e.g.,
https://script.google.com/macros/s/AKfycb.../exec). -
Paste this URL into
config.lua:LuaConfig.Application.googleApi = { deleteWebhookUrl = 'https://script.google.com/macros/s/YOUR_APPS_SCRIPT_ID/exec', }
🛠️ Troubleshooting & FAQs
| Error Message | Root Cause | Solution |
|---|---|---|
HTTP 403: The caller does not have permission | Service account is not shared on the Google Form. | Open your Google Form -> Click Three Dots (⋮) -> Add collaborators -> Paste client_email from service_account.json as Editor. |
HTTP 404: Not Found | Invalid Form ID or Form deleted. | Verify the URL in config.lua. Ensure you copied the full link from your browser. |
Failed to read service_account.json | File missing or misplaced. | Ensure service_account.json is located in the root of finals_management (next to fxmanifest.lua). |
| Applicant name appears blank | Question title keyword mismatch. | Rename the question in Google Form to include the word Name (e.g. "Applicant Character Name"). |
Citizen ID displays as GFORM-XXXXXX | Form does not have a Citizen ID question. | Add a short answer question titled "Citizen ID" or "State ID" to your Google Form. |
| Token fails to generate on startup | Invalid private key or clock skew. | Verify system clock on your server machine is accurate (OAuth JWT rejects requests with skewed timestamps). |