To integrate Lovable with Google Meet, create a Supabase Edge Function that uses the Google Calendar API to create calendar events with conferenceData, which automatically generates a Meet link. Authenticate using a Google service account, store the private key in Cloud → Secrets, and call the Edge Function from your Lovable frontend to create meetings and return joinable Meet URLs.
Generate Google Meet links in Lovable using the Calendar API
Google Meet works differently from Zoom or Webex — there is no standalone 'create a meeting' endpoint. Instead, Meet links are a feature of Google Calendar events. When you create a calendar event via the API and include a conferenceData request, Google automatically provisions a Meet room and attaches its URI to the event. This design means Meet integration is fundamentally a Calendar API integration, using Google Calendar v3 with the conferenceDataVersion=1 query parameter.
For Lovable applications, this pattern works cleanly through an Edge Function: your function signs a JWT using a service account private key, exchanges it for a short-lived Google access token, then calls the Calendar API to insert an event. The response includes the event's conferenceData.entryPoints array, from which you extract the VIDEO type entry to get the Meet link URI. This URI is permanent for the duration of the event and can be shared with participants immediately.
Service accounts are the right authentication approach for this use case — they operate server-to-server without requiring individual users to grant OAuth permission. The service account must be granted access to the target calendar (or you can have it create events on any user's calendar by enabling Google Workspace domain-wide delegation). This guide uses a simple approach: the service account creates events on its own calendar, and the Meet link is extracted and returned to your app.
Integration method
Google Meet does not have a standalone REST API for meeting creation. Instead, Meet links are generated by creating Google Calendar events with the conferenceData parameter set to requestNewConference. A Supabase Edge Function authenticates to Google's Calendar API using a service account JWT, creates the calendar event, and returns the Meet link. Credentials live encrypted in Cloud → Secrets and never reach the browser.
Prerequisites
- A Lovable project with Lovable Cloud enabled (visible in the Cloud tab of the editor)
- A Google Cloud project at console.cloud.google.com with the Google Calendar API enabled
- A Google Cloud service account created with the service account JSON key file downloaded
- Basic understanding of Lovable Edge Functions and Cloud → Secrets
- The Google account whose calendar the service account will create events on (can be the service account's own calendar)
Step-by-step guide
Set up a Google Cloud service account with Calendar API access
Set up a Google Cloud service account with Calendar API access
Navigate to console.cloud.google.com and select or create a project. In the left sidebar, go to 'APIs & Services' → 'Library', search for 'Google Calendar API', and click Enable. Next, go to 'APIs & Services' → 'Credentials' and click 'Create Credentials' → 'Service Account'. Give the service account a descriptive name like 'lovable-meet-bot' and click Create and Continue. You do not need to assign any Google Cloud IAM roles for Calendar API access — click Done. Back on the Credentials page, click on your new service account to open it. Go to the 'Keys' tab and click 'Add Key' → 'Create New Key'. Select JSON format and click Create. This downloads a JSON file containing your service account credentials, including the private_key, client_email, and other fields. Keep this file secure — treat it like a password. You will need the private_key (a PEM-formatted RSA private key) and the client_email from this file for the next step.
Pro tip: The service account's client_email is the 'identity' that will appear as the organizer of created calendar events. If you want events to appear on a specific Google Calendar, you will need to share that calendar with the service account email and grant it 'Make changes to events' permission.
Expected result: A service account JSON key file downloaded with private_key and client_email values ready to extract.
Store service account credentials in Cloud → Secrets
Store service account credentials in Cloud → Secrets
The service account JSON file contains several fields, but you only need two for the Edge Function: private_key and client_email. In your Lovable editor, open the Cloud tab by clicking the '+' panel selector at the top right, then click Cloud. Scroll to 'Secrets' and add two secrets. Name the first secret GOOGLE_SERVICE_ACCOUNT_EMAIL and paste the client_email value from your JSON file (it looks like lovable-meet-bot@your-project.iam.gserviceaccount.com). Name the second secret GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY and paste the entire private_key value — this is a multi-line string starting with -----BEGIN RSA PRIVATE KEY----- or -----BEGIN PRIVATE KEY-----. Copy the entire string including the header and footer lines. A common issue: the private key in the JSON file has literal \n characters representing newlines. When you paste it into Cloud → Secrets, you may need to replace the literal \n sequences with actual newlines (or your Edge Function code will handle this with a .replace(/\\n/g, '\n') call). Lovable's Secrets panel stores the value as-is, so be careful about how the key is formatted. Lovable blocks approximately 1,200 hardcoded keys daily — always use Secrets, never paste keys in chat.
Pro tip: When pasting a multi-line private key into a single-line Secrets field, many developers store it with \n as literal characters and then use .replace(/\\n/g, '\n') in the Edge Function to convert them to actual newlines before using the key.
Expected result: Two secrets saved: GOOGLE_SERVICE_ACCOUNT_EMAIL and GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY visible in Cloud → Secrets.
Build the Google Meet link generation Edge Function
Build the Google Meet link generation Edge Function
The Edge Function needs to: (1) construct a JWT from the service account credentials, (2) exchange the JWT for a Google access token, and (3) call the Calendar API to create an event with conferenceData. Deno has built-in crypto support for JWT signing. The JWT for Google service accounts uses RS256 signing. The header is { alg: 'RS256', typ: 'JWT' }. The payload includes iss (service account email), scope (https://www.googleapis.com/auth/calendar), aud (https://oauth2.googleapis.com/token), exp (one hour from now), and iat (now). The token endpoint is https://oauth2.googleapis.com/token with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. Once you have the access token, POST to https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1 with the event body. The conferenceData field must include createRequest with requestId (unique per request) and conferenceSolutionKey.type set to 'hangoutsMeet'. The response contains conferenceData.entryPoints array — find the entry with entryPointType 'video' and return its uri as the Meet link.
Create a Supabase Edge Function called google-meet that: 1) Reads GOOGLE_SERVICE_ACCOUNT_EMAIL and GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY from env. 2) Creates a Google OAuth2 JWT using RS256, with scope https://www.googleapis.com/auth/calendar. 3) Exchanges the JWT at https://oauth2.googleapis.com/token for an access token. 4) Accepts a POST body with: summary (string), start_time (ISO string), end_time (ISO string), description (string, optional). 5) Calls POST https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1 to create an event with hangoutsMeet conferenceData. 6) Returns the Meet join URI extracted from conferenceData.entryPoints where entryPointType is 'video'. Include CORS headers and error handling.
Paste this in Lovable chat
1// supabase/functions/google-meet/index.ts2import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';34const corsHeaders = {5 'Access-Control-Allow-Origin': '*',6 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',7};89async function getGoogleAccessToken(): Promise<string> {10 const email = Deno.env.get('GOOGLE_SERVICE_ACCOUNT_EMAIL')!;11 const privateKeyPem = Deno.env.get('GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY')!.replace(/\\n/g, '\n');1213 const now = Math.floor(Date.now() / 1000);14 const payload = {15 iss: email,16 scope: 'https://www.googleapis.com/auth/calendar',17 aud: 'https://oauth2.googleapis.com/token',18 exp: now + 3600,19 iat: now,20 };2122 // Encode JWT header and payload23 const header = btoa(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');24 const body = btoa(JSON.stringify(payload)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');25 const signingInput = `${header}.${body}`;2627 // Import private key28 const pemContents = privateKeyPem.replace(/-----BEGIN.*-----/, '').replace(/-----END.*-----/, '').replace(/\s/g, '');29 const binaryKey = Uint8Array.from(atob(pemContents), c => c.charCodeAt(0));30 const cryptoKey = await crypto.subtle.importKey(31 'pkcs8', binaryKey.buffer,32 { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },33 false, ['sign']34 );3536 // Sign JWT37 const signature = await crypto.subtle.sign(38 'RSASSA-PKCS1-v1_5',39 cryptoKey,40 new TextEncoder().encode(signingInput)41 );42 const sigBase64 = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');43 const jwt = `${signingInput}.${sigBase64}`;4445 // Exchange JWT for access token46 const tokenRes = await fetch('https://oauth2.googleapis.com/token', {47 method: 'POST',48 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },49 body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${jwt}`,50 });5152 const tokenData = await tokenRes.json();53 if (!tokenRes.ok) throw new Error(`Token error: ${JSON.stringify(tokenData)}`);54 return tokenData.access_token;55}5657serve(async (req) => {58 if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders });5960 try {61 const { summary, start_time, end_time, description = '' } = await req.json();62 const accessToken = await getGoogleAccessToken();6364 const eventRes = await fetch(65 'https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1',66 {67 method: 'POST',68 headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' },69 body: JSON.stringify({70 summary,71 description,72 start: { dateTime: start_time, timeZone: 'UTC' },73 end: { dateTime: end_time, timeZone: 'UTC' },74 conferenceData: {75 createRequest: {76 requestId: crypto.randomUUID(),77 conferenceSolutionKey: { type: 'hangoutsMeet' },78 },79 },80 }),81 }82 );8384 const event = await eventRes.json();85 if (!eventRes.ok) throw new Error(`Calendar API error: ${JSON.stringify(event)}`);8687 const meetEntry = event.conferenceData?.entryPoints?.find((ep: { entryPointType: string }) => ep.entryPointType === 'video');88 const meetLink = meetEntry?.uri ?? null;8990 return new Response(91 JSON.stringify({ meetLink, eventId: event.id, eventLink: event.htmlLink }),92 { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }93 );94 } catch (error) {95 return new Response(96 JSON.stringify({ error: error.message }),97 { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }98 );99 }100});Pro tip: The conferenceData.createRequest.requestId must be unique per request — using crypto.randomUUID() on each call guarantees this. Using the same requestId twice may cause Google to return the same conference room, which can be intentional but is usually a mistake.
Expected result: A deployed google-meet Edge Function that creates a Calendar event with a Meet link and returns the Meet URI.
Connect the Meet link generator to your Lovable UI
Connect the Meet link generator to your Lovable UI
With the Edge Function deployed, ask Lovable to build the UI component that calls it. The frontend code should call supabase.functions.invoke('google-meet', { body: { summary, start_time, end_time } }) and handle the returned meetLink. The Supabase client automatically adds the authentication header required by Edge Functions. Display the Meet link with a prominent 'Join Meeting' button and a copy-to-clipboard button. If your app manages bookings or sessions, store the meetLink and eventId in your Supabase bookings table alongside the meeting time and participant information. Make sure your Row Level Security policy allows authenticated users to read their own bookings but not others'. For emailing the link, you can chain the Edge Function response with a Resend call — Lovable can generate this combined flow if you describe it in your prompt.
Create a schedule meeting form with: summary text input, date/time picker for start and end times. When submitted, call the google-meet Edge Function. On success, display the Meet join link in a card with a 'Join' button (opens the link) and a 'Copy link' button. Save the meeting data including meetLink and eventId to a meetings table in Supabase. Show a loading state while the function runs.
Paste this in Lovable chat
Pro tip: Google Meet links (meet.google.com/xxx-xxxx-xxx) are valid as long as the calendar event exists. If you delete the event from Google Calendar, the Meet room may become inaccessible.
Expected result: A UI form in Lovable that creates a real Google Meet link when submitted and displays it for copying or sharing.
Common use cases
Instant Meet link generation for a coaching platform
When a client books a session in your scheduling app, automatically generate a Google Meet link and include it in the confirmation email and the client's dashboard. The Edge Function creates the calendar event and returns the Meet URI, which gets stored in your bookings table.
Add a booking confirmation flow that calls my google-meet Edge Function to generate a Meet link when a session is booked. Store the meet_link in the bookings table and display it in the booking confirmation screen. Include a 'Join Meeting' button that opens the Meet link.
Copy this prompt to try it in Lovable
Automated team standup link refresher
Create a weekly scheduler that generates fresh Google Meet links for recurring team standups. The Edge Function creates recurring calendar events with unique Meet rooms, and the links are posted to a Slack channel or stored in a Supabase table for team members to access.
Create a scheduler page where I can set a recurring standup time. When I click 'Generate This Week's Link', call my google-meet Edge Function to create a calendar event and return a Meet link. Display the link with a copy button and store it in a standups table.
Copy this prompt to try it in Lovable
Customer onboarding call scheduler with Meet links
Build an onboarding flow where new users schedule their kickoff call and immediately receive a Google Meet link. The Edge Function creates the event for the specified time, returns the join URL, and your Lovable app sends a confirmation with the link via Resend.
Build an onboarding call scheduler. Show a time picker, and when the user selects a slot and clicks 'Schedule Call', call my google-meet Edge Function to create a 30-minute meeting at that time. Show the Meet link and send a confirmation email with the link using Resend.
Copy this prompt to try it in Lovable
Troubleshooting
Edge Function returns 'invalid_grant' or 401 when exchanging the JWT for a Google access token
Cause: The service account private key is incorrectly formatted — the literal \n characters in the JSON file were not converted to actual newlines, so the PEM parsing fails.
Solution: In your Edge Function code, add .replace(/\\n/g, '\n') when reading the private key from Deno.env.get('GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY'). Also verify the service account has not been deleted or disabled in Google Cloud Console → IAM & Admin → Service Accounts.
1const privateKeyPem = Deno.env.get('GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY')!.replace(/\\n/g, '\n');Calendar API returns 403 with 'Service accounts cannot invite attendees without Domain-Wide Delegation of Authority'
Cause: The service account is trying to create events on a user's calendar (not the service account's own primary calendar) without domain-wide delegation enabled in Google Workspace.
Solution: Either create events on the service account's own primary calendar (use 'primary' as the calendarId), or enable domain-wide delegation in Google Workspace Admin Console and add the Calendar API scope. For most Lovable use cases, using the service account's own calendar and simply sharing the Meet link is the simplest approach.
Event is created successfully but conferenceData is null or missing the Meet link
Cause: The conferenceDataVersion=1 query parameter was omitted from the Calendar API request URL, or the Google Calendar API was not enabled in Google Cloud Console.
Solution: Verify your API call URL includes ?conferenceDataVersion=1 as a query parameter. Also confirm in Google Cloud Console → APIs & Services → Enabled APIs that the Google Calendar API is listed as enabled for your project.
1const url = 'https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1';The service account cannot access the Google Calendar API — returns 'API has not been used in project'
Cause: The Google Calendar API is not enabled for the Google Cloud project where the service account was created.
Solution: Go to console.cloud.google.com → APIs & Services → Library. Search for 'Google Calendar API' and click Enable. Wait 1-2 minutes for the enablement to propagate, then retry the Edge Function call.
Best practices
- Cache Google access tokens for up to 55 minutes (tokens last 60 minutes — subtract 5 as a safety buffer) to avoid signing a new JWT on every meet link request
- Always include a unique requestId in conferenceData.createRequest — use crypto.randomUUID() to generate one per call
- Store the Google Calendar eventId in your Supabase database alongside the Meet link so you can update or delete the event later if the meeting is cancelled
- Use the service account's own primary calendar for event creation unless you specifically need events to appear on users' individual calendars, which requires domain-wide delegation
- Set the timeZone field in event start and end objects to match the user's timezone — 'UTC' works as a safe default but is confusing for users who see the event in their calendar
- Rotate service account keys periodically and update Cloud → Secrets immediately — treat these keys with the same care as database passwords
Alternatives
Zoom has a purpose-built REST API for meeting creation that is simpler and more feature-rich than Google Meet's Calendar API approach, making it better for apps where meetings are a primary feature.
Microsoft Teams is the better choice if your users are in the Microsoft 365 ecosystem and need meetings integrated with Outlook calendars and Teams channels.
Webex has a dedicated meetings REST API (not calendar-based) and is preferred for enterprise environments with Cisco hardware room systems.
Frequently asked questions
Can I create Google Meet links without a Google Workspace account?
You can use a standard Google account to create a service account and call the Calendar API, but service account access to create calendar events with Meet links works most reliably with Google Workspace. Personal Google accounts have some limitations around service account calendar access — if you encounter issues, using your service account's own calendar (calendarId: 'primary') is the most reliable approach.
Do Google Meet links expire?
Google Meet links are tied to calendar events. As long as the calendar event exists, the Meet link remains valid. If you delete the event, the Meet room may eventually become inaccessible. Meet links from the same event can be reused across multiple sessions — the room does not automatically close when a call ends.
Can I add participants to the calendar event so they receive an invite?
Yes — add an attendees array to the event creation body with objects containing email addresses. However, service accounts can only send invites to attendees within the same Google Workspace domain unless domain-wide delegation is configured. For external attendees, share the Meet link directly rather than relying on calendar invites.
Why does my Edge Function work locally but return errors in production?
The most common cause is the private key formatting issue — the \n sequences in the JSON file need to be actual newlines in the PEM string. Check Cloud → Logs for the specific error. Also verify that the GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY secret in production contains the full key including the BEGIN and END header lines.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation