Coding QGIS Plug-ins with AI coding tools
Following up on my last post, I wanted to share some more details about the experience of using AI tools to code a plugin for QGIS, one that has seen some reasonable success, with over 2000 downloads in the past couple of months. My hope is to inspire others to make their own QGIS plugins and other geospatial tools, as I think more people doing AI-assisted coding has the potential to accelerate the momentum of the open source ecosystem.

Cursor & QGIS — awesome together :)
Can you really code a QGIS plug-in just using AI tools?
Before we dig in I want to give everyone who is not a coder some encouragement to jump in and try things out. The quick answer is yes! You can code a QGIS plug-in even if you’re not a software developer. I’m sure you’ve seen the videos of people building cool things with AI tools, but it can still be hard to actually dive into it. For me the most important thing is to have a real problem you’re trying to solve. I could never follow those tutorials about ‘making a e-commerce store’ since I just don’t care about making an e-commerce store. I could follow the instructions, and get a generic thing, but I wouldn’t actually learn much. But when I’m trying to solve something specific that I care about it becomes much easier, because I really want the result.
I’m guessing you have some interest in geospatial in general and likely QGIS specifically if you’re reading this post. So the top thing I’d encourage you to do is to think about something you’d like QGIS to do that it doesn’t do for you today. This could just be a common workflow that you do all the time, or it could be some cool new functionality you always wish it had. And if you want to start even easier than just think about some processing of files that doesn’t even use QGIS — doing a basic python program that processes geospatial data with GDAL/OGR or GeoPandas can be even easier than a QGIS plugin.
After I made the GeoParquet downloader QGIS plug-in I had one of the best developers I know reply with a post on bluesky:

I responded that 99.5% was AI-coded — so Matthias still has to believe me when I downplay my coding skills :) I don’t actually write the code, I just instruct the AI what I want it to do. I am learning more, and at this point I perhaps could write more of the code, but I’d rather just get it right the first time than try to memorize the syntax or struggle when I mistype something.
Now, I am not without any coding experience, and I’ll share my background a bit below, so I’m not yet ready to say ‘anyone can make a QGIS plugin’. I do think there is a decent chance that the process of breaking problems down and iterating through debugging is likely a skill I retain that may not come instantly to someone with no coding experience. But I do think if you have a problem you want to solve and you are resilient then you can just use the chat interface to teach you everything you need to know — you just need to keep asking and aim to really understand it. No matter what you’ll learn something, and if it doesn’t work out today I’m confident that it won’t be long until anyone who is motivated can do it.
Since I started writing this article I did get a great proof point on how easy it can be:

My Background
I wanted to share a bit more about my background, and how I have recently started programming again thanks to the power of AI tools. I started my career coding, serving as the first lead developer of GeoServer for a couple of years in 2002, learning a ton from a number of great early members of the GeoTools community — shout outs to Ian Schneider, Gabriel Roldan and Andrea Aime! But after less than two years I realized they were all better software engineers than me, so I (eventually) recruited all of them to work on GeoServer at OpenGeo, and I focused on community building (the fun part) and bringing in money to support the software (the less fun part, but a really essential one).
I found it really hard to do ‘both’, as coding was too fun and satisfying — I know of few other things you can get paid for where the day just flies by. So I cut myself from coding in order to figure out all the other things needed to turn a growing open source project into a successful ‘business’. It ended up being a lot of fun, and I learned a ton, but I did miss the act of creating software. After a few years, when I felt confident in things like ‘product management’, ‘business development’ and ‘managing’, I’d periodically try to code and it’d just be too frustrating. It’d take a couple hours just to get a few lines of barely working code down, as I’d first need to get my coding environment all set up, and then would struggle to remember basic syntax and would need to look up almost every call.
When ChatGPT came out it had been over 20 years since I’d seriously programmed, and I had under two years of total experience coding professionally. My first couple of attempts to use ChatGPT to code didn’t quite work, as it’d just get too much wrong to be worth it. But sometime around GPT-3 I wanted to explore Google Open Buildings & GeoParquet and had my first success. As long as I gave it small, constrained tasks to process the data with GDAL/OGR or GeoPandas it’d do amazing. 75% of the time it’d give me a perfect result, 20% it’d get a bug but you could feed it the error and in 2–3 iterations it’d fix it. And 5% of the time it’d get stuck in a loop, trying to fix things but going back to the previous bad way, which was frustrating. But overall it felt ‘worth it’ and I was able to make far faster progress using it than not. Since then I’ve been taking on more ambitious projects as the LLM’s and tooling around them has improved, and this year I’m aiming to spend at least 50% of my time doing AI-assisted software development.
My Set Up
Before I dig into my experiences building the plugin I first want to share a bit about what tools are working for me, and some ideas and recommendations for how to think about what to use. The first thing I will say is that if you’re coding you should absolutely pay instead of using the free versions. If you’re less experienced with coding then it’s harder to work through things when the LLM doesn’t get things right. And the latest models that you get from paying absolutely get things right more often. It’s hard to put a number on how much better they are, but my feeling is that even if it’s only 10–20% better then it can easily save you hours of frustration, and that having that frustration when you’re getting started can easily turn you off from pushing further. And in my experience it’s more than 20% better.
My primary tool for these types of projects is Cursor. I only discovered it after a number of months successfully coding with ChatGPT, but a number of great coders I work with said it was amazing, so I thought I’d give it a shot. I believe it’s the fastest I’ve gone from starting a free trial to deciding ‘yup, I’m absolutely going to pay for this’ — it was maybe 15–20 minutes. The key feature that moved me to buy is actually different than the one more advanced coders love. It’s the ability to generate ‘diffs’ on the code that is generated by the LLM.

