---
title: Google Docs
description: Mount Google Docs as JSON-backed files so Python agents can read document structure and content through Mirage.
icon: file-lines
---
The Google Docs resource exposes Docs documents as a virtual filesystem
mounted at some prefix such as `/gdocs/`.

For Google OAuth setup, see [Google Workspace Setup](/python/setup/google).

## Config

```python
import os

from mirage import MountMode, Workspace
from mirage.resource.gdocs import GDocsConfig, GDocsResource

config = GDocsConfig(
    client_id=os.environ["GOOGLE_CLIENT_ID"],
    client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
    refresh_token=os.environ["GOOGLE_REFRESH_TOKEN"],
)
resource = GDocsResource(config=config)
ws = Workspace({"/gdocs": resource}, mode=MountMode.READ)
```

## Filesystem Layout

```text
/gdocs/
  owned/
    <YYYY-MM-DD>_<sanitized-title>__<doc-id>.gdoc.json
    ...
  shared/
    <YYYY-MM-DD>_<sanitized-title>__<doc-id>.gdoc.json
    ...
```

Example:

```text
/gdocs/
  owned/
    2026-04-04_Project_Plan__1AbCdEf.gdoc.json
    2026-04-10_Meeting_Notes__2BcDeFg.gdoc.json
  shared/
    2026-04-03_Design_Notes__9XyZ.gdoc.json
```

Documents are split into `owned` (documents you created) and `shared`
(documents shared with you). The filename shape is:

```text
<YYYY-MM-DD>_<sanitized-title>__<document-id>.gdoc.json
```

If the modified date is unavailable, the date prefix is omitted.
Reading a document file returns the full Google Docs API JSON for
that document.

### Shared Drives

The listing covers every corpus the account can reach, so a document that
lives in a Shared Drive appears here too. A Shared Drive document has no
owner (the drive owns it), so it lands under `shared`, which is what that
directory means. This matches the `gdrive` mount, where Shared Drives are
top-level directories: without it the same account would see a document
under one Google mount and not the other, with no error explaining the
difference.

Drive answers an all-corpora search best-effort. When it reports that it
skipped a corpus, the short listing is still returned but is not cached as
the directory, so the next `ls` re-lists instead of serving the gap until
the cache expires.

## Cache

The Google Docs resource uses `IndexCacheStore`. Index entries store
document IDs and metadata. There is no separate content cache -- file
content caching is handled by the workspace `IOResult` mechanism.

## Example

```python
import asyncio
import os

from dotenv import load_dotenv

from mirage import MountMode, Workspace
from mirage.commands.cli.builtin.gws import GWS
from mirage.resource.gdocs import GDocsConfig, GDocsResource

load_dotenv(".env.development")

config = GDocsConfig(
    client_id=os.environ["GOOGLE_CLIENT_ID"],
    client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
    refresh_token=os.environ["GOOGLE_REFRESH_TOKEN"],
)
resource = GDocsResource(config=config)


async def main():
    ws = Workspace({"/gdocs": resource}, mode=MountMode.READ)
    ws.register_cli("gws", GWS, config.model_dump())

    # List structure
    r = await ws.execute("ls /gdocs/")
    print(await r.stdout_str())

    # List owned documents
    r = await ws.execute("ls /gdocs/owned/")
    print(await r.stdout_str())

    # Read a document
    r = await ws.execute(
        "cat /gdocs/owned/2026-04-04_Project_Plan__1AbCdEf.gdoc.json")
    print(await r.stdout_str())

    # Extract title with jq
    r = await ws.execute(
        'jq ".title"'
        " /gdocs/owned/2026-04-04_Project_Plan__1AbCdEf.gdoc.json")
    print(await r.stdout_str())

    # Search across all documents
    r = await ws.execute('rg "quarterly" /gdocs/owned/')
    print(await r.stdout_str())

    # Tree view
    r = await ws.execute("tree -L 1 /gdocs/")
    print(await r.stdout_str())

    # Create a new document
    r = await ws.execute(
        'gws docs documents create --json \'{"title":"MIRAGE Example Doc"}\'')
    print(await r.stdout_str())

    # Append text to a document
    r = await ws.execute(
        'gws docs write --document 1AbCdEf --text "Appended via MIRAGE."')
    print(await r.stdout_str())


if __name__ == "__main__":
    asyncio.run(main())
```

See `examples/gdocs/gdocs.py` for the full working example.

## Shell Commands

Standard commands available on the mounted Google Docs tree:

| Command                             | Notes                       |
| ----------------------------------- | --------------------------- |
| `ls`                                | List owned/shared documents |
| `cat`                               | Read document JSON          |
| `head` / `tail`                     | First/last N lines          |
| `grep` / `rg`                       | Pattern search              |
| `jq`                                | Query JSON fields           |
| `wc`                                | Line/word/byte counts       |
| `stat`                              | File metadata               |
| `find`                              | Recursive search            |
| `tree`                              | Directory tree view         |
| `basename` / `dirname` / `realpath` | Path utilities              |
| `nl`                                | Number lines                |

Acting on documents (appending text, raw API calls) goes through
the [gws CLI](/python/cli/gws) when installed.
