diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93aef7b..1dfa6a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ New features and improvements:
   using ``engine="pyogrio"`` (#2788).
 - Added a ``to_wgs84`` keyword to ``to_json`` allowing automatic re-projecting to follow
   the 2016 GeoJSON specification (#416).
+- ``to_json`` output now includes a ``"crs"`` field if the CRS is not the default WGS84 (#1774).
 - Improve error messages when accessing the `geometry` attribute of GeoDataFrame without an active geometry column 
   related to the default name `"geometry"` being provided in the constructor (#2577)
 
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
index 3557a2f..3fb64d0 100644
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -766,22 +766,28 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
           feature individually so that features may have different properties.
         - ``keep``: output the missing entries as NaN.
 
+        If the GeoDataFrame has a defined CRS, its definition will be included
+        in the output unless it is equal to WGS84 (default GeoJSON CRS) or not
+        possible to represent in the URN OGC format, or unless ``to_wgs84=True``
+        is specified.
+
         Examples
         --------
 
         >>> from shapely.geometry import Point
         >>> d = {'col1': ['name1', 'name2'], 'geometry': [Point(1, 2), Point(2, 1)]}
-        >>> gdf = geopandas.GeoDataFrame(d, crs="EPSG:4326")
+        >>> gdf = geopandas.GeoDataFrame(d, crs="EPSG:3857")
         >>> gdf
-            col1                 geometry
-        0  name1  POINT (1.00000 2.00000)
-        1  name2  POINT (2.00000 1.00000)
+            col1             geometry
+        0  name1  POINT (1.000 2.000)
+        1  name2  POINT (2.000 1.000)
 
         >>> gdf.to_json()
         '{"type": "FeatureCollection", "features": [{"id": "0", "type": "Feature", \
 "properties": {"col1": "name1"}, "geometry": {"type": "Point", "coordinates": [1.0,\
  2.0]}}, {"id": "1", "type": "Feature", "properties": {"col1": "name2"}, "geometry"\
-: {"type": "Point", "coordinates": [2.0, 1.0]}}]}'
+: {"type": "Point", "coordinates": [2.0, 1.0]}}], "crs": {"type": "name", "properti\
+es": {"name": "urn:ogc:def:crs:EPSG::3857"}}}'
 
         Alternatively, you can write GeoJSON to file:
 
@@ -801,9 +807,26 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
                 )
         else:
             df = self
-        return json.dumps(
-            df._to_geo(na=na, show_bbox=show_bbox, drop_id=drop_id), **kwargs
-        )
+
+        geo = df._to_geo(na=na, show_bbox=show_bbox, drop_id=drop_id)
+
+        # if the geometry is not in WGS84, include CRS in the JSON
+        if df.crs is not None and not df.crs.equals("epsg:4326"):
+            auth_crsdef = self.crs.to_authority()
+            allowed_authorities = ["EDCS", "EPSG", "OGC", "SI", "UCUM"]
+
+            if auth_crsdef is None or auth_crsdef[0] not in allowed_authorities:
+                warnings.warn(
+                    "GeoDataFrame's CRS is not representable in URN OGC "
+                    "format. Resulting JSON will contain no CRS information.",
+                    stacklevel=2,
+                )
+            else:
+                authority, code = auth_crsdef
+                ogc_crs = f"urn:ogc:def:crs:{authority}::{code}"
+                geo["crs"] = {"type": "name", "properties": {"name": ogc_crs}}
+
+        return json.dumps(geo, **kwargs)
 
     @property
     def __geo_interface__(self):
@@ -814,7 +837,10 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
         ``FeatureCollection``.
 
         This differs from `_to_geo()` only in that it is a property with
-        default args instead of a method
+        default args instead of a method.
+
+        CRS of the dataframe is not passed on to the output, unlike
+        :meth:`~GeoDataFrame.to_json()`.
 
         Examples
         --------
@@ -833,8 +859,6 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
 , 2.0)}, 'bbox': (1.0, 2.0, 1.0, 2.0)}, {'id': '1', 'type': 'Feature', 'properties\
 ': {'col1': 'name2'}, 'geometry': {'type': 'Point', 'coordinates': (2.0, 1.0)}, 'b\
 box': (2.0, 1.0, 2.0, 1.0)}], 'bbox': (1.0, 1.0, 2.0, 2.0)}
