WFS-T with OpenLayers 3.16

This post was originally published for OL 3.5 in June 2015. There is no good reason not to use the latest version of OpenLayers and I am updating the code examples today for OL 3.16.

I am currently using GeoServer 2.8. The data store being a PostGIS 2.1 database.

For my test service I use a very simple table with an ID and geometry only. The geometry is defined as geometry, no type nor projection. It is important that the geometry field is called geometry. Otherwise inserts may create records with empty geometry fields. A constraint must be set on the ID or GeoServer will not be able to insert records into the table.

  id bigint NOT NULL,
  geometry geometry,
  CONSTRAINT wfs_geom_pkey PRIMARY KEY (id)
ALTER TABLE wfs_geom
  OWNER TO geoserver;

CREATE INDEX sidx_wfs_geom
  ON wfs_geom
  USING gist

At the core of the OL javascript snippets is the ol.format.WFS.writeTransaction function which takes 4 input parameter. The first 3 parameter define whether the data should be inserted, updated, or deleted from the data source. The 4th parameter takes the form of ol.format.GML and passes on information about the feature type, namespace, and projection of the data.

The writeTransaction node must be serialised with an XMLSerializer to be used in a WFS-T post.

The three use cases (insert/update/delete) and the AJAX call are shown in the following code snippet.

var formatWFS = new ol.format.WFS();

