How to Convert Shapefiles with ogr2ogr on the Command Line

Run ogr2ogr -f GPKG output.gpkg input.shp -nln layername -tsrs EPSG:4326 -lco SPATIALINDEX=YES -lco GEOMETRYNAME=geom to convert a Shapefile into an…

Run ogr2ogr -f GPKG output.gpkg input.shp -nln layer_name -t_srs EPSG:4326 -lco SPATIAL_INDEX=YES -lco GEOMETRY_NAME=geom to convert a Shapefile into an indexed, reprojected GeoPackage layer in a single command — no Python, no desktop GIS, just the GDAL utility on your PATH.

This page belongs to the SpatiaLite CLI & Shell Automation guide, which covers driving the whole GDAL and SQLite command-line stack from shell scripts. Here the focus is narrow and deliberate: the ogr2ogr flags that matter when your entire conversion runs from the command line.

Why This Matters

ogr2ogr is the workhorse of spatial format conversion, and knowing its flag vocabulary means a Shapefile-to-GeoPackage job becomes one line in a shell script rather than a notebook you have to open and run by hand. On a headless CI runner, a field gateway, or inside a cron-driven pipeline, there is no Python interpreter to import — only the GDAL binaries. Every option below is a command-line switch, so the same command reproduces exactly whether it runs on your laptop or on a remote box three time zones away.

The command-line approach also composes naturally with the rest of the shell toolchain: you can pipe an ogr2ogr conversion into an ogrinfo validation and a sqlite3 PRAGMA check inside one script, and fail the whole run on the first non-zero exit code. If you would rather drive the conversion from Python — for streaming control, per-feature transforms, or DataFrame integration — use the Fiona batch-convert walkthrough instead; this page stays strictly on the CLI.

Prerequisites

  • GDAL/OGR 3.4+ with ogr2ogr and ogrinfo on PATH (ogr2ogr --version)
  • The GPKG and SQLite drivers compiled in (ogrinfo --formats | grep -E 'GPKG|SQLite')
  • A source Shapefile with its companion .shx, .dbf, and .prj sidecars present
  • The source CRS defined in the .prj file, or known so you can supply it with -s_srs
  • Write permission in the output directory for the .gpkg file and any -wal sidecar

Primary Method

The following command converts a Shapefile to a GeoPackage layer, reprojects it to WGS 84, names the layer explicitly, builds an R-tree spatial index at creation, and sets the geometry column name to geom.

bash
# GDAL 3.4+ — Shapefile to indexed, reprojected GeoPackage layer in one command
# -f GPKG           output driver: GeoPackage
# survey.gpkg       destination dataset (created)
# parcels.shp       source dataset
# -nln parcels      name the new layer explicitly
# -t_srs EPSG:4326  reproject to WGS 84
# -lco ...          layer creation options: R-tree index and geometry column name
# -nlt ...          accept mixed Polygon / MultiPolygon input
ogr2ogr \
    -f GPKG survey.gpkg parcels.shp \
    -nln parcels \
    -t_srs EPSG:4326 \
    -lco SPATIAL_INDEX=YES \
    -lco GEOMETRY_NAME=geom \
    -nlt PROMOTE_TO_MULTI

Each flag does one job. -f GPKG selects the output format; -nln (new layer name) controls the layer name written into gpkg_contents; -t_srs sets the target CRS and triggers on-the-fly reprojection; the two -lco (layer creation option) flags configure spatial indexing and the geometry column name; and -nlt PROMOTE_TO_MULTI promotes single geometries to their multi-part variant so a layer with both Polygon and MultiPolygon features does not error.

Step-by-Step Walkthrough

1. Select the output driver with -f

The -f flag names the output driver. GPKG writes an OGC GeoPackage; SQLite writes a generic SQLite/SpatiaLite database. The driver must appear in ogrinfo --formats.

bash
# List available formats to confirm the GPKG driver is present
ogrinfo --formats | grep GPKG
# Output: GPKG -raster,vector- (rw+vs): GeoPackage

2. Name the layer with -nln