-
-
         """
         return self._to_geo(na="null", show_bbox=True, drop_id=False)
 

diff --git a/geopandas/tests/test_geodataframe.py b/geopandas/tests/test_geodataframe.py
index 9e13af7..3441270 100644
--- a/geopandas/tests/test_geodataframe.py
+++ b/geopandas/tests/test_geodataframe.py
@@ -1427,3 +1427,45 @@ def test_geodataframe_crs():
     gdf = GeoDataFrame(columns=["geometry"])
     gdf.crs = "IGNF:ETRS89UTM28"
     assert gdf.crs.to_authority() == ("IGNF", "ETRS89UTM28")
+
+
+def test_geodataframe_nocrs_json():
+    # no CRS, no crs field
+    gdf = GeoDataFrame(columns=["geometry"])
+    gdf_geojson = json.loads(gdf.to_json())
+    assert "crs" not in gdf_geojson
+
+    # WGS84, no crs field (default as per spec)
+    gdf.crs = 4326
+    gdf_geojson = json.loads(gdf.to_json())
+    assert "crs" not in gdf_geojson
+
+
+def test_geodataframe_crs_json():
+    gdf = GeoDataFrame(columns=["geometry"])
+    gdf.crs = 25833
+    gdf_geojson = json.loads(gdf.to_json())
+    assert "crs" in gdf_geojson
+    assert gdf_geojson["crs"] == {
+        "type": "name",
+        "properties": {"name": "urn:ogc:def:crs:EPSG::25833"},
+    }
+    gdf_geointerface = gdf.__geo_interface__
+    assert "crs" not in gdf_geointerface
+
+
+@pytest.mark.parametrize(
+    "crs",
+    ["+proj=cea +lon_0=0 +lat_ts=45 +x_0=0 +y_0=0 +ellps=WGS84 +units=m", "IGNF:WGS84"],
+)
+def test_geodataframe_crs_nonrepresentable_json(crs):
+    gdf = GeoDataFrame(
+        [Point(1000, 1000)],
+        columns=["geometry"],
+        crs=crs,
+    )
+    with pytest.warns(
+        UserWarning, match="GeoDataFrame's CRS is not representable in URN OGC"
+    ):
+        gdf_geojson = json.loads(gdf.to_json())
+    assert "crs" not in gdf_geojson

ENH: include CRS in "to_json"
_Note (by @martinfleis): see the discussion below for details on the enhancement proposal._

#### Question about geopandas

gdf.to_json() 

> {"type": "FeatureCollection", 
> "features": [{**"id": "0500"**, "type": "Feature", **"properties": {}**, "geometry": ...}]
> }
> 

gdf.to_file('temp.geojson', driver='GeoJSON')

> {
> "type": "FeatureCollection",
> **"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::32246" } },**
> "features": [
> { "type": "Feature", **"properties": { "id": "0500" }**, "geometry": ...}]
> }


the   "id" is in "properties" for .to_file(driver='GeoJSON')  
and to_json miss the "crs"

Why they have different behavior?

and

How to:

1  let to_json()  output "crs" ?

2 let to_file()  output   each feature  with "id" as  to_json()?

Thanks.
geopandas/tests/test_geodataframe.py::test_geodataframe_crs_json geopandas/tests/test_geodataframe.py::test_geodataframe_crs_nonrepresentable_json[+proj=cea geopandas/tests/test_geodataframe.py::test_geodataframe_crs_nonrepresentable_json[IGNF:WGS84]
geopandas/tests/test_geodataframe.py::TestDataFrame::test_df_init geopandas/tests/test_geodataframe.py::TestDataFrame::test_different_geo_colname geopandas/tests/test_geodataframe.py::TestDataFrame::test_geo_getitem geopandas/tests/test_geodataframe.py::TestDataFrame::test_getitem_no_geometry geopandas/tests/test_geodataframe.py::TestDataFrame::test_geo_setitem geopandas/tests/test_geodataframe.py::TestDataFrame::test_geometry_property geopandas/tests/test_geodataframe.py::TestDataFrame::test_geometry_property_errors geopandas/tests/test_geodataframe.py::TestDataFrame::test_rename_geometry geopandas/tests/test_geodataframe.py::TestDataFrame::test_set_geometry geopandas/tests/test_geodataframe.py::TestDataFrame::test_set_geometry_col geopandas/tests/test_geodataframe.py::TestDataFrame::test_set_geometry_inplace geopandas/tests/test_geodataframe.py::TestDataFrame::test_set_geometry_series geopandas/tests/test_geodataframe.py::TestDataFrame::test_set_geometry_empty geopandas/tests/test_geodataframe.py::TestDataFrame::test_set_geometry_np_int geopandas/tests/test_geodataframe.py::TestDataFrame::test_get_geometry_invalid geopandas/tests/test_geodataframe.py::TestDataFrame::test_get_geometry_geometry_inactive geopandas/tests/test_geodataframe.py::TestDataFrame::test_align geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_wgs84_false geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_no_crs geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_geom_col geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_only_geom_column geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_na geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_bad_na geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_dropna geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_keepna geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_drop_id geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_drop_id_only_geom_column geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_json_with_duplicate_columns geopandas/tests/test_geodataframe.py::TestDataFrame::test_copy geopandas/tests/test_geodataframe.py::TestDataFrame::test_empty_copy geopandas/tests/test_geodataframe.py::TestDataFrame::test_no_geom_copy geopandas/tests/test_geodataframe.py::TestDataFrame::test_bool_index geopandas/tests/test_geodataframe.py::TestDataFrame::test_coord_slice_points geopandas/tests/test_geodataframe.py::TestDataFrame::test_from_dict geopandas/tests/test_geodataframe.py::TestDataFrame::test_from_features geopandas/tests/test_geodataframe.py::TestDataFrame::test_from_features_unaligned_properties geopandas/tests/test_geodataframe.py::TestDataFrame::test_from_features_empty_properties geopandas/tests/test_geodataframe.py::TestDataFrame::test_from_features_geom_interface_feature geopandas/tests/test_geodataframe.py::TestDataFrame::test_from_feature_collection geopandas/tests/test_geodataframe.py::TestDataFrame::test_dataframe_to_geodataframe geopandas/tests/test_geodataframe.py::TestDataFrame::test_dataframe_not_manipulated geopandas/tests/test_geodataframe.py::TestDataFrame::test_geodataframe_geointerface geopandas/tests/test_geodataframe.py::TestDataFrame::test_geodataframe_iterfeatures geopandas/tests/test_geodataframe.py::TestDataFrame::test_geodataframe_iterfeatures_non_scalars geopandas/tests/test_geodataframe.py::TestDataFrame::test_geodataframe_geojson_no_bbox geopandas/tests/test_geodataframe.py::TestDataFrame::test_geodataframe_geojson_bbox geopandas/tests/test_geodataframe.py::TestDataFrame::test_pickle geopandas/tests/test_geodataframe.py::TestDataFrame::test_pickle_method geopandas/tests/test_geodataframe.py::TestDataFrame::test_estimate_utm_crs geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_wkb geopandas/tests/test_geodataframe.py::TestDataFrame::test_to_wkt geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[None-None-left] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[None-None-inner] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[None-None-right] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[None-1-left] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[None-1-inner] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[None-1-right] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[distance-None-left] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[distance-None-inner] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[distance-None-right] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[distance-1-left] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[distance-1-inner] geopandas/tests/test_geodataframe.py::TestDataFrame::test_sjoin_nearest[distance-1-right] geopandas/tests/test_geodataframe.py::TestDataFrame::test_clip geopandas/tests/test_geodataframe.py::TestDataFrame::test_overlay[union] geopandas/tests/test_geodataframe.py::TestDataFrame::test_overlay[intersection] geopandas/tests/test_geodataframe.py::TestDataFrame::test_overlay[difference] geopandas/tests/test_geodataframe.py::TestDataFrame::test_overlay[symmetric_difference] geopandas/tests/test_geodataframe.py::TestDataFrame::test_overlay[identity] geopandas/tests/test_geodataframe.py::TestConstructor::test_dict geopandas/tests/test_geodataframe.py::TestConstructor::test_dict_of_series geopandas/tests/test_geodataframe.py::TestConstructor::test_dict_specified_geometry geopandas/tests/test_geodataframe.py::TestConstructor::test_array geopandas/tests/test_geodataframe.py::TestConstructor::test_from_frame geopandas/tests/test_geodataframe.py::TestConstructor::test_from_frame_specified_geometry geopandas/tests/test_geodataframe.py::TestConstructor::test_only_geometry geopandas/tests/test_geodataframe.py::TestConstructor::test_no_geometries geopandas/tests/test_geodataframe.py::TestConstructor::test_empty geopandas/tests/test_geodataframe.py::TestConstructor::test_column_ordering geopandas/tests/test_geodataframe.py::TestConstructor::test_overwrite_geometry geopandas/tests/test_geodataframe.py::TestConstructor::test_repeat_geo_col geopandas/tests/test_geodataframe.py::TestConstructor::test_multiindex_with_geometry_label[geometry] geopandas/tests/test_geodataframe.py::TestConstructor::test_multiindex_with_geometry_label[object] geopandas/tests/test_geodataframe.py::TestConstructor::test_default_geo_colname_none geopandas/tests/test_geodataframe.py::TestConstructor::test_multiindex_geometry_colname_2_level geopandas/tests/test_geodataframe.py::TestConstructor::test_multiindex_geometry_colname_3_level geopandas/tests/test_geodataframe.py::TestConstructor::test_multiindex_geometry_colname_3_level_new_col geopandas/tests/test_geodataframe.py::TestConstructor::test_assign_cols_using_index geopandas/tests/test_geodataframe.py::test_geodataframe_crs geopandas/tests/test_geodataframe.py::test_geodataframe_nocrs_json