var formatGML = new ol.format.GML({
    featureNS: '',
    featureType: 'wfs_geom',
    srsName: 'EPSG:3857'

var xs = new XMLSerializer();

var transactWFS = function (mode, f) {
    var node;
    switch (mode) {
        case 'insert':
            node = formatWFS.writeTransaction([f], null, null, formatGML);
        case 'update':
            node = formatWFS.writeTransaction(null, [f], null, formatGML);
        case 'delete':
            node = formatWFS.writeTransaction(null, null, [f], formatGML);
    var payload = xs.serializeToString(node);
    $.ajax('', {
        service: 'WFS',
        type: 'POST',
        dataType: 'xml',
        processData: false,
        contentType: 'text/xml',
        data: payload
    }).done(function() {

Inserts are called from the drawend event of the OL interaction. The .clear() function on the WFS source reloads the source after each transaction. This ensure that feature IDs are correct for new features and that deleted features are removed from the view.

The ID of a modified feature is stored and the update transaction is posted once the feature is unselected. In order to successfully post an update transaction the boundedBy property must be stripped from the feature properties. A clone of the feature is used to achieve this.

interaction = new ol.interaction.Modify({
    features: interactionSelect.getFeatures()
dirty = {};
interactionSelect.getFeatures().on('add', function (e) {
    e.element.on('change', function (e) {
        dirty[] = true;
interactionSelect.getFeatures().on('remove', function (e) {
    var f = e.element;
    if (dirty[f.getId()]) {
        delete dirty[f.getId()];
        var featureProperties = f.getProperties();
        delete featureProperties.boundedBy;
        var clone = new ol.Feature(featureProperties);
        transactWFS('update', clone);

A complete working example is hosted in this jsFiddle.

A previous working example for OL 3.5 is still hosted on Google App Engine.

The complete code example for the OL 3.5 version is on GitHub.

I plan to update my Google App Engine projects and GitHub repositories after I complete my switch to CodeEnvy.

32 thoughts on “WFS-T with OpenLayers 3.16”

  1. Hi,
    excellent tutorial! I tries to follow your explanation but I get some problems. I configure geoserver to allow “write” for the workspace. So I succeed to add a feature in layer with qgis but not with your script. I get an error of geoserver in post response : No such feature type …
    It’s ok for update and delete the data.
    Thank’s for your help.

  2. Your example work perfectly, but I think text missing description of authentification configuration for OWC service. I had some problem with this. But after some configurations by server side (filter chain for /owc/ ) and JavaScript (beforeSend option of ajax) everything is great.
    Thx a lot!

  3. This is excatly what I want to do with my web map but I am struggling to get it working. I draw a point on the map and want to add attributes from a html form, everything works till I run transactWFS, the xml sent to the server seems not correct as my new feature is not saved, any suggestions?

    1. I recommend to put together a jsfiddle in order to post the problem on stack overflow. We also need to know the error message you get from Geoserver and which versions you use in the back end.

  4. that’s great tutorial, but my concern is that i want to view big data as a WMS , and i want to enable the user to edit at the same time.. so can i use your example under the hood ?! so infront the updates are displayed as WMS but when and editing session starts it’s the wfs-t which is being edited

  5. I thx for this awesome post.

    I just have a question, if i want to put a attribute(data) when i draw a point how can i do?

    Thx a lot

    1. You can set the feature properties on the feature before you write the feature to GML for the WFS payload like so f.setProperties({color: ‘purple’}). I put together a small jsfiddle which assigns a random funky colour to new polygons before storing them in the WFS data source.

  6. Hey d,
    I have my own geoserver up and running with some openstreetmap data loaded in via postgis. I am seeing some weird behavior in various places and I think some of it might be attributed to my geoserver setup. geoservers’ documentation is largely incomplete and has been for years. I was wondering if you could post some of the settings you use to set up your geoserver (specifically anything having to do with workspace, stores, layers, and WFS options or anything else relevant for that matter) so I could compare them with what I’ve got. Any tips you can provide would be greatly appreciated.

    1. I tend to have separate workspaces for different OGC services. I.e. A workspace for WMS and a workspace for WFS. I disable the global setting for all services and enable the service on the workspace alone. I had problems with dots in workspace names. I use only underscores and letter in the name. The namespace URI I define as the actual URI for the service. E.g. The workspace namespace for the workspace geolytix_wfs on is Calls are made to with the service being described in the AJAX service parameter. I only ever assign every store to a single workspace but I use several stores on each workspace. I would never connect to Geoserver in a non-secure way. I user https whenever possible. I set the CORS access rules in the Tomcat config.

      1. Very useful tips. Especially the one about turning off the global workspace. Could you explain what you mean by “I only ever assign every store to a single workspace…”

      2. Currently, my geoserver is not accessible to the outside world like yours is. I’ve just been using localhost like a good little boy, but I’m thinking about making it public. I was considering setting up a dyndns account (if that’s even possible) and serve it up straight from home. But I’m a bit worried about going down that route for obvious security reasons. What do you use to host your geoserver? A third party vendor?

  7. Sir, I’m newbie for all of this, but I want to learn it further.
    I will try to make it works in my server

    I put this in my pgadmin sql query :

    CREATE TABLE wfs_geom
    id bigint NOT NULL,
    geometry geometry,
    CONSTRAINT wfs_geom_pkey PRIMARY KEY (id)
    WITH (
    ALTER TABLE wfs_geom
    OWNER TO geoserver;

    CREATE INDEX sidx_wfs_geom
    ON wfs_geom
    USING gist

    and this is what happened :

    ERROR: role “geoserver” does not exist
    ********** Error **********

    ERROR: role “geoserver” does not exist
    SQL state: 42704

    I don’t understand why
    Could you help me sir?
    Thank You very much

    1. I create the table in a database of which the user geoserver is the owner. I am logged in as a superuse in pgadmin. You can leave the alter table change owner bit out and create the table with your logged in user account as owner.

  8. Hello, Great that you updated it to 3.16 v.
    Can you explain shortly the difference between this and prev version??

    1. I added .clear() to the WFS source in order to update the WFS layer after transactions. I only learned about this recently. I also changed the delete transaction in response to the comment from Willie Maddox. The main change was that I added a working jsfiddle. And I changed the geoserver backend to a different server.

      1. First of all: Thanks for this great example! Works fine with QGIS Server (after setting WFS to 1.0.0 and GML to version 2).

        Using the .clear() method seems to have the following flaw:

        If you’re saving an edited feature by clicking and therefore selecting another feature instead of just deselecting the edited feature, then the display of the newly selected feature is strange… this is because the WFS is updated after the “add” event of the added feature is fired…
        No idea about a workaround though..

        1. I have been trying to replicate this but wasn’t able to. I activate the edit mode, select a feature by clicking inside the feature, draw a few new nodes from the existing boundary of this feature and then select straight away another polygon feature by clicking inside this feature. The display is expected. Can you write down the steps which produce a weird shape?

          1. sure:
            1. click edit
            2. click on point1
            3. move point1 by click-dragging
            4. release point1
            5. click on point2
            6. click-drag point2
            then point2 stays at the same place, but the modify-layer draws while im moving the point. upon releasing point2, the feature in the modify-layer dissapears (but is still selectable by hovering over the exact location) and only reappears if i e.g. click into the empty space.

            Tested in IE11 and Chrome

  9. help me!
    where give link? i don’t know this url.
    layerOSM_BW = new ol.layer.Tile({
    source: new ol.source.XYZ({
    url : ‘{z}/{x}/{y}.png’

  10. I finally got it to run…

    I’ve been studying your events and I was wondering if there was any particular reason why for the ‘case btnDelete:’ you use:

    interaction.getFeatures().on(‘change:length’, function(e) {…})

    rather than,

    interaction.on(‘select’, function(e) {…})

    I would have never thought to do it the ‘change:length’ way. The only thing I’ve noticed is that using ‘change:length’ actually fires transactWFS twice (once for clicking, and then again when clear() is called) which makes me think you must have had a good reason for doing it the way you did. Would you mind commenting on why you chose this method? It’s interesting.

    1. Thanks for pointing that out. You are absolutely right. That wasn’t an elegant way of handling the delete. I probably copy and pasted this bit from somewhere without giving it too much thought. I am in process of reviewing the code and updating this for OL3.16. I will also put the complete example into a JSFiddle.

  11. Thanks for posting this! Your working example is nice. I forked your repo to learn how you did it but I can’t make it run. I am guessing I should start by calling “python” from the Webmaps directory, but I am not sure what to do next.

    Any tips?

    1. The repo was put together for a GAE project. Not sure whether that will work out of the box on different environments. I have switched since then away from GAE and now use a Web2Py backend on a virtual server.

  12. Thanks for the post! It’s been the most helpful for me trying to get my application to work.

    I followed the post and I have been able to insert, delete, and edit features in the application. However, I have not been able to get anything to save to the db. My db on geoserver is password protected, do I need to include that in my ajax statement? Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *