
Qgis Impl Web Services
- 8 installs
- 29 repo stars
- Updated July 8, 2026
- openaec-foundation/qgis-claude-skill-package
Helps with ai & agent building tasks.
About
qgis-impl-web-services is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- qgis-impl-web-services
- AI & Agent Building
- AI-coding skill
Qgis Impl Web Services by the numbers
- 8 all-time installs (skills.sh)
- Ranked #12,339 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/qgis-claude-skill-package --skill qgis-impl-web-servicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 29 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/qgis-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
qgis-impl-web-services
Quick Reference
OGC Client Layer Types
| Service | Provider | Layer Class | URI Style |
|---|---|---|---|
| WMS | "wms" | QgsRasterLayer | Key-value params |
| WMTS | "wms" | QgsRasterLayer | Key-value params + tilematrixset |
| WFS | "WFS" | QgsVectorLayer | Full URL with query params |
| WCS | "wcs" | QgsRasterLayer | Key-value params |
| XYZ Tiles | "wms" | QgsRasterLayer | type=xyz&url=... |
Exact URI Format Strings
WMS:
crs=EPSG:4326&format=image/png&layers=layername&styles=&url=https://server/wmsWMTS:
crs=EPSG:3857&format=image/png&layers=layername&styles=default&tilematrixset=GoogleMapsCompatible&url=https://server/wmtsWFS:
https://server/wfs?service=WFS&version=2.0.0&request=GetFeature&typename=namespace:layernameWCS:
url=https://server/wcs&identifier=coveragename&crs=EPSG:4326XYZ Tiles:
type=xyz&url=https://tile.server.com/%7Bz%7D/%7Bx%7D/%7By%7D.png&zmax=19&zmin=0&crs=EPSG3857Server Filter Registration
| Filter Type | Base Class | Registration Method |
|---|---|---|
| I/O Filter | QgsServerFilter | serverIface.registerFilter(filter, priority) |
| Access Control | QgsAccessControlFilter | serverIface.registerAccessControl(filter, priority) |
| Cache | QgsServerCacheFilter | serverIface.registerServerCache(filter, priority) |
Priority: lower number = invoked first.
---
Critical Warnings
NEVER assume a WMS layer supports all CRS -- ALWAYS check the GetCapabilities response before requesting a specific CRS.
ALWAYS verify layer validity immediately after construction with layer.isValid(). An invalid WMS/WFS layer silently fails with no exception.
ALWAYS URL-encode curly braces in XYZ tile URLs when building the URI parameter string: {z} becomes %7Bz%7D, {x} becomes %7Bx%7D, {y} becomes %7By%7D.
NEVER hardcode credentials in URI strings. ALWAYS use QgsAuthManager with stored authentication configurations (authcfg parameter).
NEVER use QGIS Server classes from multiple threads -- they are NOT thread-safe. ALWAYS use multiprocessing or container-based scaling.
ALWAYS set server=True in metadata.txt for server plugins -- without it, QGIS Server will NOT load the plugin.
ALWAYS use serverClassFactory() (not classFactory()) as the entry point for server plugins.
ALWAYS check filter callback return values -- returning False from onRequestReady(), onSendResponse(), or onResponseComplete() stops filter chain propagation.
---
Decision Tree
Which OGC client to use?
Need map imagery (rendered tiles/images)?
├── YES: Need pre-rendered tiles?
│ ├── YES: Is the server OGC WMTS?
│ │ ├── YES → Use WMTS (provider "wms" + tilematrixset param)
│ │ └── NO → Use XYZ Tiles (provider "wms" + type=xyz)
│ └── NO → Use WMS (provider "wms")
└── NO: Need vector features (geometry + attributes)?
├── YES → Use WFS (provider "WFS")
└── NO: Need raster coverage data (raw values)?
└── YES → Use WCS (provider "wcs")Which WFS version?
Need paging or temporal filters?
├── YES → WFS 2.0.0 (uses typeNames, plural)
└── NO: Need stored queries?
├── YES → WFS 1.1.0
└── NO → WFS 1.0.0 (uses typeName, singular)Server plugin filter type?
Need to control layer/feature/attribute visibility?
├── YES → QgsAccessControlFilter
└── NO: Need to modify request/response content?
├── YES → QgsServerFilter
└── NO: Need to cache responses?
└── YES → QgsServerCacheFilter---
Essential Patterns
Loading a WMS Layer
from qgis.core import QgsRasterLayer, QgsProject
url_with_params = (
"crs=EPSG:4326"
"&format=image/png"
"&layers=continents"
"&styles="
"&url=https://demo.mapserver.org/cgi-bin/wms"
)
rlayer = QgsRasterLayer(url_with_params, "WMS Layer", "wms")
if rlayer.isValid():
QgsProject.instance().addMapLayer(rlayer)Loading a WMTS Layer
from qgis.core import QgsRasterLayer, QgsProject
url_with_params = (
"crs=EPSG:3857"
"&format=image/png"
"&layers=layername"
"&styles=default"
"&tilematrixset=GoogleMapsCompatible"
"&url=https://server/wmts"
)
rlayer = QgsRasterLayer(url_with_params, "WMTS Layer", "wms")
if rlayer.isValid():
QgsProject.instance().addMapLayer(rlayer)Loading a WFS Layer
from qgis.core import QgsVectorLayer, QgsProject
uri = (
"https://demo.mapserver.org/cgi-bin/wfs"
"?service=WFS"
"&version=2.0.0"
"&request=GetFeature"
"&typename=ms:cities"
)
vlayer = QgsVectorLayer(uri, "WFS Cities", "WFS")
if vlayer.isValid():
QgsProject.instance().addMapLayer(vlayer)Loading XYZ Tiles
from qgis.core import QgsRasterLayer, QgsProject
xyz_url = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
uri = f"type=xyz&url={xyz_url}&zmax=19&zmin=0&crs=EPSG3857"
rlayer = QgsRasterLayer(uri, "OpenStreetMap", "wms")
if rlayer.isValid():
QgsProject.instance().addMapLayer(rlayer)Loading a WCS Layer
from qgis.core import QgsRasterLayer, QgsProject
layer_name = "modis"
url = f"https://demo.mapserver.org/cgi-bin/wcs?identifier={layer_name}"
rlayer = QgsRasterLayer(url, "WCS Coverage", "wcs")
if rlayer.isValid():
QgsProject.instance().addMapLayer(rlayer)Authenticated WMS with QgsDataSourceUri
from qgis.core import QgsDataSourceUri, QgsRasterLayer, QgsProject
auth_cfg = "fm1s770" # Stored auth config ID from QgsAuthManager
quri = QgsDataSourceUri()
quri.setParam("layers", "usa:states")
quri.setParam("format", "image/png")
quri.setParam("authcfg", auth_cfg)
quri.setParam("url", "https://server/wms")
quri.setParam("crs", "EPSG:4326")
quri.setParam("styles", "")
rlayer = QgsRasterLayer(
str(quri.encodedUri(), "utf-8"), "Authenticated WMS", "wms"
)
if rlayer.isValid():
QgsProject.instance().addMapLayer(rlayer)Storing Authentication Config
from qgis.core import QgsAuthMethodConfig, QgsApplication
config = QgsAuthMethodConfig()
config.setName("My WMS Auth")
config.setMethod("Basic")
config.setUri("https://server/wms")
config.setConfig("username", "myuser")
config.setConfig("password", "mypassword")
auth_mgr = QgsApplication.authManager()
auth_mgr.storeAuthenticationConfig(config)
auth_id = config.id() # Auto-generated ID like "fm1s770"Supported auth methods: Basic, PKI-Paths, PKI-PKCS#12, Identity-Cert, OAuth2, APIHeader, MapTilerHmacSha256.
---
Common Operations
QGIS Server: Handling a Request
from qgis.core import QgsApplication
from qgis.server import QgsServer, QgsBufferServerRequest, QgsBufferServerResponse
app = QgsApplication([], False)
server = QgsServer()
request = QgsBufferServerRequest(
"http://localhost:8081/?SERVICE=WMS&REQUEST=GetCapabilities"
"&MAP=/path/to/project.qgs"
)
response = QgsBufferServerResponse()
server.handleRequest(request, response)
print(response.headers())
print(response.body().data().decode("utf8"))
app.exitQgis()Server Plugin Structure
$QGIS_PLUGINPATH/
MyServerPlugin/
__init__.py # MUST contain serverClassFactory()
MyServerPlugin.py # Plugin implementation
metadata.txt # MUST contain server=True__init__.py:
def serverClassFactory(serverIface):
from .MyServerPlugin import MyServerPluginServer
return MyServerPluginServer(serverIface)Plugin class with filter registration:
class MyServerPluginServer:
def __init__(self, serverIface):
serverIface.registerFilter(MyFilter(serverIface), 100)I/O Filter (QgsServerFilter)
from qgis.server import QgsServerFilter
class HelloFilter(QgsServerFilter):
def __init__(self, serverIface):
super().__init__(serverIface)
def onRequestReady(self) -> bool:
request = self.serverInterface().requestHandler()
params = request.parameterMap()
return True # Return False to stop filter chain
def onSendResponse(self) -> bool:
return True
def onResponseComplete(self) -> bool:
request = self.serverInterface().requestHandler()
params = request.parameterMap()
if params.get("SERVICE", "").upper() == "HELLO":
request.clear()
request.setResponseHeader("Content-type", "text/plain")
request.appendBody(b"HelloServer!")
return TrueAccess Control Filter
from qgis.server import QgsAccessControlFilter
class MyAccessControl(QgsAccessControlFilter):
def layerFilterExpression(self, layer):
return '"public" = true'
def layerFilterSubsetString(self, layer):
return "public = true"
def layerPermissions(self, layer):
rights = QgsAccessControlFilter.LayerPermissions()
rights.canRead = True
rights.canInsert = False
rights.canUpdate = False
rights.canDelete = False
return rights
def authorizedLayerAttributes(self, layer, attributes):
return [a for a in attributes if a != "secret_field"]
def allowToEdit(self, layer, feature):
return False
def cacheKey(self):
return "my_access_control"OGC API Handler
from qgis.PyQt.QtCore import QRegularExpression
from qgis.server import (
QgsServerOgcApi, QgsServerOgcApiHandler,
QgsServerQueryStringParameter,
)
class MyApiHandler(QgsServerOgcApiHandler):
def __init__(self):
super().__init__()
self.setContentTypes([QgsServerOgcApi.HTML, QgsServerOgcApi.JSON])
def path(self):
return QRegularExpression("/myapi")
def operationId(self):
return "MyApiEndpoint"
def summary(self):
return "My custom endpoint"
def description(self):
return "My custom endpoint description"
def linkTitle(self):
return "My API"
def linkType(self):
return QgsServerOgcApi.data
def handleRequest(self, context):
values = self.values(context)
self.write({"result": "ok"}, context)
def templatePath(self, context):
return ""
def parameters(self, context):
return [
QgsServerQueryStringParameter(
"param1", True,
QgsServerQueryStringParameter.Type.String,
"A required parameter",
),
]Registration:
class MyApiPlugin:
def __init__(self, serverIface):
api = QgsServerOgcApi(
serverIface, "/myapi", "My API",
"Custom API endpoint", "1.0"
)
api.registerHandler(MyApiHandler())
serverIface.serviceRegistry().registerApi(api)Custom OGC Service
from qgis.server import QgsService
class CustomService(QgsService):
def name(self):
return "CUSTOM"
def version(self):
return "1.0.0"
def executeRequest(self, request, response, project):
response.setStatusCode(200)
response.write("Custom service response")
# Register: serverIface.serviceRegistry().registerService(CustomService())Key Server Environment Variables
| Variable | Default | Purpose |
|---|---|---|
QGIS_PLUGINPATH | -- | Python plugin directory |
QGIS_PROJECT_FILE | -- | Default project file |
QGIS_SERVER_LOG_STDERR | false | Enable stderr logging |
QGIS_SERVER_LOG_LEVEL | 0 | 0=INFO, 1=WARNING, 2=CRITICAL |
QGIS_SERVER_PARALLEL_RENDERING | false | Parallel WMS GetMap |
QGIS_SERVER_MAX_THREADS | -1 | Thread count for parallel rendering |
QGIS_SERVER_PROJECT_CACHE_SIZE | 100 | Max cached projects |
QGIS_SERVER_WMS_MAX_HEIGHT | -1 | Max WMS image height |
QGIS_SERVER_WMS_MAX_WIDTH | -1 | Max WMS image width |
QGIS_SERVER_API_WFS3_MAX_LIMIT | 10000 | Max OGC API features per request |
---
Reference Links
- references/methods.md -- API signatures for web service classes, server filters, and OGC API handlers
- references/examples.md -- Working code examples for all OGC client types and server patterns
- references/anti-patterns.md -- Common mistakes with URI formats, authentication, and server configuration
Official Sources
- https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/loadlayer.html
- https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/server.html
- https://docs.qgis.org/latest/en/docs/server_manual/index.html
- https://docs.qgis.org/latest/en/docs/server_manual/config.html
- https://qgis.org/pyqgis/master/server/index.html
qgis-impl-web-services — Anti-patterns
AP-01: Not Checking Layer Validity After Construction
WRONG:
rlayer = QgsRasterLayer(wms_uri, "My WMS", "wms")
QgsProject.instance().addMapLayer(rlayer) # May add invalid layerRIGHT:
rlayer = QgsRasterLayer(wms_uri, "My WMS", "wms")
if rlayer.isValid():
QgsProject.instance().addMapLayer(rlayer)
else:
print(f"Layer failed to load: {rlayer.error().summary()}")WHY: An invalid WMS/WFS layer silently fails with no exception. Adding an invalid layer to the project causes rendering errors and confusing blank map areas. ALWAYS check isValid() immediately after construction.
---
AP-02: Hardcoding Credentials in URI Strings
WRONG:
uri = (
"crs=EPSG:4326&format=image/png&layers=buildings"
"&url=https://server/wms"
"&username=admin&password=secret123"
)
rlayer = QgsRasterLayer(uri, "Buildings", "wms")RIGHT:
from qgis.core import QgsDataSourceUri, QgsRasterLayer
quri = QgsDataSourceUri()
quri.setParam("layers", "buildings")
quri.setParam("format", "image/png")
quri.setParam("authcfg", "fm1s770") # Stored auth config ID
quri.setParam("url", "https://server/wms")
quri.setParam("crs", "EPSG:4326")
rlayer = QgsRasterLayer(
str(quri.encodedUri(), "utf-8"), "Buildings", "wms"
)WHY: Hardcoded credentials appear in project files, log output, and layer properties dialogs. They are visible to anyone who opens the project. ALWAYS use QgsAuthManager with stored authentication configurations (authcfg).
---
AP-03: Not URL-Encoding Curly Braces in XYZ URIs
WRONG:
# Curly braces may be interpreted as parameter placeholders
uri = "type=xyz&url=https://tile.server.com/{z}/{x}/{y}.png&zmax=19&zmin=0"RIGHT:
# URL-encode curly braces in the URI parameter value
uri = "type=xyz&url=https://tile.server.com/%7Bz%7D/%7Bx%7D/%7By%7D.png&zmax=19&zmin=0"NOTE: In practice, QGIS handles both encoded and unencoded forms for XYZ URLs passed to QgsRasterLayer. However, when constructing URIs programmatically via QgsDataSourceUri.setParam(), ALWAYS use the encoded form (%7B / %7D) to prevent parameter parsing issues.
---
AP-04: Using Wrong Provider Key for WFS
WRONG:
# Provider key is case-sensitive
vlayer = QgsVectorLayer(wfs_uri, "WFS Layer", "wfs") # lowercase failsRIGHT:
vlayer = QgsVectorLayer(wfs_uri, "WFS Layer", "WFS") # uppercase requiredWHY: The WFS provider key is "WFS" (uppercase). Using lowercase "wfs" causes QGIS to fail to find the provider, resulting in an invalid layer with no clear error message.
---
AP-05: Using classFactory Instead of serverClassFactory for Server Plugins
WRONG:
# __init__.py for a server plugin
def classFactory(iface): # This is for DESKTOP plugins
from .MyPlugin import MyPlugin
return MyPlugin(iface)RIGHT:
# __init__.py for a server plugin
def serverClassFactory(serverIface): # This is for SERVER plugins
from .MyPlugin import MyServerPlugin
return MyServerPlugin(serverIface)WHY: QGIS Server looks for serverClassFactory(), not classFactory(). Using the desktop entry point means the plugin is silently ignored by the server. The serverIface parameter is a QgsServerInterface, not QgisInterface.
---
AP-06: Missing server=True in metadata.txt
WRONG:
[general]
name=MyServerPlugin
description=A server plugin
version=1.0.0
qgisMinimumVersion=3.0
# No server=True lineRIGHT:
[general]
name=MyServerPlugin
description=A server plugin
version=1.0.0
qgisMinimumVersion=3.0
server=TrueWHY: Without server=True in metadata.txt, QGIS Server skips the plugin directory entirely during plugin discovery. There is no warning or error message -- the plugin is silently not loaded.
---
AP-07: Using QGIS Server Classes from Multiple Threads
WRONG:
import threading
from qgis.server import QgsServer
server = QgsServer()
def handle(url):
# NEVER share QgsServer across threads
request = QgsBufferServerRequest(url)
response = QgsBufferServerResponse()
server.handleRequest(request, response)
threads = [threading.Thread(target=handle, args=(url,)) for url in urls]
for t in threads:
t.start()RIGHT:
# Use multiprocessing or container-based scaling
# Each process gets its own QgsServer instance
from multiprocessing import Process
from qgis.core import QgsApplication
from qgis.server import QgsServer, QgsBufferServerRequest, QgsBufferServerResponse
def worker(url):
app = QgsApplication([], False)
server = QgsServer()
request = QgsBufferServerRequest(url)
response = QgsBufferServerResponse()
server.handleRequest(request, response)
app.exitQgis()
processes = [Process(target=worker, args=(url,)) for url in urls]
for p in processes:
p.start()WHY: QGIS Server classes are explicitly NOT thread-safe. Sharing a QgsServer instance across threads causes race conditions, data corruption, and crashes. ALWAYS use multiprocessing or deploy multiple container instances.
---
AP-08: Ignoring Filter Return Values
WRONG:
class MyFilter(QgsServerFilter):
def onRequestReady(self):
# Forgot to return True/False
request = self.serverInterface().requestHandler()
params = request.parameterMap()
# No return statement — defaults to None (falsy)RIGHT:
class MyFilter(QgsServerFilter):
def onRequestReady(self) -> bool:
request = self.serverInterface().requestHandler()
params = request.parameterMap()
return True # ALWAYS explicitly return True to continue chainWHY: Filter callbacks form a chain. If a callback returns False (or None, which is falsy), the chain stops and subsequent filters are not executed. ALWAYS explicitly return True to propagate to the next filter, or False only when intentionally stopping the chain.
---
AP-09: Assuming WMS Supports All CRS
WRONG:
# Assuming the server supports EPSG:28992 without checking
uri = "crs=EPSG:28992&format=image/png&layers=data&url=https://server/wms"
rlayer = QgsRasterLayer(uri, "My WMS", "wms")RIGHT:
# First check capabilities, then request with a supported CRS
# The GetCapabilities response lists supported CRS per layer
uri = "crs=EPSG:4326&format=image/png&layers=data&url=https://server/wms"
rlayer = QgsRasterLayer(uri, "My WMS", "wms")
if not rlayer.isValid():
# Try a different CRS or check GetCapabilities
uri = "crs=EPSG:3857&format=image/png&layers=data&url=https://server/wms"
rlayer = QgsRasterLayer(uri, "My WMS", "wms")WHY: WMS servers only support specific CRS per layer, as declared in their GetCapabilities response. Requesting an unsupported CRS results in an invalid layer or a server error. ALWAYS verify CRS support before loading.
---
AP-10: Mixing WFS Version Parameter Names
WRONG:
# WFS 2.0.0 uses typeNames (plural), not typeName (singular)
uri = (
"https://server/wfs?service=WFS&version=2.0.0"
"&request=GetFeature&typeName=ns:layer" # Wrong for 2.0.0
)RIGHT:
# WFS 2.0.0: typeNames (plural)
uri = (
"https://server/wfs?service=WFS&version=2.0.0"
"&request=GetFeature&typeNames=ns:layer"
)
# WFS 1.0.0: typeName (singular)
uri = (
"https://server/wfs?service=WFS&version=1.0.0"
"&request=GetFeature&typeName=ns:layer"
)WHY: WFS 2.0.0 renamed typeName to typeNames (plural). Using the wrong parameter name for the version causes the server to ignore the layer selection, returning either an error or unexpected results.
---
AP-11: Manual String Concatenation for Service URIs
WRONG:
uri = (
"crs=" + crs + "&format=" + fmt + "&layers=" + layer
+ "&url=" + url + "&username=" + user + "&password=" + pwd
)RIGHT:
from qgis.core import QgsDataSourceUri
quri = QgsDataSourceUri()
quri.setParam("crs", crs)
quri.setParam("format", fmt)
quri.setParam("layers", layer)
quri.setParam("url", url)
quri.setParam("authcfg", auth_id) # Use auth manager instead of plain creds
uri_string = str(quri.encodedUri(), "utf-8")WHY: Manual string concatenation is error-prone: special characters in values break the URI, ampersands in URLs cause parameter splitting, and credentials end up in plain text. ALWAYS use QgsDataSourceUri for programmatic URI construction.
---
AP-12: Not Setting QGIS_PLUGINPATH for Server Plugins
WRONG:
# Plugin placed in default desktop plugin path
~/.local/share/QGIS/QGIS3/profiles/default/python/plugins/MyServerPlugin/RIGHT:
# Set QGIS_PLUGINPATH environment variable
export QGIS_PLUGINPATH=/opt/qgis-server-plugins
# Place plugin in that path
/opt/qgis-server-plugins/MyServerPlugin/
__init__.py
MyServerPlugin.py
metadata.txtWHY: QGIS Server does NOT use the desktop plugin paths. Server plugins MUST be placed in the directory specified by the QGIS_PLUGINPATH environment variable. Without this variable set, no server plugins are loaded.
qgis-impl-web-services — Examples
Example 1: Load Multiple WMS Layers from a Single Server
from qgis.core import QgsRasterLayer, QgsProject
base_url = "https://demo.mapserver.org/cgi-bin/wms"
layers_to_load = ["continents", "cities", "rivers"]
for layer_name in layers_to_load:
uri = (
f"crs=EPSG:4326"
f"&format=image/png"
f"&layers={layer_name}"
f"&styles="
f"&url={base_url}"
)
rlayer = QgsRasterLayer(uri, f"WMS - {layer_name}", "wms")
if rlayer.isValid():
QgsProject.instance().addMapLayer(rlayer)
else:
print(f"FAILED to load WMS layer: {layer_name}")---
Example 2: Load XYZ Basemap with Attribution
from qgis.core import QgsRasterLayer, QgsProject
# OpenStreetMap
osm_url = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
osm_uri = f"type=xyz&url={osm_url}&zmax=19&zmin=0&crs=EPSG3857"
osm_layer = QgsRasterLayer(osm_uri, "OpenStreetMap", "wms")
if osm_layer.isValid():
QgsProject.instance().addMapLayer(osm_layer)
# Stamen Terrain (via Stadia Maps)
terrain_url = "https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}.png"
terrain_uri = f"type=xyz&url={terrain_url}&zmax=18&zmin=0&crs=EPSG3857"
terrain_layer = QgsRasterLayer(terrain_uri, "Stamen Terrain", "wms")
if terrain_layer.isValid():
QgsProject.instance().addMapLayer(terrain_layer)---
Example 3: WFS with Paging (WFS 2.0.0)
from qgis.core import QgsVectorLayer, QgsProject
# WFS 2.0.0 supports paging with startIndex and count
uri = (
"https://demo.mapserver.org/cgi-bin/wfs"
"?service=WFS"
"&version=2.0.0"
"&request=GetFeature"
"&typename=ms:cities"
"&count=100"
"&startIndex=0"
)
vlayer = QgsVectorLayer(uri, "WFS Cities (Page 1)", "WFS")
if vlayer.isValid():
QgsProject.instance().addMapLayer(vlayer)
print(f"Loaded {vlayer.featureCount()} features")---
Example 4: Authenticated WMS Using QgsDataSourceUri
from qgis.core import (
QgsDataSourceUri, QgsRasterLayer, QgsProject,
QgsAuthMethodConfig, QgsApplication,
)
# Step 1: Store credentials (run once)
config = QgsAuthMethodConfig()
config.setName("GeoServer Production")
config.setMethod("Basic")
config.setUri("https://geoserver.example.com/wms")
config.setConfig("username", "admin")
config.setConfig("password", "secret")
auth_mgr = QgsApplication.authManager()
auth_mgr.storeAuthenticationConfig(config)
auth_id = config.id()
# Step 2: Load layer using authcfg
quri = QgsDataSourceUri()
quri.setParam("layers", "workspace:buildings")
quri.setParam("format", "image/png")
quri.setParam("authcfg", auth_id)
quri.setParam("url", "https://geoserver.example.com/wms")
quri.setParam("crs", "EPSG:28992")
quri.setParam("styles", "")
rlayer = QgsRasterLayer(
str(quri.encodedUri(), "utf-8"), "Buildings WMS", "wms"
)
if rlayer.isValid():
QgsProject.instance().addMapLayer(rlayer)---
Example 5: QGIS Server — WMS GetMap Request
from qgis.core import QgsApplication
from qgis.server import QgsServer, QgsBufferServerRequest, QgsBufferServerResponse
app = QgsApplication([], False)
server = QgsServer()
# GetMap request for a rendered image
request = QgsBufferServerRequest(
"http://localhost:8081/?"
"MAP=/data/projects/world.qgs"
"&SERVICE=WMS"
"&VERSION=1.3.0"
"&REQUEST=GetMap"
"&LAYERS=countries"
"&CRS=EPSG:4326"
"&BBOX=-90,-180,90,180"
"&WIDTH=800"
"&HEIGHT=400"
"&FORMAT=image/png"
)
response = QgsBufferServerResponse()
server.handleRequest(request, response)
# Save the rendered image
if response.statusCode() == 200:
with open("/tmp/map_output.png", "wb") as f:
f.write(response.body())
app.exitQgis()---
Example 6: Server Filter — Watermark on WMS GetMap
from qgis.server import QgsServerFilter
from qgis.PyQt.QtCore import QByteArray, QBuffer, QIODevice
from qgis.PyQt.QtGui import QImage, QPainter, QRect
import os
class WatermarkFilter(QgsServerFilter):
def __init__(self, serverIface):
super().__init__(serverIface)
def onResponseComplete(self) -> bool:
request = self.serverInterface().requestHandler()
params = request.parameterMap()
if (params.get("SERVICE", "").upper() == "WMS"
and params.get("REQUEST", "").upper() == "GETMAP"
and not request.exceptionRaised()):
img = QImage()
img.loadFromData(request.body())
watermark = QImage(os.path.join(
os.path.dirname(__file__), "media/watermark.png"))
p = QPainter(img)
p.drawImage(QRect(20, 20, 40, 40), watermark)
p.end()
ba = QByteArray()
buf = QBuffer(ba)
buf.open(QIODevice.WriteOnly)
img.save(buf, "PNG" if "png" in
request.parameter("FORMAT") else "JPG")
request.clearBody()
request.appendBody(ba)
return True
# Registration in plugin:
# serverIface.registerFilter(WatermarkFilter(serverIface), 200)---
Example 7: Access Control — Role-Based Layer Filtering
from qgis.server import QgsAccessControlFilter
class RoleBasedAccessControl(QgsAccessControlFilter):
def __init__(self, server_iface):
super().__init__(server_iface)
def layerFilterExpression(self, layer):
"""Only show features marked as public."""
return "\"visibility\" = 'public'"
def layerFilterSubsetString(self, layer):
return "status = 'published'"
def layerPermissions(self, layer):
rights = QgsAccessControlFilter.LayerPermissions()
rights.canRead = True
rights.canInsert = False
rights.canUpdate = False
rights.canDelete = False
return rights
def authorizedLayerAttributes(self, layer, attributes):
hidden = {"password", "internal_id", "api_key"}
return [a for a in attributes if a not in hidden]
def allowToEdit(self, layer, feature):
return False
def cacheKey(self):
return "role_public"
# Registration:
# serverIface.registerAccessControl(RoleBasedAccessControl(serverIface), 100)---
Example 8: Custom OGC API Endpoint
import json
from qgis.PyQt.QtCore import QRegularExpression
from qgis.server import (
QgsServerOgcApi, QgsServerOgcApiHandler,
QgsServerQueryStringParameter,
)
from qgis.core import (
QgsJsonExporter, QgsCircle, QgsFeature,
QgsPoint, QgsGeometry,
)
class CircleApiHandler(QgsServerOgcApiHandler):
def __init__(self):
super().__init__()
self.setContentTypes([QgsServerOgcApi.HTML, QgsServerOgcApi.JSON])
def path(self):
return QRegularExpression("/circles")
def operationId(self):
return "CreateCircle"
def summary(self):
return "Creates a circle around a point"
def description(self):
return "Generates a circular geometry from x, y, and radius parameters"
def linkTitle(self):
return "Circle Generator"
def linkType(self):
return QgsServerOgcApi.data
def handleRequest(self, context):
values = self.values(context)
x, y, r = values["x"], values["y"], values["r"]
feature = QgsFeature()
feature.setAttributes([x, y, r])
feature.setGeometry(QgsCircle(QgsPoint(x, y), r).toCircularString())
exporter = QgsJsonExporter()
self.write(json.loads(exporter.exportFeature(feature)), context)
def templatePath(self, context):
return ""
def parameters(self, context):
return [
QgsServerQueryStringParameter(
"x", True,
QgsServerQueryStringParameter.Type.Double, "X coordinate"),
QgsServerQueryStringParameter(
"y", True,
QgsServerQueryStringParameter.Type.Double, "Y coordinate"),
QgsServerQueryStringParameter(
"r", True,
QgsServerQueryStringParameter.Type.Double, "Radius"),
]
class CircleApiPlugin:
def __init__(self, serverIface):
api = QgsServerOgcApi(
serverIface, "/circles",
"Circle API", "Generate circles", "1.0")
api.registerHandler(CircleApiHandler())
serverIface.serviceRegistry().registerApi(api)---
Example 9: Custom Service (Full OGC-style)
from qgis.server import QgsService
class HealthCheckService(QgsService):
def name(self):
return "HEALTH"
def version(self):
return "1.0.0"
def executeRequest(self, request, response, project):
response.setStatusCode(200)
response.setHeader("Content-Type", "application/json")
response.write('{"status": "ok", "service": "QGIS Server"}')
# Registration in plugin __init__:
# serverIface.serviceRegistry().registerService(HealthCheckService())
# Access via: ?SERVICE=HEALTH&REQUEST=GetCapabilities---
Example 10: Docker Deployment Configuration
docker-compose.yml:
version: "3"
services:
qgis-server:
image: debian:bookworm-slim
build:
context: .
environment:
QGIS_SERVER_LOG_STDERR: "1"
QGIS_SERVER_LOG_LEVEL: "0"
QGIS_PREFIX_PATH: /usr
QGIS_PLUGINPATH: /plugins
QGIS_PROJECT_FILE: /data/project.qgs
volumes:
- ./data:/data
- ./plugins:/plugins
ports:
- "5555:5555"
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- "80:80"
depends_on:
- qgis-servernginx.conf:
server {
listen 80;
location /ogc/ {
fastcgi_pass qgis-server:5555;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param SCRIPT_NAME /ogc/;
}
}qgis-impl-web-services — Methods Reference
OGC Client Classes
QgsRasterLayer (WMS/WMTS/WCS/XYZ)
| Method | Signature | Return | Purpose |
|---|---|---|---|
| Constructor | QgsRasterLayer(uri: str, baseName: str, providerKey: str) | QgsRasterLayer | Create raster layer from URI |
isValid() | () -> bool | bool | Check if layer loaded successfully |
renderer() | () -> QgsRasterRenderer | Renderer | Get the current renderer |
dataProvider() | () -> QgsRasterDataProvider | Provider | Access data provider |
crs() | () -> QgsCoordinateReferenceSystem | CRS | Get layer CRS |
extent() | () -> QgsRectangle | Extent | Get layer extent |
bandCount() | () -> int | Band count | Number of bands |
width() | () -> int | Width | Raster width in pixels |
height() | () -> int | Height | Raster height in pixels |
QgsVectorLayer (WFS)
| Method | Signature | Return | Purpose |
|---|---|---|---|
| Constructor | QgsVectorLayer(uri: str, baseName: str, providerKey: str) | QgsVectorLayer | Create vector layer from URI |
isValid() | () -> bool | bool | Check if layer loaded successfully |
featureCount() | () -> int | Count | Number of features |
fields() | () -> QgsFields | Fields | Get field definitions |
getFeatures() | (request?: QgsFeatureRequest) -> QgsFeatureIterator | Iterator | Iterate features |
geometryType() | () -> Qgis.GeometryType | Geometry type | Point/Line/Polygon |
crs() | () -> QgsCoordinateReferenceSystem | CRS | Get layer CRS |
QgsDataSourceUri
| Method | Signature | Return | Purpose |
|---|---|---|---|
setParam() | (key: str, value: str) | None | Set a URI parameter |
param() | (key: str) -> str | str | Get a URI parameter value |
removeParam() | (key: str) | None | Remove a URI parameter |
encodedUri() | () -> QByteArray | QByteArray | Get encoded URI bytes |
uri() | (expandAuthConfig?: bool) -> str | str | Get full URI string |
setUsername() | (username: str) | None | Set username |
setPassword() | (password: str) | None | Set password |
---
Authentication Classes
QgsAuthMethodConfig
| Method | Signature | Return | Purpose |
|---|---|---|---|
setName() | (name: str) | None | Set config display name |
setMethod() | (method: str) | None | Set auth method (Basic, OAuth2, etc.) |
setUri() | (uri: str) | None | Set associated URI |
setConfig() | (key: str, value: str) | None | Set config key-value pair |
id() | () -> str | str | Get auto-generated auth config ID |
QgsApplication.authManager() -> QgsAuthManager
| Method | Signature | Return | Purpose |
|---|---|---|---|
storeAuthenticationConfig() | (config: QgsAuthMethodConfig) -> bool | bool | Store a new auth config |
loadAuthenticationConfig() | (authcfg: str, config: QgsAuthMethodConfig) -> bool | bool | Load auth config by ID |
removeAuthenticationConfig() | (authcfg: str) -> bool | bool | Remove auth config |
availableAuthMethodConfigs() | () -> dict | dict | Get all stored configs |
---
QGIS Server Core Classes
QgsServer
| Method | Signature | Return | Purpose |
|---|---|---|---|
| Constructor | QgsServer() | QgsServer | Create server instance |
handleRequest() | (request: QgsServerRequest, response: QgsServerResponse) | None | Process OGC request |
QgsBufferServerRequest
| Method | Signature | Return | Purpose |
|---|---|---|---|
| Constructor | QgsBufferServerRequest(url: str, method?: QgsServerRequest.Method) | Request | Create request from URL |
url() | () -> QUrl | URL | Get request URL |
method() | () -> QgsServerRequest.Method | Method | GET/POST/PUT/DELETE |
QgsBufferServerResponse
| Method | Signature | Return | Purpose |
|---|---|---|---|
headers() | () -> dict | Headers | Get response headers |
body() | () -> QByteArray | Body | Get response body |
statusCode() | () -> int | Status | Get HTTP status code |
QgsRequestHandler
| Method | Signature | Return | Purpose |
|---|---|---|---|
parameterMap() | () -> dict | dict | Get all query parameters |
setParameter() | (key: str, value: str) | None | Inject/modify parameter |
parameter() | (key: str) -> str | str | Get single parameter |
clear() | () | None | Clear response |
setResponseHeader() | (name: str, value: str) | None | Set response header |
appendBody() | (body: bytes) | None | Append to response body |
clearBody() | () | None | Clear response body |
body() | () -> bytes | bytes | Get current response body |
exceptionRaised() | () -> bool | bool | Check for exceptions |
---
Server Filter Classes
QgsServerFilter
| Method | Signature | Return | Purpose |
|---|---|---|---|
| Constructor | QgsServerFilter(serverIface: QgsServerInterface) | Filter | Create filter |
serverInterface() | () -> QgsServerInterface | Interface | Get server interface |
onRequestReady() | () -> bool | bool | Called before core processing |
onSendResponse() | () -> bool | bool | Called on partial output flush |
onResponseComplete() | () -> bool | bool | Called after processing complete |
QgsAccessControlFilter
| Method | Signature | Return | Purpose |
|---|---|---|---|
layerFilterExpression() | (layer: QgsVectorLayer) -> str | Expression | QGIS expression to filter features |
layerFilterSubsetString() | (layer: QgsVectorLayer) -> str | SQL | SQL subset string filter |
layerPermissions() | (layer: QgsMapLayer) -> LayerPermissions | Permissions | CRUD permission flags |
authorizedLayerAttributes() | (layer: QgsVectorLayer, attributes: list) -> list | Filtered list | Remove hidden attributes |
allowToEdit() | (layer: QgsVectorLayer, feature: QgsFeature) -> bool | bool | Per-feature edit permission |
cacheKey() | () -> str | str | Cache key for this ACL context |
QgsAccessControlFilter.LayerPermissions
| Attribute | Type | Default | Purpose |
|---|---|---|---|
canRead | bool | True | Layer is visible |
canInsert | bool | True | Features can be added (WFS-T) |
canUpdate | bool | True | Features can be modified (WFS-T) |
canDelete | bool | True | Features can be deleted (WFS-T) |
QgsServerCacheFilter (QGIS 3.4+)
| Method | Signature | Return | Purpose |
|---|---|---|---|
getCachedDocument() | (project, request, key) -> QByteArray | Cached data | Retrieve cached document |
setCachedDocument() | (doc, project, request, key) -> bool | Success | Store document |
deleteCachedDocument() | (project, request, key) -> bool | Success | Remove cached document |
deleteCachedDocuments() | (project) -> bool | Success | Remove all cached documents |
getCachedImage() | (project, request, key) -> QByteArray | Cached data | Retrieve cached image |
setCachedImage() | (img, project, request, key) -> bool | Success | Store image |
deleteCachedImage() | (project, request, key) -> bool | Success | Remove cached image |
deleteCachedImages() | (project) -> bool | Success | Remove all cached images |
---
OGC API Classes
QgsServerOgcApi
| Method | Signature | Return | Purpose |
|---|---|---|---|
| Constructor | QgsServerOgcApi(serverIface, rootPath, name, description, version) | API | Create OGC API |
registerHandler() | (handler: QgsServerOgcApiHandler) | None | Register path handler |
QgsServerOgcApiHandler (Abstract)
| Method | Signature | Return | Purpose |
|---|---|---|---|
path() | () -> QRegularExpression | Pattern | URL path pattern |
operationId() | () -> str | ID | Unique operation ID |
summary() | () -> str | Text | Short description |
description() | () -> str | Text | Full description |
linkTitle() | () -> str | Title | Title for links |
linkType() | () -> QgsServerOgcApi.Rel | Rel | Link relation type |
handleRequest() | (context: QgsServerApiContext) | None | Process request |
parameters() | (context) -> list | Param list | Accepted parameters |
templatePath() | (context) -> str | Path | HTML template path |
setContentTypes() | (types: list) | None | Set supported content types |
values() | (context) -> dict | Values | Get parsed parameter values |
write() | (data, context) | None | Write response data |
QgsServerQueryStringParameter
| Method | Signature | Return | Purpose |
|---|---|---|---|
| Constructor | (name, required, type, description) | Param | Define query parameter |
Types: QgsServerQueryStringParameter.Type.Double, Type.Integer, Type.String
---
QgsServerInterface
| Method | Signature | Return | Purpose |
|---|---|---|---|
registerFilter() | (filter, priority: int) | None | Register I/O filter |
registerAccessControl() | (filter, priority: int) | None | Register access control |
registerServerCache() | (filter, priority: int) | None | Register cache filter (3.4+) |
requestHandler() | () -> QgsRequestHandler | Handler | Get current request handler |
serviceRegistry() | () -> QgsServiceRegistry | Registry | Get service registry |
configFilePath() | () -> str | Path | Get config file path |
reloadSettings() | () | None | Reload server settings (3.28+) |
QgsService (Abstract)
| Method | Signature | Return | Purpose |
|---|---|---|---|
name() | () -> str | Name | Service name (e.g., "CUSTOM") |
version() | () -> str | Version | Service version |
executeRequest() | (request, response, project) | None | Process service request |