Without -nln, the output layer inherits the Shapefile’s base filename, which is often not what you want in a shared GeoPackage. -nln sets an explicit, script-controlled name.

bash
# Force a clean layer name regardless of the source filename
ogr2ogr -f GPKG survey.gpkg "Field Survey 2026.shp" -nln parcels_2026

3. Reproject with -t_srs and -s_srs

-t_srs sets the target CRS; OGR reads the source CRS from the .prj sidecar and reprojects each geometry as it writes. When the source has no .prj or a wrong one, declare it explicitly with -s_srs.

bash
# Source CRS is missing or wrong: state it explicitly, then reproject
ogr2ogr -f GPKG survey.gpkg parcels.shp \
    -s_srs EPSG:27700 \
    -t_srs EPSG:4326 \
    -nln parcels

4. Configure the layer with -lco

Layer creation options tune how the GeoPackage layer is built. The two that matter most are SPATIAL_INDEX=YES, which builds the R-tree at creation so spatial queries are fast immediately, and GEOMETRY_NAME=geom, which sets the geometry column name registered in the metadata tables.

bash
# Build the spatial index and name the geometry column at creation time
ogr2ogr -f GPKG survey.gpkg parcels.shp -nln parcels \
    -lco SPATIAL_INDEX=YES \
    -lco GEOMETRY_NAME=geom \
    -lco FID=fid

The R-tree index this creates is the same structure documented in the GeoPackage specification; building it at conversion time avoids a separate index pass later.

5. Filter rows with -where and reshape with -sql

-where applies an attribute filter so only matching features are written. For anything beyond a simple predicate — computed columns, joins, renamed fields — use -sql with an OGR SQL statement.

bash
# Attribute filter: only write large parcels
ogr2ogr -f GPKG large.gpkg parcels.shp -nln big_parcels \
    -where "area_m2 > 10000"

# Full SQL: rename and compute columns during conversion
ogr2ogr -f GPKG derived.gpkg parcels.shp -nln parcels \
    -sql "SELECT fid, owner AS holder, area_m2 / 10000.0 AS area_ha FROM parcels"

6. Add to an existing GeoPackage with -update and -append

To add a layer or more features to an existing GeoPackage instead of recreating it, pass -update to open the file in write mode and -append to add features to an existing layer. Without -update, OGR refuses to touch an existing dataset; without -append, it tries to create a layer that already exists and errors.

bash
# Append a second Shapefile's features into the same layer
ogr2ogr -f GPKG -update -append survey.gpkg parcels_north.shp -nln parcels

7. Handle mixed geometries and bad features

-nlt PROMOTE_TO_MULTI sets the new layer’s geometry type to the multi-part variant so a source mixing Polygon and MultiPolygon (or LineString and MultiLineString) loads without a type-mismatch error. -skipfailures lets the conversion continue past individual invalid features instead of aborting the whole run.

bash
# Promote to multi-geometry and skip individual bad features
ogr2ogr -f GPKG survey.gpkg mixed.shp -nln parcels \
    -nlt PROMOTE_TO_MULTI \
    -skipfailures

8. Write to SpatiaLite instead of GeoPackage

To target a SpatiaLite database rather than a GeoPackage, use the SQLite driver and the dataset creation option -dsco SPATIALITE=YES, which tells the driver to emit SpatiaLite metadata tables. Layer creation options carry over.

bash
# Convert into a SpatiaLite database with the SQLite driver
ogr2ogr -f SQLite parcels.sqlite parcels.shp \
    -dsco SPATIALITE=YES \
    -nln parcels \
    -t_srs EPSG:4326 \
    -lco SPATIAL_INDEX=YES \
    -lco GEOMETRY_NAME=geometry

Note the difference between -dsco (dataset creation option, applies to the whole file) and -lco (layer creation option, applies to one layer). SPATIALITE=YES is a -dsco because it governs the file’s metadata format; SPATIAL_INDEX=YES is a -lco because it governs one layer’s index.

Verification

Confirm the conversion produced the layer, geometry type, CRS, and feature count you expect with ogrinfo, then cross-check the spatial index exists with a raw SQL query.

