diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
index 0718a8886f6c..a0c66192fdc9 100644
--- a/include/libcamera/control_ids.h.in
+++ b/include/libcamera/control_ids.h.in
@@ -18,10 +18,6 @@ namespace libcamera {
 
 namespace controls {
 
-enum {
-${ids}
-};
-
 ${controls}
 
 extern const ControlIdMap controls;
diff --git a/include/libcamera/internal/control_ids.h.in b/include/libcamera/internal/control_ids.h.in
new file mode 100644
index 000000000000..c9bfaf119578
--- /dev/null
+++ b/include/libcamera/internal/control_ids.h.in
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * control_ids.h - Internal Control ID list
+ *
+ * This file is auto-generated. Do not edit.
+ */
+
+#pragma once
+
+#include <libcamera/controls.h>
+
+namespace libcamera {
+
+namespace controls {
+
+enum {
+${ids}
+};
+
+} /* namespace controls */
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 7f1f344014c4..dc7fa83ed978 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -47,4 +47,24 @@ libcamera_internal_headers = files([
     'yaml_parser.h',
 ])
 
+# Internal control and property ID mappings
+internal_control_source_files = [
+    'control_ids',
+    'property_ids',
+]
+
+internal_control_headers = []
+
+foreach header : internal_control_source_files
+    input_files = files('../../../src/libcamera/' + header + '.yaml',
+                        header + '.h.in')
+    internal_control_headers += custom_target(header + '_h',
+                                              input : input_files,
+                                              output : header + '.h',
+                                              command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@'],
+                                              install : false)
+endforeach
+
+libcamera_internal_headers += internal_control_headers
+
 subdir('converter')
diff --git a/include/libcamera/internal/property_ids.h.in b/include/libcamera/internal/property_ids.h.in
new file mode 100644
index 000000000000..15f4950953b4
--- /dev/null
+++ b/include/libcamera/internal/property_ids.h.in
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * property_ids.h - Internal Property ID list
+ *
+ * This file is auto-generated. Do not edit.
+ */
+
+#pragma once
+
+#include <libcamera/controls.h>
+
+namespace libcamera {
+
+namespace properties {
+
+enum {
+${ids}
+};
+
+} /* namespace properties */
+
+} /* namespace libcamera */
diff --git a/include/libcamera/property_ids.h.in b/include/libcamera/property_ids.h.in
index ff0194083af0..0fbdcc0c8504 100644
--- a/include/libcamera/property_ids.h.in
+++ b/include/libcamera/property_ids.h.in
@@ -17,10 +17,6 @@ namespace libcamera {
 
 namespace properties {
 
-enum {
-${ids}
-};
-
 ${controls}
 
 namespace draft {
diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp
index a47ae3a9e5cb..8e083396cb01 100644
--- a/src/ipa/rpi/common/ipa_base.cpp
+++ b/src/ipa/rpi/common/ipa_base.cpp
@@ -11,9 +11,12 @@
 
 #include <libcamera/base/log.h>
 #include <libcamera/base/span.h>
+
 #include <libcamera/control_ids.h>
 #include <libcamera/property_ids.h>
 
+#include "libcamera/internal/control_ids.h"
+
 #include "controller/af_algorithm.h"
 #include "controller/af_status.h"
 #include "controller/agc_algorithm.h"
diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in
index 5fb1c2c30558..20ef147ab826 100644
--- a/src/libcamera/control_ids.cpp.in
+++ b/src/libcamera/control_ids.cpp.in
@@ -10,8 +10,10 @@
 #include <libcamera/control_ids.h>
 #include <libcamera/controls.h>
 
+#include "libcamera/internal/control_ids.h"
+
 /**
- * \file control_ids.h
+ * \file libcamera/control_ids.h
  * \brief Camera control identifiers
  */
 
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index b24f82965764..e7f5edb2f39b 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -131,6 +131,7 @@ foreach source : control_source_files
     control_sources += custom_target(source + '_cpp',
                                      input : input_files,
                                      output : source + '.cpp',
+                                     depends : internal_control_headers,
                                      command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@'])
 endforeach
 
diff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in
index f917e3349766..c7dbf9838411 100644
--- a/src/libcamera/property_ids.cpp.in
+++ b/src/libcamera/property_ids.cpp.in
@@ -7,10 +7,11 @@
  * This file is auto-generated. Do not edit.
  */
 
+#include "libcamera/internal/property_ids.h"
 #include <libcamera/property_ids.h>
 
 /**
- * \file property_ids.h
+ * \file libcamera/property_ids.h
  * \brief Camera property identifiers
  */
 
