Skip to main content

Overview

QFieldCloud provides seamless synchronization between QGIS desktop and QField mobile app, enabling field data collection with offline capabilities. The sync workflow involves packaging data for download, collecting changes in the field, and applying updates back to the master dataset.

Synchronization Architecture

Three-Stage Workflow

  1. Package - Prepare optimized data for mobile device
  2. Collect - Make edits offline on QField mobile
  3. Synchronize - Upload changes and apply to master data

Packaging Workflow

What is Packaging?

Packaging prepares project data for offline use by:
  • Converting online layers to offline formats (e.g., GeoPackage)
  • Optimizing file sizes
  • Bundling required assets
  • Configuring QField-specific settings

Package Job Model

The PackageJob extends the base Job model:
class PackageJob(Job):
    # Type is automatically set to Job.Type.PACKAGE
    type = models.CharField(max_length=32, choices=Type.choices)
    status = models.CharField(choices=Status.choices, default=Status.PENDING)
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    triggered_by = models.ForeignKey(User, on_delete=models.CASCADE)
See core/models.py:2366.

Triggering a Package Job

Via API

POST /api/v1/jobs/
{
  "project_id": "uuid-here",
  "type": "package"
}

Via QFieldSync Plugin

The QFieldSync QGIS plugin automatically triggers packaging when synchronizing.

Packaging Process

  1. Job Creation - PackageJob created with status PENDING
  2. Queue - Job moves to QUEUED when worker resources allocated
  3. Execution - Worker container processes the project:
    • Loads QGIS project file
    • Applies offlining algorithm
    • Copies required files
    • Generates package files
  4. Completion - Status changes to FINISHED or FAILED

Packaging Offliners

Two packaging algorithms are available:
class PackagingOffliner(models.TextChoices):
    QGISCORE = "qgiscore", "QGIS Core Offline Editing (deprecated)"
    PYTHONMINI = "pythonmini", "Optimized Packager"
Recommended: Use PYTHONMINI for better performance and smaller package sizes. See core/models.py:1087.

User-Specific Packages

Packages can be user-specific when secrets are assigned:
def package_jobs_for_user(self, user: User) -> PackageJobQuerySet:
    secret_filters = Q(project=self, organization=None, assigned_to=user)
    
    if self.owner.is_organization:
        secret_filters |= Q(project=None, organization=self.owner, assigned_to=user)
    
    secret_qs = Secret.objects.filter(secret_filters)
    
    jobs_qs = self.jobs.filter(type=Job.Type.PACKAGE)
    jobs_qs = jobs_qs.annotate(has_user_secret=Exists(secret_qs)).filter(
        Q(has_user_secret=False) | Q(triggered_by=user)
    )
    
    return jobs_qs
See core/models.py:1348.

Download Workflow

Package Files

Packaged files are stored separately from project files:
class FileType(models.IntegerChoices):
    PROJECT_FILE = 1, "Project File"
    PACKAGE_FILE = 2, "Package File"
Package files are stored at:
projects/{project_id}/packages/{package_job_id}/{filename}
See filestorage/models.py:48.

Downloading to QField

QField mobile app:
  1. Lists available projects via API
  2. Checks latest package job status
  3. Downloads packaged files
  4. Stores locally for offline use

Attachment Download Modes

Projects can configure attachment download behavior:
is_attachment_download_on_demand = models.BooleanField(
    default=False,
    verbose_name="On demand attachment files download",
    help_text="If enabled, attachment files should be downloaded on demand with QField"
)
When enabled:
  • Reduces initial download size
  • Attachments fetched only when needed
  • Useful for projects with many/large attachments
See core/models.py:1311.

Delta-Based Synchronization

What are Deltas?

Deltas represent individual changes made in QField:
  • Feature creation
  • Feature modification (attribute or geometry)
  • Feature deletion

Delta Creation in QField

When editing offline, QField:
  1. Tracks all changes locally
  2. Creates delta objects for each edit
  3. Bundles deltas into a deltafile
  4. Uploads deltafile when online

Deltafile Format

Deltafiles are JSON documents:
{
  "id": "deltafile-uuid",
  "project": "project-uuid",
  "version": "1.0",
  "deltas": [
    {
      "uuid": "delta-uuid",
      "clientId": "device-uuid",
      "localLayerId": "layer_name",
      "method": "create",
      "new": {
        "geometry": {...},
        "attributes": {...}
      }
    }
  ]
}

Upload and Apply

When QField syncs:
  1. Upload - Deltafile POSTed to API
  2. Validation - Schema and permissions checked
  3. Storage - Individual deltas saved to database
  4. Apply Job - Automatic or manual delta application
See core/views/deltas_views.py:67 for upload handling.

