Skip to main content
Connect one Google account — personal or Workspace — to Cognee through an ordinary per-user OAuth consent, pick the folders and shared drives you want covered, and have their files become searchable memory in a dataset of that account’s own.
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.
Unreleased. The connector landed after 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 as account_type: workspace or personal.
  • 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-drive extra installed on that backend:
    It pulls in google-api-python-client, google-auth and google-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 an ImportError.
  • 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:8000 is 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.
drive.readonly is a restricted scope. Google requires OAuth verification (including a security assessment) before an app using it can be published to the public. Test users work without verification, which is enough for an internal or single-team deployment.

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.
A split-domain deployment needs the cookie loosened. That cookie is SameSite=Lax, which requires your API and your app to share a registrable domain. Where they do not, it has to be SameSite=None; Secure instead — otherwise every connect ends in error_invalid_state, logged as google_drive callback arrived without a matching install nonce; refusing.

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:
The list is 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.
Narrowing a selection deletes data. The next sync after you drop a folder from resourceIds retires what that folder put in the dataset — the rows go through an explicit replacement and Cognee’s ordinary orphan cleanup purges them from the graph, vector and relational stores. Setting resourceIds to [] retires everything the account has indexed. That reconciliation is skipped whenever the sync recorded a failure, so a transient error never takes data with it, and re-selecting a folder re-indexes it from scratch.

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:
The sync runs detached — the response returns immediately, it does not carry the result. It fans out over the selected folders (or 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.
Because this connector receives no webhooks and Cognee runs no durable scheduler, nothing refreshes it on its own — call /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 under skipped_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.
connected: true does not mean healthy. A connection whose last sync failed or came back partial still reads as connected, so check syncStatus too — otherwise a half-empty memory looks exactly like a working one.

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=consent exists to prevent. It is logged at connect time (connected without a refresh token) and can only be fixed by reconnecting.

Disconnect

Disconnecting marks the stored credential revoked — that local revoke is the actual access cut-off — and best-effort asks Google to kill the token on its own side. Best-effort by design: a network blip must never block a disconnect. Because Drive’s grant was kept separate from any other Google product’s, that remote revoke cannot take a sibling Google connector down with it. Without 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 — /sync is 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.readonly is a restricted scope — public distribution needs Google’s OAuth verification.

Troubleshooting

One of GOOGLE_DRIVE_CLIENT_ID, GOOGLE_DRIVE_REDIRECT_URI or GOOGLE_DRIVE_STATE_SECRET is unset. The backend log names the exact variable. A missing GOOGLE_DRIVE_FRONTEND_BASE_URL surfaces later, as the same 503 raised from the callback instead — the browser is already on your domain by then, so there is nowhere valid left to redirect it.
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.
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.
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).
This Google account is bound to another Cognee user. Disconnect it there first.
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.
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.
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.
The connection has no refresh token (see Token Lifecycle). Reconnect — prompt=consent forces Google to issue one.
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.
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.
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.

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.