This integration is for self-hosted Cognee deployments — you create and own the Google Cloud OAuth client, so it lives in your own Google Cloud project and talks only to your own Cognee backend.
v1.6.0 and is not tagged yet, so it needs a dev checkout until the next release. See the changelog.
What It Does
- One Google account, one Cognee user. The credential is owned by whoever completed the consent, and the account’s files are indexed into that user’s memory. There is no Workspace-wide install: domain-wide delegation (one service account impersonating every member) is a different entry point the generic OAuth callback cannot express, and is deliberately not attempted.
- Personal and Workspace are the same code path. They differ only by the hosted-domain (
hd) claim Google returns, which is recorded on the connection asaccount_type: workspaceorpersonal. - Read-only. The requested scopes are
openid email https://www.googleapis.com/auth/drive.readonly— Cognee never writes to Drive. - Drive’s grant stays its own. The authorize URL deliberately omits
include_granted_scopes, so Drive’s grant is never folded together with the Gmail connector’s — or a later Calendar one’s. Google’s revoke endpoint kills a whole grant, so sharing one would mean disconnecting one product silently disconnects the others. - Only supported file types are read. Google Docs, Google Sheets, PDFs, and plain text, Markdown and CSV files become memory; everything else in Drive is skipped. See What gets indexed.
- Incremental after the first pass. The first sync lists the selected folders in full and records a Drive Changes-API cursor; later syncs carry only what changed, including deletions.
Prerequisites
-
A Cognee backend on
dev— see the version note above. -
The
google-driveextra installed on that backend:It pulls ingoogle-api-python-client,google-authandgoogle-auth-oauthlib, which the sync path builds its Drive client with. OAuth itself works without them, so a deployment that skips this connects fine and then fails on its first sync with anImportError. - A Google Cloud project you can create an OAuth client in.
-
A backend URL Google can redirect a browser to. Unlike webhook-driven connectors, Google only ever redirects the user’s browser back, so
http://localhost:8000is fine for local development — no tunnel is required.
Setup
1. Create the Google Cloud OAuth client
In the Google Cloud Console, pick (or create) a project, then:
Then collect the client’s Client ID and Client secret.
2. Configure Cognee
Copy the client’s values into your backend’s.env (the same block is in .env.template):
The prefix is
GOOGLE_DRIVE_ rather than GOOGLE_ on purpose: the latter is already taken by unrelated settings, and the Gmail connector — like a later Calendar one — is a separate provider with its own scopes and its own consent screen. They share the Cloud project, not the configuration block.
Every one of these is checked at use time, not at boot: a deployment with Drive unconfigured still starts normally, and the first call that needs a missing value fails naming it rather than degrading into a token exchange with empty credentials. POST /authorize turns that into a 503 (google_drive integration is not configured on this server.) instead of a 500.
Restart the backend after any .env change.
3. Connect
/authorize also sets a short-lived cookie that the callback checks alongside the signed state, so the install has to be finished in the browser that started it, within 10 minutes — which means a frontend calling /authorize with credentials: "include", not a detached curl. The response headers of a curl call carry the cookie if you want to drive the flow by hand, but you then have to present it back to the callback yourself.
Approve the consent screen and Google redirects to /api/v1/integrations/google_drive/callback, which binds the account to your Cognee user and then sends the browser to <GOOGLE_DRIVE_FRONTEND_BASE_URL>/integrations?google_drive=<outcome>:
The authorize URL carries
access_type=offline and prompt=consent. Both are required for Google to issue a refresh token — without them an account that has consented before gets an access token only, and the connector goes dark about an hour after install with no way back short of reconnecting.
Choosing What to Index
A fresh connection selects nothing. It is stored with an empty folder selection, so no files are imported until you choose what to cover — connecting only grants authorization. Reconnecting an account that already has a selection keeps it; only a first-time connection starts empty. List what is selectable:My Drive (id root, which covers the account root including nested folders), every shared drive visible to the account, and every folder, paginated through in full. Then persist a selection:
/folders is a readable alias for /resources on both verbs. The selection is three-state:
A selection that is neither
null nor a list of non-empty strings fails the sync with Drive selection must be a list of folder IDs or null.
Syncing and Status
Connecting does not start a sync —on_installed is an explicit no-op, so the first pass happens when you ask for one:
root plus every shared drive when the selection is null), and each one is ingested through the ordinary remember() path with write_disposition="merge" on the file id, so re-syncing updates rows instead of duplicating them. It runs with self_improvement=False: whole-graph improve() enrichment carries LLM cost far too heavy to fire on every Drive refresh, so it stays a human or scheduled decision.
Only the first sync reads everything. It lists the selected folders in full and, before doing so, captures a Drive Changes API cursor; every later sync asks Drive only what changed since that cursor and carries just those files. The cursor lives in the per-folder ingestion state, and it is advanced only when the run recorded no failures — a file whose content could not be extracted is therefore retried on the next sync rather than lost behind a cursor that moved past it. A sync that finds no changes still re-reads the folder’s already-staged files, so if their processing failed, or the dataset was emptied locally, the next /sync restores them without anything changing in Drive.
Changes include deletions: a file deleted, trashed, or moved out of a selected folder is emitted as a tombstone and forgotten from memory on the next sync, graph and vector rows included. Drive’s Changes feed is account-wide and carries no metadata on deleted files, so an incremental run may also emit harmless no-op deletes for files removed elsewhere in the account.
{"accepted": true} means the request was taken, not that a sync started: while one is already in flight for that account the call returns the same body and does nothing, so a manual refresh can never interleave two runs over one cursor. That guard lives in the API process, so a deployment running more than one worker needs its own job queue or distributed lease to get the same protection.
Files land in one dataset per connected account, named google_drive_<email-slug>_<hash> — the email with every run of characters outside A-Z a-z 0-9 _ collapsed to _, trimmed of leading and trailing _ and lowercased (an email that slugifies to nothing becomes account), plus the first 10 hex characters of the SHA-256 of the Google account id. The hash suffix is what keeps two accounts whose emails slugify identically (a.b@x.com and a-b@x.com) in separate datasets. One dataset per account rather than one shared per Workspace domain is deliberate: Cognee’s permissions are dataset-scoped, so a shared dataset would let one colleague’s questions answer from another’s private files.
/sync again (or wire it to your own scheduler) to pick up changes.
What gets indexed
A selected folder is not read wholesale. Only file types the connector knows how to turn into text are ingested; everything else is skipped with a warning and counted underskipped_unsupported_type, which is the usual answer to “why isn’t this file in memory?”.
Two more files never make it in: anything larger than 25 MB (per Drive’s reported size, configurable with
GOOGLE_DRIVE_MAX_FILE_SIZE_MB) is skipped before it is read, and a file whose extracted text is empty is skipped as well. A file that fails to parse — a corrupt PDF, an export error — is logged and skipped rather than failing the folder, but it does count as a failure and marks the sync degraded.
Reading the connection state
GET /api/v1/integrations/status carries the same per-provider fields for every registered provider in one call — that is what powers an integrations page.
Token Lifecycle
Google access tokens last about an hour, so the adapter rotates them: an access token is refreshed in place 5 minutes before it expires, which keeps a long sync from dying halfway through its own file list. Google returns no new refresh token on a rotation, so the original is carried forward until the account revokes it. Two failure modes are worth knowing:- Google rejects the refresh with
invalid_grant. The account revoked Cognee’s access on Google’s side (myaccount.google.com/permissions), or the refresh token expired. Nothing else tells Cognee that — there are no webhooks on this path — so the local credential is revoked too, and the connection stops reading as connected instead of showing healthy forever while every sync fails. Reconnect to fix it. - The connection was minted without a refresh token. Google withholds it when a prior consent is reused, which is exactly what
prompt=consentexists to prevent. It is logged at connect time (connected without a refresh token) and can only be fixed by reconnecting.
Disconnect
delete_data=true the google_drive_… dataset stays exactly as it is; use forget() later if you change your mind. With it, the dataset is forgotten after the revoke, and a deletion that fails answers 502 (google_drive disconnected, but its data could not be deleted) — the disconnect itself has already happened at that point.
Limitations
- Only some file types are indexed, and large files are skipped. Google Docs, Sheets (first sheet), PDFs and plain text/Markdown/CSV are read; everything else is skipped, as is any file over 25 MB. See What gets indexed.
- No automatic refresh. No webhooks, no scheduler —
/syncis the only thing that updates memory. - No Workspace-wide install. One consent per user; domain-wide delegation is not supported.
- A Google account belongs to exactly one Cognee user. Connecting an account that another user already holds is refused (
error_already_connected) rather than silently reassigned — the original owner has to disconnect first. drive.readonlyis a restricted scope — public distribution needs Google’s OAuth verification.
Troubleshooting
Google shows redirect_uri_mismatch
Google shows redirect_uri_mismatch
GOOGLE_DRIVE_REDIRECT_URI and the Authorized redirect URI on the OAuth client differ. They must match character for character, scheme and trailing slash included.Redirected to ?google_drive=error_invalid_state
Redirected to ?google_drive=error_invalid_state
The install did not start from a freshly minted
/authorize URL, more than 10 minutes passed before approving it, GOOGLE_DRIVE_STATE_SECRET changed in between, or the install finished in a different browser than the one that called /authorize. On a split-domain deployment, check the cookie’s SameSite note in step 3. Mint a new URL and retry.Redirected to ?google_drive=error_exchange_failed
Redirected to ?google_drive=error_exchange_failed
The callback never 500s — it catches everything and redirects, and the traceback is in the backend log. Usual causes: mismatched client id/secret, a redirect URI that does not match the registered one, a missing
INTEGRATION_CREDENTIALS_KEY, or a userinfo response with no subject (Google userinfo response carries no subject).Redirected to ?google_drive=error_already_connected
Redirected to ?google_drive=error_already_connected
This Google account is bound to another Cognee user. Disconnect it there first.
Connected, but the dataset is empty
Connected, but the dataset is empty
Expected until you make a selection and call
/sync — connecting alone stores an empty selection and starts nothing. Check GET /resources for what is selected and GET /connection for syncStatus and lastSyncedAt. A /sync that answered {"accepted": true} while syncStatus was already syncing did nothing — one run per account at a time.syncStatus: "degraded"
syncStatus: "degraded"
Something failed; everything else was indexed. Read
syncCounts to tell which: failed_content_extraction counts individual unreadable files, logged as Skipping Drive file '<name>' (<id>): content extraction failed, while a whole folder that failed logs Google Drive sync failed for account <id> folder <id>. A single bad file is enough to mark the run degraded. Re-running /sync retries what failed — the changes cursor is not advanced past a failed run.syncStatus: "syncing" that never settles
syncStatus: "syncing" that never settles
The status is derived from an in-flight run, so it clears when the detached sync ends. If it persists across a restart, look for the sync’s traceback in the backend log.
Everything 401s about an hour after connecting
Everything 401s about an hour after connecting
The connection has no refresh token (see Token Lifecycle). Reconnect —
prompt=consent forces Google to issue one.The connection silently stops being connected
The connection silently stops being connected
An
invalid_grant on refresh revoked it locally, which means access was revoked at Google. The log line is Google Drive access for account <id> was revoked at the provider.A document is missing entirely
A document is missing entirely
Either its type is not supported or it is over the size limit — see What gets indexed. The backend log says which:
Skipping unsupported Drive file '<name>' (<id>): mime type '<type>'. or Skipping Drive file '<name>' (<id>): size exceeds max_file_size_mb=25.ImportError on the first sync
ImportError on the first sync
The
google-drive extra is not installed on the backend. pip install "cognee[google-drive]" and restart — OAuth works without it, so the connection itself looks healthy.Related
Linear
The other first-party OAuth connector, sharing the same credential store, state signing and connect flow.
GitHub
Install a GitHub App into an org and index its repositories into the code graph.