diff --git a/src/core/providers/arcgis/qgsarcgisrestutils.cpp b/src/core/providers/arcgis/qgsarcgisrestutils.cpp
index 1fcea44ce8d7..07b4976f7485 100644
--- a/src/core/providers/arcgis/qgsarcgisrestutils.cpp
+++ b/src/core/providers/arcgis/qgsarcgisrestutils.cpp
@@ -563,8 +563,7 @@ QgsSymbol *QgsArcGisRestUtils::convertSymbol( const QVariantMap &symbolData )
   }
   else if ( type == QLatin1String( "esriTS" ) )
   {
-    // text symbol - not supported
-    return nullptr;
+    return parseEsriTextMarkerSymbolJson( symbolData ).release();
   }
   return nullptr;
 }
@@ -748,6 +747,83 @@ std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriPictureMarkerSymbo
   return symbol;
 }
 
+std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriTextMarkerSymbolJson( const QVariantMap &symbolData )
+{
+  QgsSymbolLayerList layers;
+
+  const QString fontFamily = symbolData.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "family" ) ).toString();
+
+  const QString chr = symbolData.value( QStringLiteral( "text" ) ).toString();
+
+  const double pointSize = symbolData.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "size" ) ).toDouble();
+
+  const QColor color = convertColor( symbolData.value( QStringLiteral( "color" ) ) );
+
+  const double esriAngle = symbolData.value( QStringLiteral( "angle" ) ).toDouble();
+
+  const double angle = 90.0 - esriAngle;
+
+  std::unique_ptr< QgsFontMarkerSymbolLayer > markerLayer = std::make_unique< QgsFontMarkerSymbolLayer >( fontFamily, chr, pointSize, color, angle );
+
+  QColor strokeColor = convertColor( symbolData.value( QStringLiteral( "borderLineColor" ) ) );
+  markerLayer->setStrokeColor( strokeColor );
+
+  double borderLineSize = symbolData.value( QStringLiteral( "borderLineSize" ) ).toDouble();
+  markerLayer->setStrokeWidth( borderLineSize );
+
+  const QString fontStyle = symbolData.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "style" ) ).toString();
+  markerLayer->setFontStyle( fontStyle );
+
+  double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
+  double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
+
+  markerLayer->setOffset( QPointF( xOffset, yOffset ) );
+  markerLayer->setOffsetUnit( Qgis::RenderUnit::Points );
+
+  markerLayer->setSizeUnit( Qgis::RenderUnit::Points );
+  markerLayer->setStrokeWidthUnit( Qgis::RenderUnit::Points );
+
+  QgsMarkerSymbolLayer::HorizontalAnchorPoint hAlign = QgsMarkerSymbolLayer::HorizontalAnchorPoint::HCenter;
+  QgsMarkerSymbolLayer::VerticalAnchorPoint vAlign = QgsMarkerSymbolLayer::VerticalAnchorPoint::VCenter;
+
+  QString horizontalAnchorPoint = symbolData.value( QStringLiteral( "horizontalAlignment" ) ).toString();
+  QString verticalAnchorPoint = symbolData.value( QStringLiteral( "verticalAlignment" ) ).toString();
+
+  if ( horizontalAnchorPoint == QString( "center" ) )
+  {
+    hAlign = QgsMarkerSymbolLayer::HorizontalAnchorPoint::HCenter;
+  }
+  else if ( horizontalAnchorPoint == QString( "left" ) )
+  {
+    hAlign = QgsMarkerSymbolLayer::HorizontalAnchorPoint::Left;
+  }
+  else if ( horizontalAnchorPoint == QString( "right" ) )
+  {
+    hAlign = QgsMarkerSymbolLayer::HorizontalAnchorPoint::Right;
+  }
+
+  if ( verticalAnchorPoint == QString( "center" ) )
+  {
+    vAlign = QgsMarkerSymbolLayer::VerticalAnchorPoint::VCenter;
+  }
+  else if ( verticalAnchorPoint == QString( "top" ) )
+  {
+    vAlign = QgsMarkerSymbolLayer::VerticalAnchorPoint::Top;
+  }
+  else if ( verticalAnchorPoint == QString( "bottom" ) )
+  {
+    vAlign = QgsMarkerSymbolLayer::VerticalAnchorPoint::Bottom;
+  }
+
+  markerLayer->setHorizontalAnchorPoint( hAlign );
+  markerLayer->setVerticalAnchorPoint( vAlign );
+
+  layers.append( markerLayer.release() );
+
+  std::unique_ptr< QgsMarkerSymbol > symbol = std::make_unique< QgsMarkerSymbol >( layers );
+  return symbol;
+}
+
 QgsAbstractVectorLayerLabeling *QgsArcGisRestUtils::convertLabeling( const QVariantList &labelingData )
 {
   if ( labelingData.empty() )
diff --git a/src/core/providers/arcgis/qgsarcgisrestutils.h b/src/core/providers/arcgis/qgsarcgisrestutils.h
index c5e9aa1c10aa..e016f64a9aed 100644
--- a/src/core/providers/arcgis/qgsarcgisrestutils.h
+++ b/src/core/providers/arcgis/qgsarcgisrestutils.h
@@ -320,6 +320,7 @@ class CORE_EXPORT QgsArcGisRestUtils
     static std::unique_ptr< QgsFillSymbol > parseEsriPictureFillSymbolJson( const QVariantMap &symbolData );
     static std::unique_ptr< QgsMarkerSymbol > parseEsriMarkerSymbolJson( const QVariantMap &symbolData );
     static std::unique_ptr< QgsMarkerSymbol > parseEsriPictureMarkerSymbolJson( const QVariantMap &symbolData );
+    static std::unique_ptr< QgsMarkerSymbol > parseEsriTextMarkerSymbolJson( const QVariantMap &symbolData );
 
     static Qgis::MarkerShape parseEsriMarkerShape( const QString &style );
 
diff --git a/tests/src/core/testqgsarcgisrestutils.cpp b/tests/src/core/testqgsarcgisrestutils.cpp
index 7c7cc724d5bc..aff166b9096c 100644
--- a/tests/src/core/testqgsarcgisrestutils.cpp
+++ b/tests/src/core/testqgsarcgisrestutils.cpp
@@ -253,6 +253,71 @@ void TestQgsArcGisRestUtils::testParseMarkerSymbol()
   QCOMPARE( markerLayer->strokeWidth(), 5.0 );
   QCOMPARE( markerLayer->strokeWidthUnit(), Qgis::RenderUnit::Points );
 
+  // esriTS
+  const QVariantMap fontMap = jsonStringToMap( "{"
+                              "\"type\": \"esriTS\","
+                              "\"text\": \"text\","
+                              "\"color\": ["
+                              "78,"
+                              "78,"
+                              "78,"
+                              "255"
+                              "],"
+                              "\"backgroundColor\": ["
+                              "0,"
+                              "0,"
+                              "0,"
+                              "0"
+                              "],"
+                              "\"borderLineSize\": 2,"
+                              "\"borderLineColor\": ["
+                              "255,"
+                              "0,"
+                              "255,"
+                              "255"
+                              "],"
+                              "\"haloSize\": 2,"
+                              "\"haloColor\": ["
+                              "0,"
+                              "255,"
+                              "0,"
+                              "255"
+                              "],"
+                              "\"verticalAlignment\": \"bottom\","
+                              "\"horizontalAlignment\": \"left\","
+                              "\"rightToLeft\": false,"
+                              "\"angle\": 45,"
+                              "\"xoffset\": 0,"
+                              "\"yoffset\": 0,"
+                              "\"kerning\": true,"
+                              "\"font\": {"
+                              "\"family\": \"Arial\","
+                              "\"size\": 12,"
+                              "\"style\": \"normal\","
+                              "\"weight\": \"bold\","
+                              "\"decoration\": \"none\""
+                              "}"
+                              "}" );
+
+  std::unique_ptr<QgsSymbol> fontSymbol( QgsArcGisRestUtils::convertSymbol( fontMap ) );
+  QgsMarkerSymbol *fontMarker = dynamic_cast< QgsMarkerSymbol * >( fontSymbol.get() );
+  QVERIFY( fontMarker );
+  QCOMPARE( fontMarker->symbolLayerCount(), 1 );
+  QgsFontMarkerSymbolLayer *fontMarkerLayer = dynamic_cast< QgsFontMarkerSymbolLayer * >( fontMarker->symbolLayer( 0 ) );
+  QVERIFY( fontMarkerLayer );
+  QCOMPARE( fontMarkerLayer->fontStyle(), QString( "normal" ) );
+  QCOMPARE( fontMarkerLayer->fontFamily(), QString( "Arial" ) );
+  QCOMPARE( fontMarkerLayer->offset(), QPointF( 0, 0 ) );
+  QCOMPARE( fontMarkerLayer->angle(), 45 );
+  QCOMPARE( fontMarkerLayer->horizontalAnchorPoint(),  QgsMarkerSymbolLayer::HorizontalAnchorPoint::Left );
+  QCOMPARE( fontMarkerLayer->verticalAnchorPoint(),  QgsMarkerSymbolLayer::VerticalAnchorPoint::Bottom );
+  QColor mainColor = fontMarkerLayer->color();
+  QCOMPARE( mainColor.name(), QStringLiteral( "#4e4e4e" ) );
+  QColor strokeColor = fontMarkerLayer->strokeColor();
+  QCOMPARE( strokeColor.name(), QStringLiteral( "#ff00ff" ) );
+  QCOMPARE( fontMarkerLayer->strokeWidth(), 2 );
+  QCOMPARE( fontMarkerLayer->character(), QString( "text" ) );
+
   // invalid json
   symbol = QgsArcGisRestUtils::parseEsriMarkerSymbolJson( QVariantMap() );
   QVERIFY( !symbol );