{"record":{"id":"136301fc90712962","repo":"dgtlmoon/changedetection.io","slug":"processor-processor-name-does-not-provide-diff","errorCode":null,"errorMessage":"Processor '{processor_name}' does not provide difference data","messagePattern":"Processor '(.+?)' does not provide difference data","errorType":"http","errorClass":null,"httpStatus":404,"severity":"error","filePath":"changedetectionio/blueprint/ui/diff.py","lineNumber":364,"sourceCode":"        if the watch's processor doesn't implement get_data().\n        \"\"\"\n        from flask import jsonify, abort\n\n        if uuid == 'first':\n            uuid = list(datastore.data['watching'].keys()).pop()\n        try:\n            watch = datastore.data['watching'][uuid]\n        except KeyError:\n            return jsonify({'error': 'Watch not found'}), 404\n\n        processor_name = watch.get('processor', 'text_json_diff')\n        from changedetectionio.processors import get_processor_submodule\n        processor_module = get_processor_submodule(processor_name, 'difference')\n\n        if processor_module and hasattr(processor_module, 'get_data'):\n            return jsonify(processor_module.get_data(watch=watch, datastore=datastore, request=request))\n\n        abort(404, description=f\"Processor '{processor_name}' does not provide difference data\")\n\n    @diff_blueprint.route(\"/diff/<uuid_str:uuid>/processor-export.xlsx\", methods=['GET'])\n    @login_optionally_required\n    def diff_history_page_processor_export(uuid):\n        \"\"\"\n        Download the processor's history as an .xlsx (e.g. the restock price/stock timeline).\n        Processor-aware: delegates to processors/{type}/difference.py::export_xlsx(), which\n        returns (bytes, filename). 404 if the processor doesn't implement it.\n        \"\"\"\n        from flask import make_response, abort\n\n        if uuid == 'first':\n            uuid = list(datastore.data['watching'].keys()).pop()\n        try:\n            watch = datastore.data['watching'][uuid]\n        except KeyError:\n            flash(gettext(\"No history found for the specified link, bad link?\"), \"error\")\n            return redirect(url_for('watchlist.index'))","sourceCodeStart":346,"sourceCodeEnd":382,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/blueprint/ui/diff.py#L346-L382","documentation":"abort(404) in the diff blueprint when the requested watch processor either has no 'difference' submodule or that submodule does not expose a get_data() function. changedetection.io delegates processor-specific difference views (e.g. text vs restock/stock timelines) to per-processor submodules; requesting difference data from a processor that implements none yields this 404.","triggerScenarios":"GET /diff/<uuid>/processor-data?processor=<name> where <name> is a processor without a difference submodule/get_data (e.g. a processor that only renders extracted data), or an unknown/misspelled processor name (get_processor_submodule returns None).","commonSituations":"Custom or third-party processors that implement rendering but not the difference data API; UI links carried over from another watch type after the processor was changed; typos in the processor query parameter.","solutions":["Check changedetectionio/processors/<name>/difference.py exists and defines get_data(watch, datastore, request) for your processor","Verify the processor name in the URL matches an installed processor (text_monitor, restock etc.)","If you maintain a custom processor, add a difference submodule exposing get_data"],"exampleFix":"# before\nabort(404, description=f\"Processor '{processor_name}' does not provide difference data\")\n\n# after (custom processor)\n# changedetectionio/processors/my_proc/difference.py\ndef get_data(watch, datastore, request):\n    return {'snapshot_count': len(watch.history)}","handlingStrategy":"validation","validationCode":"from changedetectionio.processors import get_processor_submodule\ndef processor_has_diff_data(name):\n    m = get_processor_submodule(name, 'difference')\n    return m is not None and hasattr(m, 'get_data')","typeGuard":"def supports_diff_data(processor_name: str) -> bool:\n    \"\"\"True if the processor exposes difference data.\"\"\"\n    m = get_processor_submodule(processor_name, 'difference')\n    return bool(m and hasattr(m, 'get_data'))","tryCatchPattern":"from flask import abort\ntry:\n    return jsonify(processor_module.get_data(watch=watch, datastore=datastore, request=request))\nexcept NotFound:\n    return 'difference data unavailable', 404","preventionTips":["Check processor capabilities before building UI links","Version-lock custom processors against the expected submodule API","Log the processor name on 404 to catch typos early"],"tags":["flask","processor-plugin","http-404","changedetectionio"],"backgroundTag":"plugin-capability-missing","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}