How to Convert Shapefiles with ogr2ogr on the Command Line
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
ogr2ograndogrinfoonPATH(ogr2ogr --version) - The
GPKGandSQLitedrivers compiled in (ogrinfo --formats | grep -E 'GPKG|SQLite') - A source Shapefile with its companion
.shx,.dbf, and.prjsidecars present - The source CRS defined in the
.prjfile, or known so you can supply it with-s_srs - Write permission in the output directory for the
.gpkgfile and any-walsidecar
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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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
# 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)".
# 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.
Related
- SpatiaLite CLI & Shell Automation — parent guide: driving
ogr2ogr,sqlite3, andogrinfotogether from shell scripts - Scripting Batch Spatial Queries with the sqlite3 CLI — run spatial SQL over the converted file non-interactively
- How to Batch Convert Shapefiles to GeoPackage with Fiona — the Python alternative when you need per-feature control
- How to Validate GeoPackage OGC Compliance — confirm the converted file meets the OGC standard