diff --git a/Documentation/release/dev/gltf_load_from_stream.md b/Documentation/release/dev/gltf_load_from_stream.md
new file mode 100644
index 0000000000000000000000000000000000000000..2729aa04a0b917d5d72c3748388f401b7063657d
--- /dev/null
+++ b/Documentation/release/dev/gltf_load_from_stream.md
@@ -0,0 +1,5 @@
+## Import GLTF scenes from in-memory streams
+
+The `vtkGLTFImporter` now accepts a resource stream to load the scene from.
+
+![](https://vtk.org/files/ExternalData/SHA512/a40ba06e652140ed031e7cda5597b2cfa84b42ab2ae05ca068c015432e5302f00c72bea03b2bb9e6532181fb0a33ec4abc7f178ad2292d57cb82d6bc9ee97bf7)
diff --git a/IO/Import/Testing/Cxx/CMakeLists.txt b/IO/Import/Testing/Cxx/CMakeLists.txt
index adbc456562801b2d364facd6f040f3e0a54a7a50..76cc48a52377dcae9183fe38b55136e1aa19562a 100644
--- a/IO/Import/Testing/Cxx/CMakeLists.txt
+++ b/IO/Import/Testing/Cxx/CMakeLists.txt
@@ -98,32 +98,41 @@ vtk_add_test_cxx(vtkIOImportCxxTests tests
   )
 
 vtk_add_test_cxx(vtkIOImportCxxTests tests
-  TestGLTFImporter,TestGLTFImporter.cxx DATA{../Data/glTF/Avocado/Avocado.glb} -1 1 0 0
+  TestGLTFImporter,TestGLTFImporter.cxx DATA{../Data/glTF/Avocado/Avocado.glb} 0 -1 1 0 0
+)
+
+vtk_add_test_cxx(vtkIOImportCxxTests tests
+  TestGLTFImporterStream,TestGLTFImporter.cxx DATA{../Data/glTF/Avocado/Avocado.glb} 1 -1 1 0 0
+)
+
+vtk_add_test_cxx(vtkIOImportCxxTests tests
+  LOOSE_VALID
+  TestGLTFImporterPBR,TestGLTFImporter.cxx DATA{../Data/glTF/WaterBottle/WaterBottle.glb} 0 -1 1 0 0
 )
 
 vtk_add_test_cxx(vtkIOImportCxxTests tests
   LOOSE_VALID
-  TestGLTFImporterPBR,TestGLTFImporter.cxx DATA{../Data/glTF/WaterBottle/WaterBottle.glb} -1 1 0 0
+  TestGLTFImporterPBRStream,TestGLTFImporter.cxx DATA{../Data/glTF/WaterBottle/WaterBottle.glb} 1 -1 1 0 0
 )
 
 vtk_add_test_cxx(vtkIOImportCxxTests tests
-  TestGLTFImporterKHRLightsPunctual,TestGLTFImporter.cxx DATA{../Data/glTF/Lights/lights.gltf} 0 1 5 1
+  TestGLTFImporterKHRLightsPunctual,TestGLTFImporter.cxx DATA{../Data/glTF/Lights/lights.gltf} 0 0 1 5 1
 )
 
 vtk_add_test_cxx(vtkIOImportCxxTests tests
-  TestGLTFImporterCamera,TestGLTFImporter.cxx DATA{../Data/glTF/Cameras/Cameras.gltf} 1 1 0 2
+  TestGLTFImporterCamera,TestGLTFImporter.cxx DATA{../Data/glTF/Cameras/Cameras.gltf} 0 1 1 0 2
 )
 
 vtk_add_test_cxx(vtkIOImportCxxTests tests
-  TestGLTFImporterUnlit,TestGLTFImporter.cxx DATA{../Data/glTF/UnlitTest/UnlitTest.glb} -1 2 0 0
+  TestGLTFImporterUnlit,TestGLTFImporter.cxx DATA{../Data/glTF/UnlitTest/UnlitTest.glb} 0 -1 2 0 0
 )
 
 vtk_add_test_cxx(vtkIOImportCxxTests tests
-  TestGLTFImporterArmature,TestGLTFImporter.cxx DATA{../Data/glTF/Armature.gltf} -1 1 0 0
+  TestGLTFImporterArmature,TestGLTFImporter.cxx DATA{../Data/glTF/Armature.gltf} 0 -1 1 0 0
 )
 
 vtk_add_test_cxx(vtkIOImportCxxTests tests
-  TestGLTFImporterURITexture,TestGLTFImporter.cxx DATA{../Data/glTF/Lantern/Lantern.gltf} -1 3 0 0
+  TestGLTFImporterURITexture,TestGLTFImporter.cxx DATA{../Data/glTF/Lantern/Lantern.gltf} 0 -1 3 0 0
    DATA{../Data/glTF/Lantern/Lantern.bin}
    DATA{../Data/glTF/Lantern/Lantern_baseColor.png}
    DATA{../Data/glTF/Lantern/Lantern_emissive.png}
diff --git a/IO/Import/Testing/Cxx/TestGLTFImporter.cxx b/IO/Import/Testing/Cxx/TestGLTFImporter.cxx
index 400a08d27072a693d0acf7d2cf3aca62ffe88a28..428a1804b0b9f34390f2da72aedbaef6919f76c4 100644
--- a/IO/Import/Testing/Cxx/TestGLTFImporter.cxx
+++ b/IO/Import/Testing/Cxx/TestGLTFImporter.cxx
@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
 // SPDX-License-Identifier: BSD-3-Clause
 
+#include <vtkFileResourceStream.h>
 #include <vtkGLTFImporter.h>
 #include <vtkLightCollection.h>
 #include <vtkRegressionTestImage.h>
@@ -11,19 +12,42 @@
 
 int TestGLTFImporter(int argc, char* argv[])
 {
-  if (argc < 6)
+  if (argc < 7)
   {
-    std::cout << "Usage: " << argv[0]
-              << " <gltf file> <camera index> <expected nb of actors> <expected nb of lights> "
-                 "<expected nb of cameras>"
-              << std::endl;
+    std::cout
+      << "Usage: " << argv[0]
+      << " <gltf file> <use_stream> <camera index> <expected nb of actors> <expected nb of lights> "
+         "<expected nb of cameras>"
+      << std::endl;
     return EXIT_FAILURE;
   }
 
-  vtkIdType cameraIndex = atoi(argv[2]);
+  vtkIdType cameraIndex = atoi(argv[3]);
 
   vtkNew<vtkGLTFImporter> importer;
-  importer->SetFileName(argv[1]);
+  if (atoi(argv[2]) > 0)
+  {
+    bool is_binary = false;
+    std::string extension = vtksys::SystemTools::GetFilenameLastExtension(argv[1]);
+    if (extension == ".glb")
+    {
+      is_binary = true;
+    }
+    vtkNew<vtkFileResourceStream> file;
+    file->Open(argv[1]);
+    if (file->EndOfStream())
+    {
+      std::cerr << "Can not open test file " << argv[1] << std::endl;
+      return EXIT_FAILURE;
+    }
+    importer->SetStream(file);
+    importer->SetStreamIsBinary(is_binary);
+  }
+  else
+  {
+    importer->SetFileName(argv[1]);
+  }
+
   importer->ImportArmatureOn();
 
   vtkNew<vtkRenderWindow> renderWindow;
@@ -43,19 +67,19 @@ int TestGLTFImporter(int argc, char* argv[])
     return EXIT_FAILURE;
   }
 
-  if (importer->GetImportedActors()->GetNumberOfItems() != atoi(argv[3]))
+  if (importer->GetImportedActors()->GetNumberOfItems() != atoi(argv[4]))
   {
     std::cerr << "ERROR: Unexpected number of imported actors: "
               << importer->GetImportedActors()->GetNumberOfItems() << "\n";
     return EXIT_FAILURE;
   }
-  if (importer->GetImportedLights()->GetNumberOfItems() != atoi(argv[4]))
+  if (importer->GetImportedLights()->GetNumberOfItems() != atoi(argv[5]))
   {
     std::cerr << "ERROR: Unexpected number of imported lights: "
               << importer->GetImportedActors()->GetNumberOfItems() << "\n";
     return EXIT_FAILURE;
   }
-  if (importer->GetImportedCameras()->GetNumberOfItems() != atoi(argv[5]))
+  if (importer->GetImportedCameras()->GetNumberOfItems() != atoi(argv[6]))
   {
     std::cerr << "ERROR: Unexpected number of imported cameras: "
               << importer->GetImportedActors()->GetNumberOfItems() << "\n";
diff --git a/IO/Import/Testing/Data/Baseline/TestGLTFImporterPBRStream.png.sha512 b/IO/Import/Testing/Data/Baseline/TestGLTFImporterPBRStream.png.sha512
new file mode 100644
index 0000000000000000000000000000000000000000..62bcd42965736512ddb48103aa12fab14da064af
--- /dev/null
+++ b/IO/Import/Testing/Data/Baseline/TestGLTFImporterPBRStream.png.sha512
@@ -0,0 +1 @@
+c59b73eff93b98942a8bd18743d71ca6d0d4de001c0c0765a7684b0bc4ca1d3b8b46805854b1f77616407fbdc8fcded7ea777cff4ea511a585a6864970ae5d71
diff --git a/IO/Import/Testing/Data/Baseline/TestGLTFImporterStream.png.sha512 b/IO/Import/Testing/Data/Baseline/TestGLTFImporterStream.png.sha512
new file mode 100644
index 0000000000000000000000000000000000000000..3178a03cbab456700f1943e79e0c9efdde10fb4a
--- /dev/null
+++ b/IO/Import/Testing/Data/Baseline/TestGLTFImporterStream.png.sha512
@@ -0,0 +1 @@
+a40ba06e652140ed031e7cda5597b2cfa84b42ab2ae05ca068c015432e5302f00c72bea03b2bb9e6532181fb0a33ec4abc7f178ad2292d57cb82d6bc9ee97bf7
diff --git a/IO/Import/vtk.module b/IO/Import/vtk.module
index 8da982b9a6f48c78e64913f030e33479b7b32526..ef9f131c0f34aca0c730de99e0495d7443ebb431 100644
--- a/IO/Import/vtk.module
+++ b/IO/Import/vtk.module
@@ -12,6 +12,7 @@ DEPENDS
   VTK::CommonCore
   VTK::CommonExecutionModel
   VTK::CommonMisc
+  VTK::IOCore
   VTK::RenderingCore
   VTK::vtksys
 PRIVATE_DEPENDS
diff --git a/IO/Import/vtkGLTFImporter.cxx b/IO/Import/vtkGLTFImporter.cxx
index f5197e6ba6988e94f49b7a6057e012143f331f99..43cd4fb94280fa0cf13d3bea871b8c32d3a57c44 100644
--- a/IO/Import/vtkGLTFImporter.cxx
+++ b/IO/Import/vtkGLTFImporter.cxx
@@ -403,9 +403,9 @@ void vtkGLTFImporter::InitializeLoader()
 int vtkGLTFImporter::ImportBegin()
 {
   // Make sure we have a file to read.
-  if (!this->FileName)
+  if (!this->Stream && !this->FileName)
   {
-    vtkErrorMacro("A FileName must be specified.");
+    vtkErrorMacro("Neither FileName nor Stream has been specified.");
     return 0;
   }
 
@@ -422,21 +422,44 @@ int vtkGLTFImporter::ImportBegin()
 
   // Check extension
   std::vector<char> glbBuffer;
-  std::string extension = vtksys::SystemTools::GetFilenameLastExtension(this->FileName);
-  if (extension == ".glb")
+  if (this->Stream != nullptr)
   {
-    if (!this->Loader->LoadFileBuffer(this->FileName, glbBuffer))
+    // this->Stream is defined.
+    if (this->StreamIsBinary)
     {
-      vtkErrorMacro("Error loading binary data");
+      if (!this->Loader->LoadStreamBuffer(this->Stream, glbBuffer))
+      {
+        vtkErrorMacro("Error loading binary data");
+        return 0;
+      }
+    }
+
+    if (!this->Loader->LoadModelMetaDataFromStream(this->Stream, this->StreamURILoader))
+    {
+      vtkErrorMacro("Error loading model metadata");
       return 0;
     }
   }
-
-  if (!this->Loader->LoadModelMetaDataFromFile(this->FileName))
+  else
   {
-    vtkErrorMacro("Error loading model metadata");
-    return 0;
+    // this->FileName is defined.
+    std::string extension = vtksys::SystemTools::GetFilenameLastExtension(this->FileName);
+    if (extension == ".glb")
+    {
+      if (!this->Loader->LoadFileBuffer(this->FileName, glbBuffer))
+      {
+        vtkErrorMacro("Error loading binary data");
+        return 0;
+      }
+    }
+
+    if (!this->Loader->LoadModelMetaDataFromFile(this->FileName))
+    {
+      vtkErrorMacro("Error loading model metadata");
+      return 0;
+    }
   }
+
   if (!this->Loader->LoadModelData(glbBuffer))
   {
     vtkErrorMacro("Error loading model data");
@@ -1037,7 +1060,16 @@ bool vtkGLTFImporter::GetTemporalInformation(vtkIdType animationIndex, double fr
 void vtkGLTFImporter::PrintSelf(ostream& os, vtkIndent indent)
 {
   this->Superclass::PrintSelf(os, indent);
-  os << indent << "File Name: " << (this->FileName ? this->FileName : "(none)") << "\n";
+  os << indent;
+  if (this->Stream != nullptr)
+  {
+    os << "Stream (" << (this->StreamIsBinary ? "binary" : "ascii") << ")";
+  }
+  else
+  {
+    os << "File Name: " << (this->FileName ? this->FileName : "(none)");
+  }
+  os << "\n";
 }
 
 //------------------------------------------------------------------------------
diff --git a/IO/Import/vtkGLTFImporter.h b/IO/Import/vtkGLTFImporter.h
index 239d1d7fb75aa01a2a609d2144dbb2e387543cc4..79ccc4e3b23971836d07b22906ab8db6b91d5d67 100644
--- a/IO/Import/vtkGLTFImporter.h
+++ b/IO/Import/vtkGLTFImporter.h
@@ -51,16 +51,21 @@
 
 #include "vtkIOImportModule.h" // For export macro
 #include "vtkImporter.h"
-#include "vtkSmartPointer.h" // For SmartPointer
+#include "vtkResourceStream.h" // For Stream
+#include "vtkSmartPointer.h"   // For SmartPointer
+#include "vtkURILoader.h"      // For URILoader
 
 #include <map>    // For map
 #include <vector> // For vector
 
 VTK_ABI_NAMESPACE_BEGIN
+
+// Forward declarations
 class vtkActor;
 class vtkCamera;
 class vtkGLTFDocumentLoader;
 class vtkTexture;
+class vtkURILoader;
 
 class VTKIOIMPORT_EXPORT vtkGLTFImporter : public vtkImporter
 {
@@ -78,6 +83,40 @@ public:
   vtkGetFilePathMacro(FileName);
   ///@}
 
+  ///@{
+  /**
+   * Specify the glTF source stream to read from. When selecting the input method, `Stream` has a
+   * higher priority than `FileName` i.e. if a stream is provided, the filename is ignored.
+   *
+   * \note If the stream contains non-data URIs, specifying a custom uri loader is crucial.
+   * \sa SetStreamURILoader()
+   *
+   * \sa SetStreamIsBinary()
+   */
+  vtkSetSmartPointerMacro(Stream, vtkResourceStream);
+  vtkGetSmartPointerMacro(Stream, vtkResourceStream);
+  ///@}
+
+  ///@{
+  /**
+   * Specify a custom URI loader for non-data URIs in the input stream.
+   * \sa SetStream(), SetStreamIsBinary()
+   */
+  vtkSetSmartPointerMacro(StreamURILoader, vtkURILoader);
+  vtkGetSmartPointerMacro(StreamURILoader, vtkURILoader);
+  ///@}
+
+  ///@{
+  /**
+   * Set/Get whether the input stream is binary
+   *
+   * \sa SetStream()
+   */
+  vtkSetMacro(StreamIsBinary, bool);
+  vtkGetMacro(StreamIsBinary, bool);
+  vtkBooleanMacro(StreamIsBinary, bool);
+  ///@}
+
   /**
    * glTF defines multiple camera objects, but no default behavior for which camera should be
    * used. The importer will by default apply the asset's first camera. This accessor lets you use
@@ -167,6 +206,9 @@ protected:
   virtual void ApplyArmatureProperties(vtkActor* actor);
 
   char* FileName = nullptr;
+  vtkSmartPointer<vtkResourceStream> Stream;
+  vtkSmartPointer<vtkURILoader> StreamURILoader;
+  bool StreamIsBinary = false;
 
   std::map<int, vtkSmartPointer<vtkCamera>> Cameras;
   std::map<int, vtkSmartPointer<vtkTexture>> Textures;