bash
# Summarise the new layer without dumping every feature
ogrinfo -al -so survey.gpkg parcels

# Expected excerpt:
# Layer name: parcels
# Geometry: Multi Polygon
# Feature Count: 5231
# Layer SRS WKT: GEOGCS["WGS 84", ...]
# Geometry Column = geom
bash
# Confirm the R-tree index table was created alongside the layer
sqlite3 survey.gpkg "SELECT name FROM sqlite_master WHERE name LIKE 'rtree_parcels%';"
# Output includes: rtree_parcels_geom

If the geometry column reports as geom and the rtree_parcels_geom virtual table exists, the layer creation options took effect and spatial queries will use the index.

Alternative Approaches or Edge Cases

Converting many Shapefiles in a loop. ogr2ogr handles one source per invocation, so batch conversion is a shell loop: create the GeoPackage with the first file, then -update -append the rest. Give each its own layer with -nln "$(basename "$shp" .shp)".

bash
# CLI batch: first file creates the GeoPackage, the rest append as new layers
set -euo pipefail
first=1
for shp in ./shapefiles/*.shp; do
    layer=$(basename "$shp" .shp)
    if [ "$first" -eq 1 ]; then
        ogr2ogr -f GPKG merged.gpkg "$shp" -nln "$layer" -lco SPATIAL_INDEX=YES
        first=0
    else
        ogr2ogr -f GPKG -update merged.gpkg "$shp" -nln "$layer" -lco SPATIAL_INDEX=YES
    fi
done

When you need per-feature logic. If the transformation cannot be expressed as -where or -sql — for example, geometry repair that depends on external data, or streaming ingestion that must not load the whole file — drive the conversion from Python with the Fiona batch-convert approach, which gives you a record-by-record loop while still using the same OGR drivers underneath.

Troubleshooting

Warning 1: Layer creation options ignored since an existing layer is being appended

Cause: You passed -lco flags together with -append. Layer creation options only apply when a layer is first created; on append the existing layer’s options already govern it, so OGR ignores the new ones.

Fix: Drop the -lco flags from append commands. Set SPATIAL_INDEX and GEOMETRY_NAME only on the command that first creates the layer; appended features are indexed automatically by the existing triggers.

ERROR 1: Attempt to write non-polygon (POLYGON) geometry to POLYGON MULTIPOLYGON

Cause: The source Shapefile mixes single and multi-part geometries, and the target layer was created with a single-part geometry type, so a MultiPolygon feature cannot be written into a Polygon layer (or vice versa).

Fix: Add -nlt PROMOTE_TO_MULTI so the layer is created as MultiPolygon and single-part features are promoted on write. Recreate the layer with this flag; it cannot be changed after creation.

ERROR 4: Failed to open datasource ... parcels.shp

Cause: A missing .shx or .dbf sidecar, a path typo, or — on a case-sensitive Linux filesystem — a filename-case mismatch such as Parcels.SHP versus parcels.shp.

Fix: Confirm all sidecars sit next to the .shp (ls parcels.* should show .shp, .shx, .dbf, and ideally .prj) and match the exact case of the filename you pass. If only -t_srs fails with a PROJ database error, ensure PROJ_LIB points at a directory containing proj.db.

Frequently Asked Questions

What is the difference between -dsco and -lco?

A dataset creation option (-dsco) applies to the output file as a whole, while a layer creation option (-lco) applies to a single layer inside it. SPATIALITE=YES is a -dsco because it decides the whole database’s metadata format; SPATIAL_INDEX=YES and GEOMETRY_NAME=geom are -lco flags because each governs one layer’s index and geometry column. When you append a second layer to an existing file, only -lco flags are relevant, since the dataset already exists.

Do I need to build the spatial index separately after converting?

No — pass -lco SPATIAL_INDEX=YES on the command that creates the layer and ogr2ogr builds the R-tree during conversion, so spatial queries are fast immediately with no extra pass. You only build an index separately when you deliberately deferred it for a very large bulk load, in which case you create the layer with SPATIAL_INDEX=NO, load the data, then build the index once with a spatial SQL call. For appends into an already-indexed layer, the existing triggers index new features automatically.