[{"data":1,"prerenderedAt":1611},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fbest-ide-for-qgis-plugin-development":3},{"id":4,"title":5,"body":6,"description":1601,"extension":1602,"meta":1603,"navigation":598,"path":1607,"seo":1608,"stem":1609,"__hash__":1610},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fbest-ide-for-qgis-plugin-development\u002Findex.md","Best IDE for QGIS Plugin Development",{"type":7,"value":8,"toc":1586},"minimark",[9,13,30,39,44,47,102,107,216,220,223,489,533,537,557,564,846,858,862,865,883,950,960,1063,1078,1082,1085,1208,1215,1219,1222,1320,1324,1327,1363,1371,1375,1432,1451,1455,1469,1473,1482,1496,1502,1529,1539,1543,1582],[10,11,5],"h1",{"id":12},"best-ide-for-qgis-plugin-development",[14,15,16,17,21,22,25,26,29],"p",{},"The best IDE for QGIS plugin development is JetBrains PyCharm Professional, with Visual Studio Code as a highly capable lightweight alternative. PyCharm delivers the most reliable integrated debugger, accurate static analysis for the compiled ",[18,19,20],"code",{},"qgis.*"," modules, and seamless environment routing when it is pointed at the QGIS-bundled Python interpreter. VS Code matches most of that functionality through the Python\u002FPylance extensions and ",[18,23,24],{},"debugpy",", but it needs a manual ",[18,27,28],{},"launch.json"," to attach to a running QGIS process. Spyder rounds out the field for exploratory analysis but is a poor fit for packaging and shipping a plugin.",[14,31,32,33,38],{},"This page is a decision guide: it weighs the three editors against the tasks that actually matter for plugin work, then gives you the exact routing and debugger recipes for each. If you have already settled on JetBrains and just want it wired to QGIS correctly, jump straight to ",[34,35,37],"a",{"href":36},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002F","setting up PyCharm for QGIS",", the parent guide this article sits under.",[40,41,43],"h2",{"id":42},"prerequisites","Prerequisites",[14,45,46],{},"Before you can meaningfully compare editors, the same underlying components must be in place — the IDE is only a front end for the QGIS Python runtime.",[48,49,50,58,68,78,88],"ul",{},[51,52,53,57],"li",{},[54,55,56],"strong",{},"QGIS 3.44 LTR or 4.0+"," installed via OSGeo4W (Windows), the standalone installer, or your Linux package manager.",[51,59,60,63,64,67],{},[54,61,62],{},"A 64-bit Python"," — specifically the interpreter bundled with QGIS. QGIS is strictly 64-bit, so a 32-bit IDE interpreter will never load ",[18,65,66],{},"qgis._core",".",[51,69,70,73,74,77],{},[54,71,72],{},"The QGIS Python bindings resolving on the command line",": ",[18,75,76],{},"python3 -c \"import qgis.core\""," must run without error using the bundled interpreter.",[51,79,80,87],{},[54,81,82,83],{},"Working familiarity with the ",[34,84,86],{"href":85},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002F","QGIS Python Console",", since every debugger recipe below injects a bridge from code that runs inside QGIS.",[51,89,90,93,94,97,98,101],{},[54,91,92],{},"An editor installed",": PyCharm Professional 2023.2+, VS Code 1.85+ with ",[18,95,96],{},"ms-python.python"," and ",[18,99,100],{},"ms-python.debugpy",", or Spyder 5+.",[103,104,106],"h3",{"id":105},"quick-compatibility-matrix","Quick compatibility matrix",[108,109,110,126],"table",{},[111,112,113],"thead",{},[114,115,116,120,123],"tr",{},[117,118,119],"th",{},"Component",[117,121,122],{},"Requirement",[117,124,125],{},"Notes",[127,128,129,143,156,169,182,199],"tbody",{},[114,130,131,137,140],{},[132,133,134],"td",{},[54,135,136],{},"QGIS",[132,138,139],{},"3.44 LTR or 4.0+",[132,141,142],{},"Python bindings match the installer's bundled version",[114,144,145,150,153],{},[132,146,147],{},[54,148,149],{},"Python",[132,151,152],{},"3.12 (QGIS 3.34+, incl. 3.44 LTR)",[132,154,155],{},"Must exactly match QGIS's internal build; avoid conda\u002Fsystem Python",[114,157,158,163,166],{},[132,159,160],{},[54,161,162],{},"PyQt",[132,164,165],{},"PyQt5 (QGIS 3.x) \u002F PyQt6 (QGIS 4.x)",[132,167,168],{},"QGIS 4.0 moved to Qt6; plugins targeting both need compatible imports",[114,170,171,176,179],{},[132,172,173],{},[54,174,175],{},"PyCharm",[132,177,178],{},"Professional 2023.2+",[132,180,181],{},"Community edition lacks remote debugger attachment",[114,183,184,189,192],{},[132,185,186],{},[54,187,188],{},"VS Code",[132,190,191],{},"1.85+",[132,193,194,195,97,197],{},"Requires ",[18,196,96],{},[18,198,100],{},[114,200,201,206,209],{},[132,202,203],{},[54,204,205],{},"OS",[132,207,208],{},"Win\u002FmacOS\u002FLinux",[132,210,211,212,215],{},"Path separators and ",[18,213,214],{},"QGIS_PREFIX_PATH"," resolution differ",[40,217,219],{"id":218},"how-the-editors-compare-for-plugin-work","How the editors compare for plugin work",[14,221,222],{},"The choice comes down to five capabilities that separate a comfortable plugin workflow from a frustrating one: live process debugging, static analysis of the compiled bindings, Qt Designer integration, project tooling for packaging, and resource footprint.",[224,225,230,234,238,245,252,255,260,268,275,281,284,287,291,294,326,359,365,372,377,380,383,388,391,394,396,399,401,404,407,410,412,415,417,419,422,425,427,430,432,434,437,441,443,446,448,451,456,460,466,470,473,477,480,485],"svg",{"viewBox":226,"role":227,"ariaLabel":228,"xmlns":229},"0 0 760 410","img","Comparison matrix scoring PyCharm Professional, VS Code, and Spyder across five plugin-development capabilities","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[231,232,233],"title",{},"IDE comparison matrix for QGIS plugin development",[235,236,237],"desc",{},"PyCharm Professional earns full marks for live QGIS-process debugging, static analysis of the compiled qgis bindings, Qt Designer integration, and plugin packaging, but only weak marks on a light resource footprint. VS Code scores full on static analysis and footprint, partial on debugging, packaging and Qt Designer because they need manual configuration. Spyder scores partial on static analysis and footprint and weak on debugging, Qt Designer and packaging.",[239,240],"rect",{"x":241,"y":241,"width":242,"height":243,"fill":244},"0","760","410","#f6f3ea",[239,246],{"x":247,"y":248,"width":249,"height":250,"fill":251},"20","153","720","48","#efeadd",[239,253],{"x":247,"y":254,"width":249,"height":250,"fill":251},"249",[239,256],{"x":247,"y":257,"width":249,"height":258,"fill":259},"55","50","#26322d",[261,262,267],"text",{"x":263,"y":264,"style":265,"fill":266},"32","86","font-size:13px;font-weight:bold;font-family:sans-serif","#d9f99d","Capability",[261,269,274],{"x":270,"y":271,"style":272,"fill":244,"textAnchor":273},"323","80","text-anchor:middle;font-size:15px;font-weight:bold;font-family:sans-serif","middle","PyCharm Pro",[261,276,280],{"x":270,"y":277,"style":278,"fill":279,"textAnchor":273},"97","text-anchor:middle;font-size:11px;font-family:sans-serif","#a7b1ab","paid · heavy",[261,282,188],{"x":283,"y":271,"style":272,"fill":244,"textAnchor":273},"490",[261,285,286],{"x":283,"y":277,"style":278,"fill":279,"textAnchor":273},"free · light",[261,288,290],{"x":289,"y":271,"style":272,"fill":244,"textAnchor":273},"656","Spyder",[261,292,293],{"x":289,"y":277,"style":278,"fill":279,"textAnchor":273},"free · analysis",[295,296,299,304,306,309,311,314,317,320,323],"g",{"stroke":297,"style":298},"#d9d3c4","stroke-width:1",[300,301],"line",{"x1":247,"y1":302,"x2":303,"y2":302},"105","740",[300,305],{"x1":247,"y1":248,"x2":303,"y2":248},[300,307],{"x1":247,"y1":308,"x2":303,"y2":308},"201",[300,310],{"x1":247,"y1":254,"x2":303,"y2":254},[300,312],{"x1":247,"y1":313,"x2":303,"y2":313},"297",[300,315],{"x1":247,"y1":316,"x2":303,"y2":316},"345",[300,318],{"x1":319,"y1":257,"x2":319,"y2":316},"240",[300,321],{"x1":322,"y1":257,"x2":322,"y2":316},"407",[300,324],{"x1":325,"y1":257,"x2":325,"y2":316},"573",[295,327,330,335,339,343,347,351,355],{"fill":328,"style":329},"#17211d","font-size:12.5px;font-family:sans-serif",[261,331,334],{"x":263,"y":332,"fill":333},"133","#2f3b35","Live QGIS-process debugging",[261,336,338],{"x":263,"y":337,"fill":333},"173","Static analysis of the",[261,340,342],{"x":263,"y":341,"fill":333},"189","compiled qgis.* bindings",[261,344,346],{"x":263,"y":345,"fill":333},"229","Qt Designer integration",[261,348,350],{"x":263,"y":349,"fill":333},"269","Plugin packaging &",[261,352,354],{"x":263,"y":353,"fill":333},"285","project tooling",[261,356,358],{"x":263,"y":357,"fill":333},"325","Light resource footprint",[360,361],"circle",{"cx":270,"cy":362,"r":363,"fill":364},"129","12","#15803d",[366,367],"path",{"d":368,"fill":369,"stroke":370,"style":371},"M318 129 L321.5 133 L329 124","none","#ffffff","stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round",[360,373],{"cx":283,"cy":362,"r":363,"fill":374,"stroke":375,"style":376},"#fdf2e2","#b45309","stroke-width:2.2",[366,378],{"d":379,"fill":375},"M490 117 A12 12 0 0 1 490 141 Z",[360,381],{"cx":289,"cy":362,"r":363,"fill":369,"stroke":382,"style":376},"#5b6670",[300,384],{"x1":385,"y1":362,"x2":386,"y2":362,"stroke":382,"style":387},"650","662","stroke-width:2.2;stroke-linecap:round",[360,389],{"cx":270,"cy":390,"r":363,"fill":364},"177",[366,392],{"d":393,"fill":369,"stroke":370,"style":371},"M318 177 L321.5 181 L329 172",[360,395],{"cx":283,"cy":390,"r":363,"fill":364},[366,397],{"d":398,"fill":369,"stroke":370,"style":371},"M485 177 L488.5 181 L496 172",[360,400],{"cx":289,"cy":390,"r":363,"fill":374,"stroke":375,"style":376},[366,402],{"d":403,"fill":375},"M656 165 A12 12 0 0 1 656 189 Z",[360,405],{"cx":270,"cy":406,"r":363,"fill":364},"225",[366,408],{"d":409,"fill":369,"stroke":370,"style":371},"M318 225 L321.5 229 L329 220",[360,411],{"cx":283,"cy":406,"r":363,"fill":374,"stroke":375,"style":376},[366,413],{"d":414,"fill":375},"M490 213 A12 12 0 0 1 490 237 Z",[360,416],{"cx":289,"cy":406,"r":363,"fill":369,"stroke":382,"style":376},[300,418],{"x1":385,"y1":406,"x2":386,"y2":406,"stroke":382,"style":387},[360,420],{"cx":270,"cy":421,"r":363,"fill":364},"273",[366,423],{"d":424,"fill":369,"stroke":370,"style":371},"M318 273 L321.5 277 L329 268",[360,426],{"cx":283,"cy":421,"r":363,"fill":374,"stroke":375,"style":376},[366,428],{"d":429,"fill":375},"M490 261 A12 12 0 0 1 490 285 Z",[360,431],{"cx":289,"cy":421,"r":363,"fill":369,"stroke":382,"style":376},[300,433],{"x1":385,"y1":421,"x2":386,"y2":421,"stroke":382,"style":387},[360,435],{"cx":270,"cy":436,"r":363,"fill":369,"stroke":382,"style":376},"321",[300,438],{"x1":439,"y1":436,"x2":440,"y2":436,"stroke":382,"style":387},"317","329",[360,442],{"cx":283,"cy":436,"r":363,"fill":364},[366,444],{"d":445,"fill":369,"stroke":370,"style":371},"M485 321 L488.5 325 L496 312",[360,447],{"cx":289,"cy":436,"r":363,"fill":374,"stroke":375,"style":376},[366,449],{"d":450,"fill":375},"M656 309 A12 12 0 0 1 656 333 Z",[360,452],{"cx":453,"cy":454,"r":455,"fill":364},"42","378","9",[366,457],{"d":458,"fill":369,"stroke":370,"style":459},"M38 378 L41 381 L46.5 374","stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round",[261,461,465],{"x":462,"y":463,"style":464,"fill":333},"57","382","font-size:12px;font-family:sans-serif","Full support",[360,467],{"cx":468,"cy":454,"r":455,"fill":374,"stroke":375,"style":469},"196","stroke-width:2",[366,471],{"d":472,"fill":375},"M196 369 A9 9 0 0 1 196 387 Z",[261,474,476],{"x":475,"y":463,"style":464,"fill":333},"211","Partial — needs manual setup",[360,478],{"cx":479,"cy":454,"r":455,"fill":369,"stroke":382,"style":469},"470",[300,481],{"x1":482,"y1":454,"x2":483,"y2":454,"stroke":382,"style":484},"465.5","474.5","stroke-width:2;stroke-linecap:round",[261,486,488],{"x":487,"y":463,"style":464,"fill":333},"485","Weak or absent",[48,490,491,513,528],{},[51,492,493,496,497,500,501,504,505,508,509,512],{},[54,494,495],{},"PyCharm Professional"," wins on the integrated debugger. Its Python Debug Server is a guided run configuration, and ",[18,498,499],{},"pydevd_pycharm"," attaches to the live GUI thread so breakpoints fire inside a plugin action. Static analysis of ",[18,502,503],{},"qgis.core",", ",[18,506,507],{},"qgis.gui",", and ",[18,510,511],{},"qgis.analysis"," is the most accurate of the three because PyCharm indexes the bundled interpreter's docstrings thoroughly. The cost is a heavier memory footprint and a paid licence — the debugger attachment is Professional-only.",[51,514,515,517,518,520,521,523,524,527],{},[54,516,188],{}," matches PyCharm on IntelliSense (via Pylance) and attach-mode debugging (via ",[18,519,24],{},"), at a fraction of the RAM. The trade-off is configuration: you hand-write ",[18,522,28],{}," and a ",[18,525,526],{},"debugpy.listen(...)"," call rather than clicking through a run configuration. For developers already living in VS Code, this is the strongest free option.",[51,529,530,532],{},[54,531,290],{}," is excellent for console-driven data analysis but lacks polished remote-attach debugging and plugin-oriented project tooling. Keep it for ad-hoc spatial queries; reach for PyCharm or VS Code the moment you start packaging.",[40,534,536],{"id":535},"environment-routing-point-the-ide-at-qgiss-python","Environment routing: point the IDE at QGIS's Python",[14,538,539,540,504,542,544,545,548,549,552,553,556],{},"QGIS ships a self-contained Python environment. Any external IDE defaults to a global or virtual environment that has no compiled ",[18,541,503],{},[18,543,511],{},", or Qt bindings, so the first job in every editor is identical: route the interpreter at the QGIS-bundled ",[18,546,547],{},"python.exe"," (Windows\u002FOSGeo4W) or ",[18,550,551],{},"python3"," (macOS\u002FLinux). The mechanics differ per editor — the ",[34,554,555],{"href":36},"PyCharm setup guide"," walks the JetBrains dialog step by step — but the target path is the same.",[14,558,559,560,563],{},"When you cannot point the interpreter directly (for example when a shared linter runs against a system Python), inject the QGIS paths at runtime before any ",[18,561,562],{},"import qgis"," statement:",[565,566,571],"pre",{"className":567,"code":568,"language":569,"meta":570,"style":570},"language-python shiki shiki-themes github-dark","import sys\nimport os\n\n# Adjust to your QGIS installation root\nQGIS_PREFIX = os.environ.get(\"QGIS_PREFIX_PATH\", r\"C:\\OSGeo4W\\apps\\qgis-ltr\")\nPYTHON_DIR = os.path.join(QGIS_PREFIX, \"python\")\nPLUGINS_DIR = os.path.join(QGIS_PREFIX, \"python\", \"plugins\")\n\n# Prepend QGIS paths\nfor p in (PYTHON_DIR, PLUGINS_DIR):\n    if os.path.isdir(p) and p not in sys.path:\n        sys.path.insert(0, p)\n\n# Set mandatory runtime variables\nos.environ[\"QGIS_PREFIX_PATH\"] = QGIS_PREFIX\nos.environ[\"QT_QPA_PLATFORM_PLUGIN_PATH\"] = os.path.join(QGIS_PREFIX, \"plugins\", \"platforms\")\n\nfrom qgis.core import QgsApplication, QgsProject\n","python","",[18,572,573,585,593,600,607,660,680,703,708,714,738,761,772,777,783,800,827,832],{"__ignoreMap":570},[574,575,577,581],"span",{"class":300,"line":576},1,[574,578,580],{"class":579},"snl16","import",[574,582,584],{"class":583},"s95oV"," sys\n",[574,586,588,590],{"class":300,"line":587},2,[574,589,580],{"class":579},[574,591,592],{"class":583}," os\n",[574,594,596],{"class":300,"line":595},3,[574,597,599],{"emptyLinePlaceholder":598},true,"\n",[574,601,603],{"class":300,"line":602},4,[574,604,606],{"class":605},"sjoCn","# Adjust to your QGIS installation root\n",[574,608,610,614,617,620,624,626,629,632,636,640,643,646,649,652,655,657],{"class":300,"line":609},5,[574,611,613],{"class":612},"sDLfK","QGIS_PREFIX",[574,615,616],{"class":579}," =",[574,618,619],{"class":583}," os.environ.get(",[574,621,623],{"class":622},"sU2Wk","\"QGIS_PREFIX_PATH\"",[574,625,504],{"class":583},[574,627,628],{"class":579},"r",[574,630,631],{"class":622},"\"",[574,633,635],{"class":634},"sns5M","C:",[574,637,639],{"class":638},"sRjNt","\\O",[574,641,642],{"class":634},"SGeo4W",[574,644,645],{"class":638},"\\a",[574,647,648],{"class":634},"pps",[574,650,651],{"class":638},"\\q",[574,653,654],{"class":634},"gis-ltr",[574,656,631],{"class":622},[574,658,659],{"class":583},")\n",[574,661,663,666,668,671,673,675,678],{"class":300,"line":662},6,[574,664,665],{"class":612},"PYTHON_DIR",[574,667,616],{"class":579},[574,669,670],{"class":583}," os.path.join(",[574,672,613],{"class":612},[574,674,504],{"class":583},[574,676,677],{"class":622},"\"python\"",[574,679,659],{"class":583},[574,681,683,686,688,690,692,694,696,698,701],{"class":300,"line":682},7,[574,684,685],{"class":612},"PLUGINS_DIR",[574,687,616],{"class":579},[574,689,670],{"class":583},[574,691,613],{"class":612},[574,693,504],{"class":583},[574,695,677],{"class":622},[574,697,504],{"class":583},[574,699,700],{"class":622},"\"plugins\"",[574,702,659],{"class":583},[574,704,706],{"class":300,"line":705},8,[574,707,599],{"emptyLinePlaceholder":598},[574,709,711],{"class":300,"line":710},9,[574,712,713],{"class":605},"# Prepend QGIS paths\n",[574,715,717,720,723,726,729,731,733,735],{"class":300,"line":716},10,[574,718,719],{"class":579},"for",[574,721,722],{"class":583}," p ",[574,724,725],{"class":579},"in",[574,727,728],{"class":583}," (",[574,730,665],{"class":612},[574,732,504],{"class":583},[574,734,685],{"class":612},[574,736,737],{"class":583},"):\n",[574,739,741,744,747,750,752,755,758],{"class":300,"line":740},11,[574,742,743],{"class":579},"    if",[574,745,746],{"class":583}," os.path.isdir(p) ",[574,748,749],{"class":579},"and",[574,751,722],{"class":583},[574,753,754],{"class":579},"not",[574,756,757],{"class":579}," in",[574,759,760],{"class":583}," sys.path:\n",[574,762,764,767,769],{"class":300,"line":763},12,[574,765,766],{"class":583},"        sys.path.insert(",[574,768,241],{"class":612},[574,770,771],{"class":583},", p)\n",[574,773,775],{"class":300,"line":774},13,[574,776,599],{"emptyLinePlaceholder":598},[574,778,780],{"class":300,"line":779},14,[574,781,782],{"class":605},"# Set mandatory runtime variables\n",[574,784,786,789,791,794,797],{"class":300,"line":785},15,[574,787,788],{"class":583},"os.environ[",[574,790,623],{"class":622},[574,792,793],{"class":583},"] ",[574,795,796],{"class":579},"=",[574,798,799],{"class":612}," QGIS_PREFIX\n",[574,801,803,805,808,810,812,814,816,818,820,822,825],{"class":300,"line":802},16,[574,804,788],{"class":583},[574,806,807],{"class":622},"\"QT_QPA_PLATFORM_PLUGIN_PATH\"",[574,809,793],{"class":583},[574,811,796],{"class":579},[574,813,670],{"class":583},[574,815,613],{"class":612},[574,817,504],{"class":583},[574,819,700],{"class":622},[574,821,504],{"class":583},[574,823,824],{"class":622},"\"platforms\"",[574,826,659],{"class":583},[574,828,830],{"class":300,"line":829},17,[574,831,599],{"emptyLinePlaceholder":598},[574,833,835,838,841,843],{"class":300,"line":834},18,[574,836,837],{"class":579},"from",[574,839,840],{"class":583}," qgis.core ",[574,842,580],{"class":579},[574,844,845],{"class":583}," QgsApplication, QgsProject\n",[14,847,848,849,853,854,67],{},"If your plugin depends on third-party packages that must not pollute the bundled interpreter, isolate them in a dedicated ",[34,850,852],{"href":851},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002F","virtual environment for GIS work"," — but keep the QGIS core modules resolving to the QGIS-provided Python to avoid ABI mismatches. For running finished tools headlessly, the same routing underpins ",[34,855,857],{"href":856},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","running Python scripts outside QGIS Desktop",[40,859,861],{"id":860},"attach-the-debugger-in-each-ide","Attach the debugger in each IDE",[14,863,864],{},"Live debugging is where editor choice pays off most, so here are the two recipes side by side.",[14,866,867,870,871,874,875,878,879,882],{},[54,868,869],{},"PyCharm Professional."," Create a ",[18,872,873],{},"Python Debug Server"," run configuration (default ",[18,876,877],{},"localhost:5678",") and start it so PyCharm listens. Then, from your plugin's ",[18,880,881],{},"__init__.py",", a target function, or the QGIS Python Console, install the bridge and trigger the plugin action:",[565,884,886],{"className":567,"code":885,"language":569,"meta":570,"style":570},"# pip install pydevd-pycharm~=\u003Cyour PyCharm build number> into the QGIS interpreter first\nimport pydevd_pycharm\npydevd_pycharm.settrace('localhost', port=5678, stdoutToServer=True, stderrToServer=True, suspend=False)\n",[18,887,888,893,900],{"__ignoreMap":570},[574,889,890],{"class":300,"line":576},[574,891,892],{"class":605},"# pip install pydevd-pycharm~=\u003Cyour PyCharm build number> into the QGIS interpreter first\n",[574,894,895,897],{"class":300,"line":587},[574,896,580],{"class":579},[574,898,899],{"class":583}," pydevd_pycharm\n",[574,901,902,905,908,910,914,916,919,921,924,926,929,931,934,936,938,940,943,945,948],{"class":300,"line":595},[574,903,904],{"class":583},"pydevd_pycharm.settrace(",[574,906,907],{"class":622},"'localhost'",[574,909,504],{"class":583},[574,911,913],{"class":912},"s9osk","port",[574,915,796],{"class":579},[574,917,918],{"class":612},"5678",[574,920,504],{"class":583},[574,922,923],{"class":912},"stdoutToServer",[574,925,796],{"class":579},[574,927,928],{"class":612},"True",[574,930,504],{"class":583},[574,932,933],{"class":912},"stderrToServer",[574,935,796],{"class":579},[574,937,928],{"class":612},[574,939,504],{"class":583},[574,941,942],{"class":912},"suspend",[574,944,796],{"class":579},[574,946,947],{"class":612},"False",[574,949,659],{"class":583},[14,951,952,955,956,959],{},[54,953,954],{},"VS Code."," Add an attach configuration to ",[18,957,958],{},".vscode\u002Flaunch.json",":",[565,961,965],{"className":962,"code":963,"language":964,"meta":570,"style":570},"language-json shiki shiki-themes github-dark","{\n    \"name\": \"Attach to QGIS\",\n    \"type\": \"python\",\n    \"request\": \"attach\",\n    \"connect\": { \"host\": \"localhost\", \"port\": 5678 },\n    \"justMyCode\": false,\n    \"subProcess\": true\n}\n","json",[18,966,967,972,985,996,1008,1036,1048,1058],{"__ignoreMap":570},[574,968,969],{"class":300,"line":576},[574,970,971],{"class":583},"{\n",[574,973,974,977,979,982],{"class":300,"line":587},[574,975,976],{"class":612},"    \"name\"",[574,978,73],{"class":583},[574,980,981],{"class":622},"\"Attach to QGIS\"",[574,983,984],{"class":583},",\n",[574,986,987,990,992,994],{"class":300,"line":595},[574,988,989],{"class":612},"    \"type\"",[574,991,73],{"class":583},[574,993,677],{"class":622},[574,995,984],{"class":583},[574,997,998,1001,1003,1006],{"class":300,"line":602},[574,999,1000],{"class":612},"    \"request\"",[574,1002,73],{"class":583},[574,1004,1005],{"class":622},"\"attach\"",[574,1007,984],{"class":583},[574,1009,1010,1013,1016,1019,1021,1024,1026,1029,1031,1033],{"class":300,"line":609},[574,1011,1012],{"class":612},"    \"connect\"",[574,1014,1015],{"class":583},": { ",[574,1017,1018],{"class":612},"\"host\"",[574,1020,73],{"class":583},[574,1022,1023],{"class":622},"\"localhost\"",[574,1025,504],{"class":583},[574,1027,1028],{"class":612},"\"port\"",[574,1030,73],{"class":583},[574,1032,918],{"class":612},[574,1034,1035],{"class":583}," },\n",[574,1037,1038,1041,1043,1046],{"class":300,"line":662},[574,1039,1040],{"class":612},"    \"justMyCode\"",[574,1042,73],{"class":583},[574,1044,1045],{"class":612},"false",[574,1047,984],{"class":583},[574,1049,1050,1053,1055],{"class":300,"line":682},[574,1051,1052],{"class":612},"    \"subProcess\"",[574,1054,73],{"class":583},[574,1056,1057],{"class":612},"true\n",[574,1059,1060],{"class":300,"line":705},[574,1061,1062],{"class":583},"}\n",[14,1064,1065,1066,1069,1070,1073,1074,67],{},"Insert ",[18,1067,1068],{},"import debugpy; debugpy.listen((\"localhost\", 5678)); debugpy.wait_for_client()"," in your plugin entry point, start the VS Code debugger, then launch QGIS. In both editors, breakpoints halt execution against the actual QGIS GUI thread so you can inspect ",[18,1071,1072],{},"QgsFeature"," attributes, geometry objects, and signal payloads in real time. For logging strategies and post-mortem tactics that complement the attach workflow, see ",[34,1075,1077],{"href":1076},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002F","debugging PyQGIS scripts",[40,1079,1081],{"id":1080},"two-debugging-models","Two debugging models",[14,1083,1084],{},"Every editor attaches to a running QGIS in one of two ways, and the difference decides how much setup each one needs.",[14,1086,1087],{},[224,1088,1091,1094,1097,1100,1116,1122,1132,1138,1146,1151,1154,1158,1163,1167,1171,1174,1178,1181,1185,1190,1193,1195,1198,1202,1205],{"viewBox":1089,"role":227,"ariaLabel":1090,"xmlns":229},"0 0 760 244","Attach-style debugging where the IDE connects to a running QGIS, compared with listen-style debugging where the plugin connects out to a waiting IDE",[231,1092,1093],{},"Attach to QGIS, or have QGIS connect out",[235,1095,1096],{},"In the attach model the IDE finds the running QGIS process and injects its debugger, which requires process-attach permission and matching architectures. In the listen model the IDE opens a port and waits, and a line inside the plugin connects out to it, which works across containers and remote machines.",[239,1098],{"x":241,"y":241,"width":242,"height":1099,"fill":244},"244",[1101,1102,1103],"defs",{},[1104,1105,1112],"marker",{"id":1106,"viewBox":1107,"refX":1108,"refY":1109,"markerWidth":1110,"markerHeight":1110,"orient":1111},"ideDbgArrow","0 0 10 10","8","5","7","auto-start-reverse",[366,1113],{"d":1114,"fill":1115},"M0 0 L10 5 L0 10 z","#0f766e",[261,1117,1121],{"x":1118,"y":1119,"style":1120,"fill":328,"textAnchor":273},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","Which end initiates the connection",[239,1123],{"x":1124,"y":1125,"width":1126,"height":1127,"rx":1128,"fill":1129,"stroke":1130,"style":1131},"16","46","356","182","10","#fffdf7","#2563eb","stroke-width:2.5",[261,1133,1137],{"x":1134,"y":1135,"style":1136,"fill":1130,"textAnchor":273},"194","70","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","attach to process",[239,1139],{"x":1140,"y":1141,"width":1142,"height":1143,"rx":1108,"fill":1129,"stroke":1144,"style":1145},"44","92","120","56","#59645f","stroke-width:1.8",[261,1147,1150],{"x":1148,"y":1149,"style":278,"fill":333,"textAnchor":273},"104","125","the IDE",[239,1152],{"x":1153,"y":1141,"width":1142,"height":1143,"rx":1108,"fill":259,"stroke":1115,"style":469},"224",[261,1155,1157],{"x":1156,"y":1149,"style":278,"fill":266,"textAnchor":273},"284","running QGIS",[300,1159],{"x1":1160,"y1":1142,"x2":1161,"y2":1142,"stroke":1130,"style":1162},"164","220","stroke-width:2.5;marker-end:url(#ideDbgArrow)",[261,1164,1166],{"x":1134,"y":1165,"style":278,"fill":333,"textAnchor":273},"176","no code change in the plugin",[261,1168,1170],{"x":1134,"y":1169,"style":278,"fill":375,"textAnchor":273},"198","needs local process access",[239,1172],{"x":1173,"y":1125,"width":1126,"height":1127,"rx":1128,"fill":1129,"stroke":364,"style":1131},"388",[261,1175,1177],{"x":1176,"y":1135,"style":1136,"fill":364,"textAnchor":273},"566","listen for a connection",[239,1179],{"x":1180,"y":1141,"width":1142,"height":1143,"rx":1108,"fill":1129,"stroke":1144,"style":1145},"416",[261,1182,1150],{"x":1183,"y":1184,"style":278,"fill":333,"textAnchor":273},"476","118",[261,1186,1189],{"x":1183,"y":1187,"style":1188,"fill":1144,"textAnchor":273},"136","text-anchor:middle;font-size:10px;font-family:monospace","port 5678",[239,1191],{"x":1192,"y":1141,"width":1142,"height":1143,"rx":1108,"fill":259,"stroke":1115,"style":469},"596",[261,1194,1157],{"x":289,"y":1184,"style":278,"fill":266,"textAnchor":273},[261,1196,1197],{"x":289,"y":1187,"style":1188,"fill":266,"textAnchor":273},"settrace(...)",[300,1199],{"x1":1200,"y1":1142,"x2":1201,"y2":1142,"stroke":364,"style":1162},"592","540",[261,1203,1204],{"x":1176,"y":1165,"style":278,"fill":333,"textAnchor":273},"one line added to the plugin",[261,1206,1207],{"x":1176,"y":1169,"style":278,"fill":364,"textAnchor":273},"works across containers and hosts",[14,1209,1210,1211,1214],{},"The listen model is the more portable of the two and the one worth learning first: it is the only option when QGIS runs in a container or on another machine, and the single ",[18,1212,1213],{},"settrace"," line can be guarded by an environment variable so it never reaches production.",[40,1216,1218],{"id":1217},"what-actually-differs-between-editors","What actually differs between editors",[14,1220,1221],{},"Once the interpreter and the debugger are configured, the day-to-day differences narrow to a few things that genuinely affect plugin work.",[14,1223,1224],{},[224,1225,1228,1231,1234,1237,1240,1246,1252,1257,1261,1265],{"viewBox":1226,"role":227,"ariaLabel":1227,"xmlns":229},"0 0 760 236","A comparison of three editors across PyQGIS autocompletion, Qt Designer integration, debugger setup effort and resource use",[231,1229,1230],{},"Where the editors actually diverge for plugin work",[235,1232,1233],{},"Four rows compare a full IDE, a lightweight editor and a plain text editor. Autocompletion against the QGIS bindings is strong in the full IDE, good in the lightweight editor with a stubs package, and absent in the plain editor. Qt Designer integration, debugger setup effort and memory use follow the same ordering in reverse.",[239,1235],{"x":241,"y":241,"width":242,"height":1236,"fill":244},"236",[261,1238,1239],{"x":1118,"y":1119,"style":1120,"fill":328,"textAnchor":273},"Four things that matter once the setup is done",[300,1241],{"x1":1242,"y1":1243,"x2":1244,"y2":1243,"stroke":1144,"style":1245},"248","66","744","stroke-width:1.5",[261,1247,1251],{"x":1236,"y":1248,"style":1249,"fill":328,"textAnchor":1250},"58","text-anchor:end;font-size:11.5px;font-weight:bold;font-family:sans-serif","end","aspect",[261,1253,1256],{"x":1254,"y":1248,"style":1255,"fill":1115,"textAnchor":273},"348","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","full IDE",[261,1258,1260],{"x":1259,"y":1248,"style":1255,"fill":1130,"textAnchor":273},"520","lightweight",[261,1262,1264],{"x":1263,"y":1248,"style":1255,"fill":1144,"textAnchor":273},"676","plain editor",[295,1266,1268,1273,1276,1279,1281,1285,1288,1291,1294,1298,1301,1304,1307,1311,1314,1317],{"style":1267},"font-size:11.5px;font-family:sans-serif",[261,1269,1272],{"x":1236,"y":1270,"style":1271,"fill":333,"textAnchor":1250},"98","text-anchor:end","PyQGIS autocompletion",[261,1274,54],{"x":1254,"y":1270,"style":1275,"fill":1115,"textAnchor":273},"text-anchor:middle",[261,1277,1278],{"x":1259,"y":1270,"style":1275,"fill":1130,"textAnchor":273},"good, with stubs",[261,1280,369],{"x":1263,"y":1270,"style":1275,"fill":1144,"textAnchor":273},[261,1282,1284],{"x":1236,"y":1283,"style":1271,"fill":333,"textAnchor":1250},"132","Qt Designer launch",[261,1286,1287],{"x":1254,"y":1283,"style":1275,"fill":1115,"textAnchor":273},"built in",[261,1289,1290],{"x":1259,"y":1283,"style":1275,"fill":1130,"textAnchor":273},"an extension",[261,1292,1293],{"x":1263,"y":1283,"style":1275,"fill":1144,"textAnchor":273},"separate app",[261,1295,1297],{"x":1236,"y":1296,"style":1271,"fill":333,"textAnchor":1250},"166","debugger setup",[261,1299,1300],{"x":1254,"y":1296,"style":1275,"fill":1115,"textAnchor":273},"a dialog",[261,1302,1303],{"x":1259,"y":1296,"style":1275,"fill":1130,"textAnchor":273},"a JSON file",[261,1305,1306],{"x":1263,"y":1296,"style":1275,"fill":1144,"textAnchor":273},"print statements",[261,1308,1310],{"x":1236,"y":1309,"style":1271,"fill":333,"textAnchor":1250},"200","memory footprint",[261,1312,1313],{"x":1254,"y":1309,"style":1275,"fill":375,"textAnchor":273},"large",[261,1315,1316],{"x":1259,"y":1309,"style":1275,"fill":1130,"textAnchor":273},"moderate",[261,1318,1319],{"x":1263,"y":1309,"style":1275,"fill":364,"textAnchor":273},"tiny",[40,1321,1323],{"id":1322},"qgis-version-compatibility-notes","QGIS-version compatibility notes",[14,1325,1326],{},"The single detail that breaks reproducible plugin code is the Qt\u002FPyQt boundary, so pin your examples to a known QGIS release.",[48,1328,1329,1347,1353],{},[51,1330,1331,1334,1335,1338,1339,1342,1343,1346],{},[54,1332,1333],{},"QGIS 3.x (including the 3.44 LTR) builds against PyQt5","; ",[54,1336,1337],{},"QGIS 4.0 moves to PyQt6",". A plugin that imports ",[18,1340,1341],{},"PyQt5.QtWidgets"," directly will not load under a Qt6 QGIS, and vice versa. Where you need to support both, import through the ",[18,1344,1345],{},"qgis.PyQt"," compatibility shim rather than the concrete binding.",[51,1348,1349,1352],{},[54,1350,1351],{},"The bundled interpreter tracks the QGIS release"," — QGIS 3.34+ ships Python 3.12, older 3.28 builds shipped 3.9. Point every editor at the interpreter that matches the QGIS you are targeting, not the newest Python on the machine.",[51,1354,1355,1362],{},[54,1356,1357,1358,1361],{},"The ",[18,1359,1360],{},"pydevd-pycharm"," egg is versioned to your PyCharm build."," A mismatched egg fails the handshake silently, so re-pin it whenever you update the IDE.",[14,1364,1365,1366,1370],{},"Before committing example code to a repository, cross-check the ",[34,1367,1369],{"href":1368},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fqgis-python-version-compatibility-guide\u002F","QGIS Python version compatibility guide"," so your interpreter, bindings, and imports all line up.",[40,1372,1374],{"id":1373},"troubleshooting","Troubleshooting",[48,1376,1377,1389,1413,1423],{},[51,1378,1379,1382,1383,1385,1386,67],{},[54,1380,1381],{},"Architecture mismatch."," QGIS is strictly 64-bit; a 32-bit IDE interpreter fails to load ",[18,1384,66],{},". Verify with ",[18,1387,1388],{},"python -c \"import platform; print(platform.architecture())\"",[51,1390,1391,1398,1399,1402,1403,1406,1407,97,1409,1412],{},[54,1392,1393,1394,1397],{},"Use ",[18,1395,1396],{},".pth"," files for static resolution."," Drop a ",[18,1400,1401],{},"qgis_paths.pth"," file into your IDE's ",[18,1404,1405],{},"site-packages"," containing absolute paths to QGIS's ",[18,1408,569],{},[18,1410,1411],{},"plugins"," directories. This forces static resolution without editing plugin source.",[51,1414,1415,1418,1419,1422],{},[54,1416,1417],{},"Isolate profile conflicts."," Launch QGIS with a clean profile — ",[18,1420,1421],{},"qgis --profile-name debug"," — because a corrupted user profile often masks IDE configuration errors.",[51,1424,1425,1428,1429,67],{},[54,1426,1427],{},"Fall back to the QGIS message log."," If a firewall blocks the debugger port, route diagnostics to QGIS's built-in log instead: ",[18,1430,1431],{},"from qgis.core import QgsMessageLog, Qgis; QgsMessageLog.logMessage(\"Debug: state\", \"MyPlugin\", Qgis.Warning)",[14,1433,1434,1435,1438,1439,1443,1444,504,1447,1450],{},"If the ",[18,1436,1437],{},"qgis"," import itself keeps failing whatever you try, work through the ordered checklist in ",[34,1440,1442],{"href":1441},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Ffixing-pyqgis-module-import-errors\u002F","fixing PyQGIS module import errors",", which traces exactly how ",[18,1445,1446],{},"sys.path",[18,1448,1449],{},"PYTHONPATH",", and the bundled site-packages are consulted.",[40,1452,1454],{"id":1453},"conclusion","Conclusion",[14,1456,1457,1458,1460,1461,1463,1464,1468],{},"For serious QGIS plugin development, PyCharm Professional is the most productive editor thanks to its guided remote debugger and deep static analysis of the compiled bindings; VS Code is the best free alternative once you have written the ",[18,1459,28],{},"; and Spyder stays useful only for exploratory analysis. Whichever you pick, the decisive factor is not the editor's feature list but environment alignment — the interpreter, Qt bindings, and debugger ports all pointing at the same QGIS runtime. Get that right and you gain full static analysis, step-through execution against the live GUI thread, and reproducible example code. With the editor chosen, the natural next step is wiring it up in detail through ",[34,1462,37],{"href":36}," and grounding your work in the wider ",[34,1465,1467],{"href":1466},"\u002Fpyqgis-fundamentals-environment-setup\u002F","PyQGIS Fundamentals & Environment Setup"," hub.",[40,1470,1472],{"id":1471},"frequently-asked-questions","Frequently Asked Questions",[14,1474,1475,1478,1479,1481],{},[54,1476,1477],{},"Is PyCharm Community Edition enough for QGIS plugin development?","\nCommunity Edition handles interpreter routing, static analysis, and local script execution, so it works for most scripting and library code. It does not include the remote debugger attachment that connects to a running QGIS process, which is the key gap for live plugin debugging. If you need to step through plugin actions inside QGIS, the Professional edition or VS Code with ",[18,1480,24],{}," is the better fit.",[14,1483,1484,1487,1488,97,1490,1492,1493,1495],{},[54,1485,1486],{},"How does VS Code compare to PyCharm for PyQGIS work?","\nVS Code matches most of PyCharm's functionality through the ",[18,1489,96],{},[18,1491,100],{}," extensions, including IntelliSense and attach-mode debugging. The main trade-off is that VS Code requires a manual ",[18,1494,28],{}," for process attachment, whereas PyCharm exposes the debug server as a guided run configuration. For lightweight setups and developers already in the VS Code ecosystem, it is a strong choice.",[14,1497,1498,1501],{},[54,1499,1500],{},"Can I use Spyder for QGIS plugin development?","\nSpyder is comfortable for exploratory, console-style data analysis but is not well suited to building and debugging full plugins. It lacks the polished remote-attach debugging and the plugin-oriented project tooling that PyCharm and VS Code provide. Most developers keep Spyder for ad-hoc analysis and reach for PyCharm or VS Code when packaging a plugin.",[14,1503,1504,1510,1511,1513,1514,1517,1518,1521,1522,1525,1526,67],{},[54,1505,1506,1507,1509],{},"Why does my IDE fail to import ",[18,1508,66],{}," even after pointing at the right interpreter?","\nThe most common cause is an architecture mismatch: QGIS is strictly 64-bit, so a 32-bit interpreter cannot load the compiled module. Verify with ",[18,1512,1388],{},". If the architecture is correct, ensure no system ",[18,1515,1516],{},"PyQt5","\u002F",[18,1519,1520],{},"PyQt6"," shadows the QGIS Qt bindings and that the QGIS ",[18,1523,1524],{},"bin"," directory is on ",[18,1527,1528],{},"PATH",[14,1530,1531,1534,1535,1538],{},[54,1532,1533],{},"What should I do if a firewall blocks the debugger port?","\nRoute diagnostic output to QGIS's built-in log instead of the debugger, using ",[18,1536,1537],{},"QgsMessageLog.logMessage(...)"," with a custom tag so you can filter your messages. This gives you a reliable observation channel without any open port. Once you can run on a network that permits the debug port, switch back to full step-through debugging for deeper inspection.",[40,1540,1542],{"id":1541},"related","Related",[48,1544,1545,1555,1560,1566,1576],{},[51,1546,1547,1550,1551,1554],{},[54,1548,1549],{},"Up:"," ",[34,1552,1553],{"href":36},"Setting Up PyCharm for QGIS Development"," — the parent guide this comparison sits under.",[51,1556,1557,1559],{},[34,1558,1467],{"href":1466}," — the hub for every environment and setup topic.",[51,1561,1562,1565],{},[34,1563,1564],{"href":85},"QGIS Python Console Basics"," — the interactive workflow every IDE builds on.",[51,1567,1568,1571,1572,1575],{},[34,1569,1570],{"href":1076},"Debugging PyQGIS Scripts"," — logging, breakpoints, and the ",[18,1573,1574],{},"pydevd"," bridge in depth.",[51,1577,1578,1581],{},[34,1579,1580],{"href":856},"Running Python Scripts Outside QGIS Desktop"," — take a finished plugin's logic headless.",[1583,1584,1585],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sns5M, html code.shiki .sns5M{--shiki-default:#DBEDFF}html pre.shiki code .sRjNt, html code.shiki .sRjNt{--shiki-default:#85E89D;--shiki-default-font-weight:bold}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":570,"searchDepth":587,"depth":587,"links":1587},[1588,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600],{"id":42,"depth":587,"text":43,"children":1589},[1590],{"id":105,"depth":595,"text":106},{"id":218,"depth":587,"text":219},{"id":535,"depth":587,"text":536},{"id":860,"depth":587,"text":861},{"id":1080,"depth":587,"text":1081},{"id":1217,"depth":587,"text":1218},{"id":1322,"depth":587,"text":1323},{"id":1373,"depth":587,"text":1374},{"id":1453,"depth":587,"text":1454},{"id":1471,"depth":587,"text":1472},{"id":1541,"depth":587,"text":1542},"Compare IDEs for QGIS plugin development: PyCharm, VS Code, and Spyder. Find the best setup for PyQGIS debugging, Qt Designer integration, and code completion.","md",{"slug":12,"type":1604,"breadcrumb":5,"datePublished":1605,"dateModified":1606},"article","2025-02-18","2026-07-18","\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fbest-ide-for-qgis-plugin-development",{"title":5,"description":1601},"pyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fbest-ide-for-qgis-plugin-development\u002Findex","dUQrAOzPQtyovWZ-ypFwgKAp_lpSoHwkyiWfctuh2SM",1787823360565]