Diffs in Cursor for the latest improvements for my plugin
You can see in the right side panel the code that gets generated by an LLM. It’s totally fine if you just take that code and use it, but if there’s a problem or if you want to add something to the code then it gets challenging to use the whole set of code. You either have to copy and paste the entire file each time, or you have to successfully spot every difference and copy over each line right. And in this example the LLM isn’t even generating the full file, you can see it says // ... existing code ...
so when that happens you’d need to figure out the right place to insert your new code. Before cursor I had that go wrong enough times that I’d ask the LLM to always generate the entire code, and then would paste the whole thing in each time. But then it’d get unweildly if you had a larger file, and also would slow things down since it’d spend a lot of time reprinting things.
This ability to apply diffs was an absolute game changer in my productivity — you can have confidence that every difference between the new code and the current code is addressed. Now, this isn’t to say that the diffs are always great. Sometimes it messes things up, or redoes things that you don’t want it to redo. But the UI of cursor is such that you can go through each of the diffs and say hit ‘y’ or ’n’ to accept it.
I often will scan each diff to make sure it’s doing what I thought it would. Though often I’ll just apply the whole thing, run the file, see the results, and then just go back to examine the diffs if things didn’t work.
The other killer feature of Cursor, that expert coders love, is the ability to give the LLM the context of your entire codebase, not just the file you’re looking at. The QGIS plugin I made is a single file, so for this project that feature doesn’t matter much, but for my geoparquet-tools project I’m trying to do some better programming practices of splitting things up, and it’s been great for that.
Cursor lets you select from different models, and it’s got access to most of the latest. It doesn’t have the most expensive ones, like o1, and you have some limit for how many calls you get to the latest. But you can enter an API key for your own LLM account if you want to have it use truly the latest models. I actually switched for this project to Claude Sonnet 3.5, and it’s now my go to within Cursor. I can’t say that it’s definitely better — it’s just that 4o was frustrating me and getting things wrong, and when I tried switching the model to Claude it nailed it, and I’ve just stuck with it since. The ‘best’ here is constantly changing, so it’s worth checking out leaderboards (like aider.chat or https://livebench.ai).
And I also will use ChatGPT Plus occasionally. I don’t think it’s essential — if you want to stick with one tool I’d go with Cursor (or Windsurf, which Evan found great success with and it may be even better for those with little experience coding). You can use it’s chat interface to ask questions, just like you would with ChatGPT — you don’t have to have it give you code responses, you can just ask for background, how to do things, etc. For coding stuff I tend to use the o1 model, and I mostly just use it for things were the other models just are looped in bad answers (more about that later). And it can be useful for planning things out and suggesting an overall approach to things.
The explosion of useful tools in this space is incredible, so I imagine there will be some new tool set that’s even better before too long. I plan to re-evaluate every six months or so, but encourage you to just try things out. Just do please pay for one, and you’ll likely have a better experience. Perhaps in a couple years maybe the free tools will be more than sufficient, but that’s not the case right now.
Coding the geoparquet downloader plugin
To be honest my successful coding of this plugin was a bit of a lark. As I mentioned in the previous post I’d been intimidated by QGIS coding — I think I looked into it once before and it was just too many things to learn. So I really wasn’t expecting it to work, as my AI successes in the past had all been on very discrete things, and when I tried to get too ambitious it’d get challenging. But I just asked it to make a plugin, and then asked how I run it to try it out, and it started working.
The key is to never ask for too much — always start small, and just ask for one more thing each time. Sometimes I’d try to ask for many things at once, and it’d get some of them, but then it was a lot more difficult to try to fix the one thing that was wrong, since too many things were introduced at once. So it’s best to just keep asking for tiny little improvements.
To start I just asked for a plugin that popped up a dialog to make sure that worked. I had to ask how to actually install it — you just zip it up and then you can ‘install zip file’ from the extensions folder, or you can copy it directly into the plugins folder. Then I just added more and more — first I hardcoded a specific Overture file to download, and got it so DuckDB ran the query to get it. I had experience with DuckDB so I knew what I wanted from the query, but LLM’s are really great at SQL, so it’s easy to have it form the query for you.
One thing I have learned is that the LLM’s do much, much better on well-established, well-documented tools. SQL is just incredible — I like SQL, but was never great at it, and now I feel like it’s a super power. I can just ask the LLM for all kinds of crazy analysis and it’ll make these complicated joins that do exactly what I want. GDAL/OGR and GeoPandas are both quite good. But often newer features aren’t as solid, as the LLM may have been trained before they became widely documented and used. But you can instruct it about them, usually I just paste the documentation in directly. So if you want to use some totally obscure tool then it can struggle — though always with a positive helpful and confident attitude, it just makes up whatever you want.
But I was pleasantly surprised to find the QGIS plugins is something they all know well. Which makes a lot of sense, as there is a very large ecosystem, so lots of code and documentation for it to learn from. Through out the process it’d come through with good answers to things that I thought it might struggle with. One example was that it become clear that my plugin was taking over the whole program, and QGIS would just stop doing anything else. So I just asked if there was a way to make it so that didn’t happen, and it said that I could run worker threads and then came up with all the code needed for that. I also wanted to open multiple dialogs, links you could click on, and reporting errors out in different ways, and it did all that well. Overall it handled most everything QGIS related with ease.

My plugin, now available to anyone directly through their QGIS!
I will admit that I definitely do not understand most of what’s going on. With my smaller python programs I’d always have a good sense of what was happening in most lines, but the structure of these is more complicated. But the cool thing is that you can just highlight code and ask it to explain it if you want to understand more.
The struggles
My biggest struggles came with getting the dependencies right. DuckDB is essential for everything to work, but it’s not installed in QGIS by default. The QDuckDB plugin did an amazing job packaging things for Windows, so for those users I recommended that they just install QDuckDB, and then I linked to their instructions for other users. For the next release we tried to make it so the plugin would automatically install DuckDB using PIP, and that was definitely my single biggest struggle. Matt Travis, the first outside contributor to the project, coded what looked like a great way to do it. But then it didn’t work on one operating system, so I tried another way, and that didn’t work on mine, and it was just a pain. I’m still not sure what is the ‘ideal’ route for QGIS plugins to get dependencies in, it seems to be that users are asked to manually get their python environments updated.
I would occasionally have the LLM get stuck in a frustrating loop. Usually it’d get the code right immediately, but about 15% of the time it’d generate an error. But the awesome thing is that usually you can just paste in the error that results back into the chat and it’ll realize what is wrong and fix it. But occasionally it’ll give you a fix that won’t work, and you paste the error back in and then its next fix will suggest something different that also doesn’t work. And when you paste those results back in it’ll give you the first fix again. And then it’ll just loop between the two things, getting it wrong each time. I’ve found the best thing to do in this situation is to try another LLM. Cursor makes it easy to just swap in a different model, going between Claude and OpenAI. These days I usually just jump to o1 through ChatGPT Plus, as it very often gets it right — I just paste in my full code and the problem (and right before publishing: o3-mini-high seems even better, and can be called directly from Cursor).
The other way to get out of the loop is to to suggest different approaches to the problem. Sometimes that means going back a few steps and directing it in a different way. Sometimes it means reading up online on other options that people use (I suppose you could also try to ask the LLM for other approaches, I think sometimes that has worked for me). Occasionally I will bug my coder friends, and the nice thing is that usually you just need the name of a different approach from them and the LLM will take it from there.
Come on in, the water’s fine!
So overall the experience of coding my first QGIS plugin was incredibly pleasant. It didn’t all happen ‘automatically’, we’re not (yet) there with these tools, and I suspect they’re most always going to require some guidance and iteration. But the challenges were all surmountable. I think the biggest thing these tools do is really make the learning curve a lot less steep. You’re still going to have to dig in and learn quite a bit, but you get much more immediate positive feedback. More recently I started another plugin and I will admit that it was more of a challenge, and I got seriously stuck for more than two hours, so I suspect there’s still some luck involved to have it be easy.
In the time between writing this and actually posting it I have hit bigger bugs than I have before — one took almost 4 hours to resolve, and the other was almost two hours. It reminded me of the frustrating parts of coding, and is proof that it’s all not magical. But it is so satisfying when you get past one of these bugs. I hope everyone finds early success without hitting frustrating bugs, but at some point you will hit frustrations, and I encourage you to be hard headed and just keep on trying until you get it working.
So I do want to encourage everyone who has read this far to at least try. Get a trial of Cursor or Windsurf and set some time aside. Be sure to start with a problem you actually want to solve, something that will make your life easier. And just keep trying even if at first you don’t succeed, it is currentlye easier than it’s ever been, and that will only improve. It doesn’t have to be QGIS, but it’s a nice platform to build upon. I’m also going to try to record some videos of building a plugin, to help demystify things even more.
If you do build something that’s useful to you then please share the code on GitHub and publish to the QGIS plugin repository. Chances are it might be useful to someone else. I know it can be scary, I still remember the first time my boss told me I needed to push my code to be open source. But honestly no one is looking at your code and judging it — they’re just psyched to have something potentially useful, and psyched that you contributed something positive to the world.
Good luck! And if you want to dive in but don’t have a project that immediatly jumps to mind I do welcome all AI-assisted contributions to my QGIS plugin. I tagged a number of ‘good first issues’ and can easily add more, and am more than happy to offer advice & help to anyone looking to contribute.

Also, if you’re not signed up already do come to the Cloud Native Geo conference in Utah. It’s going to be awesome.
Our blog is open source. You can suggest edits on GitHub.