Apply Jobs

Applying Deltas

The ApplyJob processes pending deltas:
class ApplyJob(Job):
    deltas_to_apply = models.ManyToManyField(
        to=Delta,
        through="ApplyJobDelta",
    )
    overwrite_conflicts = models.BooleanField(
        help_text="Automatically overwrite conflicts while applying deltas"
    )
See core/models.py:2403.

Automatic Apply

By default, deltas are applied automatically:
if created_deltas and not jobs.apply_deltas(
    project_obj,
    request.user,
    project_obj.the_qgis_file_name,
    project_obj.overwrite_conflicts,
):
    logger.warning("Failed to start delta apply job.")
See core/views/deltas_views.py:152.

Manual Apply

For projects with overwrite_conflicts=False, deltas must be applied manually after conflict resolution.

Data Synchronization Flow

Desktop to Cloud

  1. Create/edit QGIS project in QGIS Desktop
  2. Upload project files via QFieldSync plugin
  3. Trigger packaging job
  4. Wait for job completion

Cloud to Mobile

  1. Open QField app
  2. Select project to sync
  3. Download latest package
  4. Work offline with local data

Mobile to Cloud

  1. Make edits in QField
  2. Sync when online
  3. Deltas uploaded to cloud
  4. Apply job processes changes

Cloud to Desktop

  1. Open QGIS Desktop
  2. Refresh project layers
  3. Changes from field are visible
  4. Continue editing cycle

Repackaging Logic

When to Repackage

def needs_repackaging(self, user: User) -> bool:
    latest_package_job_for_user = self.latest_package_job_for_user(user)
    
    if (
        self.has_online_vector_data is False
        and self.data_last_updated_at
        and self.data_last_packaged_at
        and latest_package_job_for_user
    ):
        if latest_package_job_for_user.finished_at:
            return (
                latest_package_job_for_user.finished_at < self.data_last_updated_at
            )
        else:
            return True
    else:
        # Has online layers - always repackage to get latest
        return True
See core/models.py:1645.

Cache Expiration

repackaging_cache_expire = models.DurationField(
    default=timedelta(minutes=60),
    validators=[MinValueValidator(timedelta(minutes=1))],
)
Prevents excessive repackaging by caching recent packages.

Online Vector Layers

Projects with online layers (PostGIS, WFS, etc.):
  • Cannot determine if data changed
  • Always marked as needing repackaging
  • Fetches fresh data on each package
@property
def has_online_vector_data(self) -> bool | None:
    if not self.project_details or not self.project_details.get("layers_by_id"):
        return None
    
    from qfieldcloud.core.utils2.project import has_online_vector_data
    return has_online_vector_data(self)
See core/models.py:1631.

Conflict Resolution

Conflicts occur when:
  • Same feature edited in multiple places
  • Feature deleted locally but modified remotely
  • Concurrent edits to same attributes

Automatic Conflict Resolution

When project.overwrite_conflicts=True:
  • Latest change wins
  • No manual intervention required
  • Suitable for most field data collection

Manual Conflict Resolution

When project.overwrite_conflicts=False:
  • Deltas marked with status CONFLICT
  • Project manager must review
  • Resolve through API or UI
  • More control but requires oversight
See Deltas for detailed conflict handling.

Best Practices

Optimize Package Size

  1. Use appropriate layer types (file-based when possible)
  2. Limit spatial extent for large datasets
  3. Enable on-demand attachment download
  4. Configure appropriate packaging offliner

Sync Frequency

  • Package when project data changes
  • Field workers sync at least daily
  • Monitor job status before deploying to field
  • Test package downloads before critical fieldwork

Network Considerations

  • QField handles intermittent connectivity
  • Deltas queued locally until online
  • Package downloads resumable
  • Consider 3G/4G bandwidth in remote areas

Monitoring Synchronization

Job Status Tracking

GET /api/v1/jobs/?project_id={projectid}
Monitor:
  • Package job completion
  • Apply job status
  • Error messages in output field
  • QGIS version used

Project Timestamps

data_last_updated_at = models.DateTimeField(blank=True, null=True)
data_last_packaged_at = models.DateTimeField(blank=True, null=True)
Track when:
  • Data was last modified
  • Last successful package created
  • Determine if repackaging needed

API Reference

Trigger Package Job

POST /api/v1/jobs/

Check Repackaging Status

GET /api/v1/projects/{projectid}/
Check needs_repackaging field in response.

List Package Jobs

GET /api/v1/jobs/?project_id={projectid}&type=package
  • Deltas - Understanding delta creation and application
  • File Management - Managing project and package files
  • Jobs - Job types and status codes

Build docs developers (and LLMs) love