diff --git a/docs/api/examples/controller_get_templateidbaseconfig.txt b/docs/api/examples/controller_get_templateidbaseconfig.txt
new file mode 100644
index 000000000..9e3d9c25c
--- /dev/null
+++ b/docs/api/examples/controller_get_templateidbaseconfig.txt
@@ -0,0 +1,17 @@
+curl -i -X 'http://localhost:3080/v2/templates/19021f99-e36f-394d-b4a1-8aaa902ab9cc/base-config/vpcs_base_config.txt'
+
+GET /v2/templates/19021f99-e36f-394d-b4a1-8aaa902ab9cc/base-config/vpcs_base_config.txt HTTP/1.1
+
+HTTP/1.1 200 OK
+Connection: close
+X-Route: /v2/templates/{template_id}/base-config/{filename}
+Server: Python/3.14 GNS3/2.2.60.dev1+713dbb7f
+Content-Type: application/json
+Content-Length: 144
+Date: Mon, 18 May 2026 18:34:10 GMT
+
+{
+ "content": "# test config\n\ndhcp\n",
+ "filename": "vpcs_base_config.txt",
+ "template_id": "19021f99-e36f-394d-b4a1-8aaa902ab9cc"
+}
\ No newline at end of file
diff --git a/docs/api/examples/controller_get_templatesbaseconfigs.txt b/docs/api/examples/controller_get_templatesbaseconfigs.txt
new file mode 100644
index 000000000..263cbfa1f
--- /dev/null
+++ b/docs/api/examples/controller_get_templatesbaseconfigs.txt
@@ -0,0 +1,44 @@
+curl -i -X 'http://localhost:3080/v2/templates/base-configs'
+
+GET /v2/templates/base-configs HTTP/1.1
+
+HTTP/1.1 200 OK
+Connection: close
+X-Route: /v2/templates/base-configs
+Server: Python/3.14 GNS3/2.2.60.dev1+713dbb7f
+Content-Type: application/json
+Content-Length: 576
+Date: Mon, 18 May 2026 18:28:53 GMT
+
+[
+ {
+ "filename": "config1.txt"
+ },
+ {
+ "filename": "config2.txt"
+ },
+ {
+ "filename": "ios_base_startup-config.txt"
+ },
+ {
+ "filename": "ios_etherswitch_startup-config.txt"
+ },
+ {
+ "filename": "iou_l2_base_startup-config.txt"
+ },
+ {
+ "filename": "iou_l3_base_startup-config.txt"
+ },
+ {
+ "filename": "test_config.txt"
+ },
+ {
+ "filename": "update_test.txt"
+ },
+ {
+ "filename": "vpcs_base_config.txt"
+ },
+ {
+ "filename": "vpcs_base_config2.txt"
+ }
+]
\ No newline at end of file
diff --git a/docs/api/examples/controller_put_templateidbaseconfig.txt b/docs/api/examples/controller_put_templateidbaseconfig.txt
new file mode 100644
index 000000000..c70e315b8
--- /dev/null
+++ b/docs/api/examples/controller_put_templateidbaseconfig.txt
@@ -0,0 +1,17 @@
+curl -i -X 'http://localhost:3080/v2/templates/19021f99-e36f-394d-b4a1-8aaa902ab9cc/base-config/vpcs_base_config.txt'
+
+PUT /v2/templates/19021f99-e36f-394d-b4a1-8aaa902ab9cc/base-config/vpcs_base_config.txt HTTP/1.1
+
+HTTP/1.1 200 OK
+Connection: close
+X-Route: /v2/templates/{template_id}/base-config/{filename}
+Server: Python/3.14 GNS3/2.2.60.dev1+713dbb7f
+Content-Type: application/json
+Content-Length: 144
+Date: Mon, 18 May 2026 18:37:57 GMT
+
+{
+ "content": "# test config\n\ndhcp\n",
+ "filename": "vpcs_base_config.txt",
+ "template_id": "19021f99-e36f-394d-b4a1-8aaa902ab9cc"
+}
\ No newline at end of file
diff --git a/docs/api/v2/controller/template/templatesbaseconfigs.rst b/docs/api/v2/controller/template/templatesbaseconfigs.rst
new file mode 100644
index 000000000..dc4de3e8c
--- /dev/null
+++ b/docs/api/v2/controller/template/templatesbaseconfigs.rst
@@ -0,0 +1,38 @@
+GET /v2/templates/base-configs
+------------------------------------------------------------------------------------------------------------------------------------------
+
+.. contents::
+
+GET /v2/templates/base-configs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+List all available base configuration files
+
+Response status codes
+**********************
+- **200**: List of base configuration files returned
+
+Output
+*******
+
+.. raw:: html
+
+
+
+ | Name |
+ Type |
+ Description |
+
+
+
+ | filename |
+ string |
+ Name of the base configuration file |
+
+
+
+
+Sample session
+***************
+
+.. literalinclude:: ../../../examples/controller_get_templatesbaseconfigs.txt
\ No newline at end of file
diff --git a/docs/api/v2/controller/template/templatestemplateidbaseconfigfilename.rst b/docs/api/v2/controller/template/templatestemplateidbaseconfigfilename.rst
new file mode 100644
index 000000000..98b356270
--- /dev/null
+++ b/docs/api/v2/controller/template/templatestemplateidbaseconfigfilename.rst
@@ -0,0 +1,90 @@
+/v2/templates/{template_id}/base-config/{filename}
+------------------------------------------------------------------------------------------------------------------------------------------
+
+.. contents::
+
+GET /v2/templates/**{template_id}**/base-configs/**{filename}**
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+List all available base configuration files
+
+Response status codes
+**********************
+- **200**: List of base configuration files returned
+
+Output
+*******
+
+.. raw:: html
+
+
+
+ | Name |
+ Type |
+ Description |
+
+
+
+ | filename |
+ string |
+ Name of the base configuration file |
+
+
+
+
+Sample session
+***************
+
+.. literalinclude:: ../../../examples/controller_get_templateidbaseconfig.txt
+
+
+PUT /v2/templates/**{template_id}**/base-config/**{filename}**
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Update base configuration file content
+
+Response status codes
+**********************
+- **200**: File updated
+- **404**: File not found
+
+Input
+*******
+
+.. raw:: html
+
+
+
+ | Name |
+ Mandatory |
+ Type |
+ Description |
+
+
+ | content | ✔ | string | New file content |
+
+
+
+Output
+*******
+
+.. raw:: html
+
+
+
+ | Name |
+ Mandatory |
+ Type |
+ Description |
+
+
+ | template_id | ✔ | string | Template UUID |
+ | filename | ✔ | string | Base configuration filename |
+ | content | ✔ | string | Updated file content |
+
+
+
+Sample session
+***************
+
+.. literalinclude:: ../../../examples/controller_put_templateidbaseconfig.txt
\ No newline at end of file
diff --git a/gns3server/handlers/api/controller/template_handler.py b/gns3server/handlers/api/controller/template_handler.py
index 277349429..278782276 100644
--- a/gns3server/handlers/api/controller/template_handler.py
+++ b/gns3server/handlers/api/controller/template_handler.py
@@ -22,6 +22,7 @@ from gns3server.schemas.template import TEMPLATE_USAGE_SCHEMA
import hashlib
import json
+import os
from gns3server.schemas.template import (
TEMPLATE_OBJECT_SCHEMA,
@@ -173,3 +174,131 @@ class TemplateHandler:
compute_id=request.json.get("compute_id"))
response.set_status(201)
response.json(node)
+
+ @Route.get(
+ r"/templates/{template_id}/base-config/{filename}",
+ description="Get base configuration file content",
+ )
+ def get_base_config(request, response):
+
+ controller = Controller.instance()
+
+ template_id = request.match_info["template_id"]
+
+ template = controller.template_manager.get_template(template_id)
+
+ filename = os.path.basename(request.match_info["filename"])
+
+ path = os.path.join(controller.configs_path(), filename)
+
+ try:
+ if not os.path.exists(path):
+ response.set_status(404)
+ response.json({"message": "File not found"})
+ return
+
+ with open(path, encoding="utf-8", errors="ignore") as f:
+ content = f.read()
+
+ except Exception as e:
+ response.set_status(500)
+ response.json({"message": str(e)})
+ return
+
+ response.set_status(200)
+ response.json({
+ "template_id": template.id,
+ "filename": filename,
+ "content": content
+ })
+
+ @Route.put(
+ r"/templates/{template_id}/base-config/{filename}",
+ description="Update base configuration file content",
+ parameters={
+ "template_id": "Template UUID",
+ "filename": "Base config filename"
+ },
+ status_codes={
+ 200: "File updated",
+ 400: "Invalid request",
+ 404: "Template or file not found"
+ },
+ )
+ def update_base_config(request, response):
+
+ controller = Controller.instance()
+
+ template_id = request.match_info["template_id"]
+ filename = os.path.basename(request.match_info["filename"])
+
+ try:
+ template = controller.template_manager.get_template(template_id)
+ except Exception:
+ response.set_status(404)
+ response.json({"message": "Template not found"})
+ return
+
+ path = os.path.join(controller.configs_path(), filename)
+
+ body = request.json
+
+ if not body or "content" not in body:
+ response.set_status(400)
+ response.json({"message": "Missing 'content' field"})
+ return
+
+ content = body["content"]
+
+ try:
+ os.makedirs(controller.configs_path(), exist_ok=True)
+
+ with open(path, "w", encoding="utf-8") as f:
+ f.write(content)
+
+ except OSError as e:
+ response.set_status(500)
+ response.json({"message": str(e)})
+ return
+
+ response.set_status(200)
+ response.json({
+ "template_id": template.id,
+ "filename": filename,
+ "content": content
+ })
+
+ @Route.get(
+ r"/templates/base-configs",
+ description="List all available base configuration files",
+ status_codes={
+ 200: "List of base configuration files returned"
+ }
+ )
+ def list_base_configs(request, response):
+
+ controller = Controller.instance()
+
+ configs_path = controller.configs_path()
+
+ try:
+ files = []
+
+ if os.path.exists(configs_path):
+ for filename in os.listdir(configs_path):
+
+ path = os.path.join(configs_path, filename)
+
+ if os.path.isfile(path):
+ files.append({"filename": filename})
+
+ except OSError as e:
+ response.set_status(500)
+ response.json({
+ "message": str(e)
+ })
+ return
+
+ files = sorted(files, key=lambda x: x["filename"])
+ response.set_status(200)
+ response.json(files)
diff --git a/tests/handlers/api/controller/test_template.py b/tests/handlers/api/controller/test_template.py
index 2accbffde..610b7da4b 100644
--- a/tests/handlers/api/controller/test_template.py
+++ b/tests/handlers/api/controller/test_template.py
@@ -14,8 +14,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-
+import os
import uuid
from tests.utils import asyncio_patch
@@ -955,3 +954,81 @@ async def test_create_node_from_template(controller_api, controller, project):
mock.assert_called_with(id, x=42, y=12, name=None, compute_id=None)
assert response.route == "/projects/{project_id}/templates/{template_id}"
assert response.status == 201
+
+
+async def test_get_base_config(controller_api, controller):
+
+ template_id = str(uuid.uuid4())
+
+ controller.template_manager._templates[template_id] = Template(template_id, {
+ "template_type": "vpcs",
+ "category": 0,
+ "name": "test",
+ "symbol": "guest.svg",
+ "default_name_format": "{name}-{0}",
+ "compute_id": "local"
+ })
+
+ config_path = os.path.join(controller.configs_path(), "test_config.txt")
+
+ with open(config_path, "w") as f:
+ f.write("hello config")
+
+ response = await controller_api.get(
+ f"/templates/{template_id}/base-config/test_config.txt"
+ )
+
+ assert response.status == 200
+ assert response.json["filename"] == "test_config.txt"
+ assert response.json["content"] == "hello config"
+
+async def test_update_base_config(controller_api, controller):
+
+ template_id = str(uuid.uuid4())
+
+ controller.template_manager._templates[template_id] = Template(template_id, {
+ "template_type": "vpcs",
+ "category": 0,
+ "name": "test",
+ "symbol": "guest.svg",
+ "default_name_format": "{name}-{0}",
+ "compute_id": "local"
+ })
+
+ config_path = os.path.join(controller.configs_path(), "update_test.txt")
+
+ with open(config_path, "w") as f:
+ f.write("old content")
+
+ response = await controller_api.put(
+ f"/templates/{template_id}/base-config/update_test.txt",
+ {
+ "content": "new updated content"
+ }
+ )
+
+ assert response.status == 200
+ assert response.json["content"] == "new updated content"
+
+ with open(config_path) as f:
+ assert f.read() == "new updated content"
+
+async def test_list_base_configs(controller_api, controller):
+
+ config1 = os.path.join(controller.configs_path(), "config1.txt")
+ config2 = os.path.join(controller.configs_path(), "config2.txt")
+
+ with open(config1, "w") as f:
+ f.write("test1")
+
+ with open(config2, "w") as f:
+ f.write("test2")
+
+ response = await controller_api.get("/templates/base-configs")
+
+ assert response.status == 200
+
+ filenames = [item["filename"] for item in response.json]
+
+ assert "config1.txt" in filenames
+ assert "config2.txt" in filenames
\ No newline at end of file