diff --git a/api/proto/common/commands/project_commands.proto b/api/proto/common/commands/project_commands.proto
index 979039aa74..ce51198d7a 100644
--- a/api/proto/common/commands/project_commands.proto
+++ b/api/proto/common/commands/project_commands.proto
@@ -21,6 +21,7 @@ syntax = "proto3";
 
 package kiapi.common.commands;
 
+import "common/types/base_types.proto";
 import "common/types/project_settings.proto";
 
 
@@ -32,3 +33,17 @@ message NetClassesResponse
 {
   repeated kiapi.common.project.NetClass net_classes = 1;
 }
+
+
+message ExpandTextVariables
+{
+  kiapi.common.types.DocumentSpecifier document = 1;
+
+  repeated string text = 2;
+}
+
+
+message ExpandTextVariablesResponse
+{
+  repeated string text = 1;
+}
diff --git a/api/proto/common/types/base_types.proto b/api/proto/common/types/base_types.proto
index ee0a64f814..445db81684 100644
--- a/api/proto/common/types/base_types.proto
+++ b/api/proto/common/types/base_types.proto
@@ -89,6 +89,7 @@ enum DocumentType
   DOCTYPE_PCB           = 3;
   DOCTYPE_FOOTPRINT     = 4;
   DOCTYPE_DRAWING_SHEET = 5;
+  DOCTYPE_PROJECT       = 6;
 }
 
 /**
diff --git a/common/api/api_handler_common.cpp b/common/api/api_handler_common.cpp
index f92c735504..03f13541a7 100644
--- a/common/api/api_handler_common.cpp
+++ b/common/api/api_handler_common.cpp
@@ -47,6 +47,8 @@ API_HANDLER_COMMON::API_HANDLER_COMMON() :
     registerHandler<GetTextExtents, types::Box2>( &API_HANDLER_COMMON::handleGetTextExtents );
     registerHandler<GetTextAsShapes, GetTextAsShapesResponse>(
             &API_HANDLER_COMMON::handleGetTextAsShapes );
+    registerHandler<ExpandTextVariables, ExpandTextVariablesResponse>(
+            &API_HANDLER_COMMON::handleExpandTextVariables );
 }
 
 
@@ -197,3 +199,27 @@ HANDLER_RESULT<GetTextAsShapesResponse> API_HANDLER_COMMON::handleGetTextAsShape
 
     return reply;
 }
+
+
+HANDLER_RESULT<ExpandTextVariablesResponse> API_HANDLER_COMMON::handleExpandTextVariables(
+    ExpandTextVariables& aMsg, const HANDLER_CONTEXT& aCtx )
+{
+    if( !aMsg.has_document() || aMsg.document().type() != DocumentType::DOCTYPE_PROJECT )
+    {
+        ApiResponseStatus e;
+        e.set_status( ApiStatusCode::AS_UNHANDLED );
+        // No error message, this is a flag that the server should try a different handler
+        return tl::unexpected( e );
+    }
+
+    ExpandTextVariablesResponse reply;
+    PROJECT& project = Pgm().GetSettingsManager().Prj();
+
+    for( const std::string& textMsg : aMsg.text() )
+    {
+        wxString result = ExpandTextVars( wxString::FromUTF8( textMsg ), &project );
+        reply.add_text( result.ToUTF8() );
+    }
+
+    return reply;
+}
diff --git a/include/api/api_handler_common.h b/include/api/api_handler_common.h
index b4979984a0..9659f86846 100644
--- a/include/api/api_handler_common.h
+++ b/include/api/api_handler_common.h
@@ -51,6 +51,9 @@ private:
 
     HANDLER_RESULT<commands::GetTextAsShapesResponse>
     handleGetTextAsShapes( commands::GetTextAsShapes& aMsg, const HANDLER_CONTEXT& aCtx );
+
+    HANDLER_RESULT<commands::ExpandTextVariablesResponse>
+    handleExpandTextVariables( commands::ExpandTextVariables& aMsg, const HANDLER_CONTEXT& aCtx );
 };
 
 #endif //KICAD_API_HANDLER_COMMON_H
diff --git a/pcbnew/api/api_handler_pcb.cpp b/pcbnew/api/api_handler_pcb.cpp
index e1b7fdad8a..d503fda39e 100644
--- a/pcbnew/api/api_handler_pcb.cpp
+++ b/pcbnew/api/api_handler_pcb.cpp
@@ -69,6 +69,8 @@ API_HANDLER_PCB::API_HANDLER_PCB( PCB_EDIT_FRAME* aFrame ) :
             &API_HANDLER_PCB::handleGetPadShapeAsPolygon );
     registerHandler<GetTitleBlockInfo, types::TitleBlockInfo>(
             &API_HANDLER_PCB::handleGetTitleBlockInfo );
+    registerHandler<ExpandTextVariables, ExpandTextVariablesResponse>(
+            &API_HANDLER_PCB::handleExpandTextVariables );
 
     registerHandler<InteractiveMoveItems, Empty>( &API_HANDLER_PCB::handleInteractiveMoveItems );
     registerHandler<GetNets, NetsResponse>( &API_HANDLER_PCB::handleGetNets );
@@ -694,6 +696,34 @@ HANDLER_RESULT<types::TitleBlockInfo> API_HANDLER_PCB::handleGetTitleBlockInfo(
 }
 
 
+HANDLER_RESULT<ExpandTextVariablesResponse> API_HANDLER_PCB::handleExpandTextVariables(
+    ExpandTextVariables& aMsg, const HANDLER_CONTEXT& aCtx )
+{
+    HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.document() );
+
+    if( !documentValidation )
+        return tl::unexpected( documentValidation.error() );
+
+    ExpandTextVariablesResponse reply;
+    BOARD* board = frame()->GetBoard();
+
+    std::function<bool( wxString* )> textResolver =
+            [&]( wxString* token ) -> bool
+            {
+                // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
+                return board->ResolveTextVar( token, 0 );
+            };
+
+    for( const std::string& textMsg : aMsg.text() )
+    {
+        wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver );
+        reply.add_text( text.ToUTF8() );
+    }
+
+    return reply;
+}
+
+
 HANDLER_RESULT<Empty> API_HANDLER_PCB::handleInteractiveMoveItems( InteractiveMoveItems& aMsg,
                                                                    const HANDLER_CONTEXT& aCtx )
 {
diff --git a/pcbnew/api/api_handler_pcb.h b/pcbnew/api/api_handler_pcb.h
index 872647831f..8ad862e576 100644
--- a/pcbnew/api/api_handler_pcb.h
+++ b/pcbnew/api/api_handler_pcb.h
@@ -27,6 +27,7 @@
 #include <api/board/board_commands.pb.h>
 #include <api/board/board_types.pb.h>
 #include <api/common/commands/editor_commands.pb.h>
+#include <api/common/commands/project_commands.pb.h>
 #include <kiid.h>
 #include <properties/property_mgr.h>
 
@@ -81,6 +82,9 @@ private:
     HANDLER_RESULT<types::TitleBlockInfo> handleGetTitleBlockInfo( commands::GetTitleBlockInfo& aMsg,
             const HANDLER_CONTEXT& aCtx );
 
+    HANDLER_RESULT<commands::ExpandTextVariablesResponse>
+    handleExpandTextVariables( commands::ExpandTextVariables& aMsg, const HANDLER_CONTEXT& aCtx );
+
     HANDLER_RESULT<Empty> handleInteractiveMoveItems( InteractiveMoveItems& aMsg,
                                                       const HANDLER_CONTEXT& aCtx );