{"slug": "i-connected-contentful-to-cognee-importing-content-was-the-easy-part", "title": "I connected Contentful to Cognee. Importing content was the easy part.", "summary": "A developer built a Contentful connector for Cognee that imports published content into the AI memory system and keeps it synchronized, including handling deletions that timestamp-based syncing misses. The connector uses Contentful's native Sync API to capture DeletedEntry and DeletedAsset events, converting them into dlt hard-delete markers so Cognee can purge obsolete documents along with their graph and vector data. It also imports content models separately for field context, tracks per-source sync state via a source_id, and refuses to save a checkpoint unless every pagination page is processed, with a local verification report recording 94 passing tests.", "body_md": "*Built for the WeMakeDevs × Cognee Mergetober 2026.*\n\nImagine running an online store where hundreds of products are managed in Contentful.\n\nYou want an AI assistant that can answer questions about those products using Cognee's memory.\n\nGetting the content into Cognee is one problem. Keeping that memory correct when someone edits or deletes a product is another.\n\nSay a product's warranty changes from two years to one. Or the product gets discontinued entirely.\n\nIf Cognee still remembers the old information, your assistant could confidently give customers the wrong answer.\n\nThat was the problem behind [Cognee issue #4787](https://github.com/topoteretes/cognee/issues/4787).\n\nI built a Contentful connector that imports published content into Cognee, keeps it synchronized, and removes outdated information when the original content disappears.\n\nThe connector lives in `cognee-community/packages/connector/contentful/`.\n\nIt does five things:\n\nContent models are especially useful here.\n\nSuppose an entry has a field called `batteryLife` with the value `12`.\n\nTwelve what? Hours? Days?\n\nThe content model provides the field's structure and description. Without that context, an AI system has less information to interpret the value correctly.\n\nSo the connector imports content models separately instead of just dumping entry values into documents.\n\n*One synchronization run. Entries and assets use the Sync API, while content models are fetched separately.*\n\nThe obvious approach to synchronization is simple.\n\nFetch everything once, then use `sys.updatedAt` to find recently edited entries.\n\nThat works for updates.\n\nBut what about deletions?\n\nA deleted entry won't appear in the normal listing anymore. A timestamp filter alone can't reliably tell Cognee which information it should forget.\n\nThat's why I used Contentful's native **Sync API**.\n\nThe first run imports the available content and saves a checkpoint. Later runs use that checkpoint to retrieve changes.\n\nMore importantly, Contentful also returns deletion events like `DeletedEntry` and `DeletedAsset`.\n\nThe connector turns those events into dlt hard-delete markers, allowing Cognee to clean up obsolete documents and their associated graph and vector data.\n\nThere's another detail I didn't want to get wrong: pagination.\n\nIf Contentful returns five pages and page three fails, the connector must not save the final checkpoint.\n\nOtherwise, the next synchronization could skip changes it never processed.\n\nSo it follows every page before accepting the terminal sync token. If the response is incomplete, the run fails instead of silently losing information.\n\nHere's another situation.\n\nSuppose one integration imports English product descriptions, while another imports articles in multiple languages.\n\nChanging the first integration shouldn't touch the second.\n\nI added a `source_id` to give each logical source its own synchronization state.\n\nChanging filters under the same source ID triggers a selection refresh. The connector removes documents that no longer belong to that selection while preserving other sources.\n\nCredentials are also kept separate from source identity.\n\nRotating an API token shouldn't mean importing the entire collection as a new source.\n\nA successful API request doesn't mean the whole synchronization succeeded.\n\nThink about this sequence:\n\nNow what?\n\nIf the connector only trusts the latest API response, that earlier failed work could remain unfinished.\n\nThe implementation relies on Cognee's retained dlt staging so a later run can reconcile existing records again, even when the provider returns an empty delta.\n\nI also covered individual cleanup failures that Cognee logs without raising an exception.\n\nThat's the distinction that matters here: **successfully loading a change isn't the same as successfully updating AI memory.**\n\nI wanted to test more than whether the connector could parse some JSON.\n\nThe important questions were:\n\nThe October 8 local verification report recorded **94 passing tests**.\n\n| Tests | Coverage | \n|---|---|\n| 87 | Contentful provider behavior, synchronization, and dlt | \n| 7 | Real local Cognee ingestion, storage, deletion, and recovery | \n\nThe same 94 tests passed on Python 3.11, 3.12, and 3.13, plus another Python 3.12 run with the newest permitted dependencies.\n\nEight relevant upstream Cognee regression tests also passed, along with Ruff checks and package builds.\n\n*Local test evidence. Contentful responses and model operations were mocked; SQLite, Ladybug, and LanceDB storage paths were exercised for real.*\n\nThat distinction is important.\n\nThese tests provide evidence of local correctness, including deletion from relational, graph, and vector storage. They don't yet prove authenticated Contentful lifecycle behavior.\n\nThe implementation is available in my [Contentful connector branch](https://github.com/ANAMASGARD/cognee-community/tree/feat/4787-contentful-connector/packages/connector/contentful).\n\nFrom the connector directory:\n\n```\nuv sync --locked\n```\n\nConfigure your Contentful space and Delivery token:\n\n```\nexport CONTENTFUL_SPACE_ID=\"your-space-id\"\nexport CONTENTFUL_DELIVERY_TOKEN=\"your-token\"\nexport CONTENTFUL_ENVIRONMENT_ID=\"master\"\nexport LLM_API_KEY=\"your-model-key\"\n```\n\nThen run:\n\n```\nuv run python examples/example.py\n```\n\nThe example synchronizes published content into Cognee and runs a search query.\n\nYou can select specific content types and languages, or disable asset ingestion. Subsequent runs synchronize changes for the same source.\n\nFor example, a product-catalog application could import descriptions, search for products matching a customer's requirements, and refresh its memory after catalog edits.\n\nThat's an illustrative use case. A live Contentful-to-Cognee demonstration is still needed to establish real-provider behavior.\n\nA few limitations worth mentioning:\n\nAn invalid synchronization checkpoint also requires deliberate recovery rather than an automatic destructive reset.\n\nImporting data once is straightforward.\n\nKeeping it correct when the source changes, an entry disappears, or processing fails halfway through is the harder problem.\n\nThe useful lesson from this connector is that synchronization needs to account for both sides: what the source says changed, and what the destination actually finished processing.\n\nOtherwise, everything can appear to work while the AI is still remembering yesterday's information.\n\n**Code and references**\n\n*Built as part of WeMakeDevs × Cognee Mergetober 2026.*", "url": "https://wpnews.pro/news/i-connected-contentful-to-cognee-importing-content-was-the-easy-part", "canonical_source": "https://dev.to/anamasgard/i-connected-contentful-to-cognee-importing-content-was-the-easy-part-387k", "published_at": "2026-10-08 16:41:59+00:00", "updated_at": "2026-10-08 16:49:35.617130+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "mlops", "ai-infrastructure"], "entities": ["Contentful", "Cognee", "dlt", "WeMakeDevs", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-connected-contentful-to-cognee-importing-content-was-the-easy-part", "markdown": "https://wpnews.pro/news/i-connected-contentful-to-cognee-importing-content-was-the-easy-part.md", "text": "https://wpnews.pro/news/i-connected-contentful-to-cognee-importing-content-was-the-easy-part.txt", "jsonld": "https://wpnews.pro/news/i-connected-contentful-to-cognee-importing-content-was-the-easy-part.jsonld"}}