{"id":20873,"date":"2026-10-14T12:16:00","date_gmt":"2026-10-14T10:16:00","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?p=20873"},"modified":"2026-10-09T22:41:10","modified_gmt":"2026-10-09T20:41:10","slug":"einstellungsseiten-settings-api-register-setting","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/","title":{"rendered":"Einstellungsseiten mit der Settings API und register_setting"},"content":{"rendered":"<p>Fast jedes Plugin braucht eine Seite, auf der Administratoren ein paar Werte \u00e4ndern k\u00f6nnen. Erstaunlich viele Plugins bauen sie noch von Hand: mit einem eigenen POST-Handler, einer Capability-Pr\u00fcfung hier, einer vergessenen Nonce dort und einem Dutzend einzelner Zeilen in <code>wp_options<\/code>. WordPress bietet daf\u00fcr seit Version 2.7 die Settings API. Sie stellt das Formular im gewohnten Admin-Layout dar, schickt es an <code>wp-admin\/options.php<\/code> und \u00fcbernimmt dort die Nonce-Pr\u00fcfung, Rechtepr\u00fcfung, Bereinigung und Speicherung. Seit WordPress 4.7 beschreibt <code>register_setting()<\/code> au\u00dferdem die Daten selbst: Typ, Standardwert, Sanitize-Callback und \u00fcber <code>show_in_rest<\/code> die Sichtbarkeit im REST-Endpunkt <code>\/wp\/v2\/settings<\/code>.<\/p>\n<p>Dieser Beitrag baut ein kleines, aber vollst\u00e4ndiges Plugin namens \u201eLW Settings Demo\u201c: eine Hinweisleiste, deren drei Einstellungen geb\u00fcndelt in einer einzigen Option liegen, eine Seite unter \u201eEinstellungen\u201c, einen Sanitize-Callback, der Formular und REST-API gleicherma\u00dfen bedient, und ein kurzes Skript, das dieselbe Option mit <code>apiFetch<\/code> liest und schreibt. Unterwegs wird ein einzelnes Formularfeld vom Eingabeelement bis zu seiner Zeile in <code>wp_options<\/code> verfolgt, samt der Stelle, an der der REST-Weg abzweigt. Der Core-Quelltext wurde am 29.09.2026 gelesen; laut Versionspr\u00fcfung von WordPress.org ist 7.1.2 die aktuelle Version, das Beispiel setzt mindestens WordPress 6.6 voraus.<\/p>\n<h2>Vier Aufgaben, vier Funktionsgruppen<\/h2>\n<p>Die Settings API zerlegt eine Einstellungsseite in klar getrennte Aufgaben. Jede Funktionsgruppe geh\u00f6rt an einen eigenen Hook. Wer das durcheinanderbringt, bekommt meist eine Seite, die sauber erscheint, aber nichts speichert.<\/p>\n<table>\n<thead>\n<tr>\n<th>Funktion<\/th>\n<th>Hook<\/th>\n<th>Aufgabe<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>add_options_page()<\/code><\/td>\n<td><code>admin_menu<\/code><\/td>\n<td>Men\u00fceintrag unter \u201eEinstellungen\u201c, Capability f\u00fcr die Anzeige des Men\u00fcpunkts<\/td>\n<\/tr>\n<tr>\n<td><code>register_setting()<\/code><\/td>\n<td><code>init<\/code> (Erl\u00e4uterung weiter unten)<\/td>\n<td>Tr\u00e4gt die Option in die Freigabeliste einer Gruppe ein, h\u00e4ngt Sanitize-Callback, Standardwert und REST-Schema an<\/td>\n<\/tr>\n<tr>\n<td><code>add_settings_section()<\/code>, <code>add_settings_field()<\/code><\/td>\n<td><code>admin_init<\/code><\/td>\n<td>Layout: \u00dcberschriften, Beschriftungen, Callbacks f\u00fcr die Eingabeelemente<\/td>\n<\/tr>\n<tr>\n<td><code>settings_fields()<\/code>, <code>do_settings_sections()<\/code><\/td>\n<td>Seiten-Callback<\/td>\n<td>Versteckte Felder mit Nonce, danach alle Abschnitte und Felder der Seite<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><code>add_options_page()<\/code> ist nur eine d\u00fcnne H\u00fclle um <code>add_submenu_page()<\/code> mit <code>options-general.php<\/code> als Elternseite. Die dort \u00fcbergebene Capability entscheidet dar\u00fcber, ob der Men\u00fcpunkt erscheint; Benutzer ohne sie erhalten auch beim direkten Aufruf der Seitenadresse den Status 403. Der Docblock verlangt ausdr\u00fccklich, dass der Seiten-Callback die Berechtigung noch einmal selbst pr\u00fcft; deshalb beginnt die Render-Funktion im Beispiel mit <code>current_user_can( 'manage_options' )<\/code>. Beim Speichern folgt eine dritte, getrennte Pr\u00fcfung in <code>options.php<\/code>, wie der Abschnitt zum Speicherweg zeigt.<\/p>\n<p>Formular und Speicherung h\u00e4ngen \u00fcber die Optionsgruppe zusammen. <code>register_setting( 'lwsd_notice_bar', 'lwsd_settings', \u2026 )<\/code> nimmt die Option <code>lwsd_settings<\/code> in die Gruppe <code>lwsd_notice_bar<\/code> auf, und <code>settings_fields( 'lwsd_notice_bar' )<\/code> schickt genau diesen Gruppennamen als verstecktes Feld <code>option_page<\/code> mit. Die Seiten-ID, die <code>add_settings_section()<\/code> und <code>do_settings_sections()<\/code> verwenden, ist ein anderer Bezeichner; im Beispiel entspricht sie dem Men\u00fc-Slug <code>lwsd-notice-bar<\/code>.<\/p>\n<h2>Eine Option statt Dutzender<\/h2>\n<p>Das Plugin Handbook empfiehlt, zusammengeh\u00f6rige Werte als Array in einer einzigen Option abzulegen, weil Lesen und Schreiben dann in einer Datenbanktransaktion statt in vielen geschieht. Der Quelltext von <code>options.php<\/code> liefert einen zweiten, weniger offensichtlichen Grund. Beim Speichern durchl\u00e4uft die Datei <strong>alle<\/strong> Optionen, die in der gesendeten Gruppe registriert sind, und ruft f\u00fcr jede <code>update_option()<\/code> auf. Fehlt ein Feld in den POST-Daten, wird <code>null<\/code> \u00fcbergeben. Zwei Formulare in derselben Gruppe \u00fcberschreiben sich deshalb gegenseitig. Mit einer Array-Option je Formular entf\u00e4llt diese Falle, und der Sanitize-Callback sieht alle Werte gleichzeitig. Damit lassen sich Regeln \u00fcber mehrere Felder formulieren, etwa \u201ekeine aktive Hinweisleiste ohne Text\u201c.<\/p>\n<p>Der Preis ist die Serialisierung: Das Array steht als serialisierte Zeichenkette in einer Zeile, einzelne Schl\u00fcssel lassen sich per SQL nicht filtern. F\u00fcr Konfigurationswerte spielt das selten eine Rolle. F\u00fcr Daten, die abgefragt werden m\u00fcssen, zeigt <a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/web-entwicklung\/wordpress-datenbank-leistungsgrenzen-von-wp_postmeta-und-der-aufbau-eigener\/\">der Beitrag zu den Leistungsgrenzen von <code>wp_postmeta<\/code> und eigenen Tabellen<\/a> die Alternativen.<\/p>\n<h3>Autoload bewusst setzen<\/h3>\n<p>WordPress 6.6 hat die Autoload-Spalte umgebaut. Der Parameter <code>$autoload<\/code> von <code>add_option()<\/code> steht seitdem standardm\u00e4\u00dfig auf <code>null<\/code>. In der Spalte landen <code>on<\/code> und <code>off<\/code> f\u00fcr ausdr\u00fcckliche Entscheidungen, <code>auto<\/code>, wenn niemand entschieden hat, sowie <code>auto-on<\/code> oder <code>auto-off<\/code>, wenn ein Filter dynamisch entschieden hat. Die alten Werte <code>yes<\/code> und <code>no<\/code> gelten seit 6.7 als veraltet. Optionen \u00fcber 150.000 Bytes werden standardm\u00e4\u00dfig nicht automatisch geladen (Filter <code>wp_max_autoloaded_option_size<\/code>). Laut <code>wp_autoload_values_to_autoload()<\/code> wird <code>auto<\/code> derzeit noch geladen; die Dev Note zu 6.6 h\u00e4lt aber fest, dass sich dieser Standard in k\u00fcnftigen Versionen \u00e4ndern kann.<\/p>\n<p>F\u00fcr die Settings API ist das relevant, weil <code>options.php<\/code> <code>update_option()<\/code> ohne Autoload-Argument aufruft. Existiert die Option noch nicht, ruft <code>update_option()<\/code> daf\u00fcr <code>add_option()<\/code> auf, und die Zeile entsteht mit <code>auto<\/code>. Wer Autoload ausdr\u00fccklich festlegen will, legt die Option selbst an. Das Beispiel macht das bei der Aktivierung mit <code>add_option( \u2026, true )<\/code>, weil die Hinweisleiste bei jedem Frontend-Aufruf gelesen wird.<\/p>\n<p>Sp\u00e4tere Aufrufe von <code>update_option()<\/code> ohne Autoload-Argument behalten ein ausdr\u00fcckliches <code>on<\/code> oder <code>off<\/code> bei; neu bewertet werden nur die <code>auto<\/code>-Varianten. Eine Option, die nur auf einer einzigen Admin-Seite gebraucht wird, bek\u00e4me stattdessen <code>false<\/code>.<\/p>\n<p>Nachtr\u00e4glich l\u00e4sst sich Autoload mit <code>wp_set_option_autoload()<\/code> (seit 6.4) \u00e4ndern, ohne den gespeicherten Optionswert anzufassen. <code>update_option()<\/code> \u00fcbernimmt einen neuen Autoload-Wert dagegen nur, wenn sich auch der Optionswert \u00e4ndert. Was die einzelnen Werte der Spalte in der Praxis bedeuten, behandelt <span class=\"lw-artikel-geplant\">ein eigener, geplanter Beitrag zu Autoload<\/span>.<\/p>\n<h2>Das vollst\u00e4ndige Plugin<\/h2>\n<p>Die Hauptdatei enth\u00e4lt alles: Registrierung, Seite, Felder, Sanitize-Callback, Frontend-Ausgabe und Aktivierung. Das Pr\u00e4fix <code>lwsd_<\/code> gilt durchgehend f\u00fcr Funktionen, Konstanten, die Option und die HTML-IDs.<\/p>\n<pre><code>&lt;?php\n\/**\n * Plugin Name:       LW Settings Demo\n * Description:       Notice bar with a settings page built on the Settings API, also available via \/wp\/v2\/settings.\n * Version:           1.0.0\n * Requires at least: 6.6\n * Requires PHP:      7.4\n * Author:            Lukas Wojcik\n * License:           GPL-2.0-or-later\n * License URI:       https:\/\/www.gnu.org\/licenses\/gpl-2.0.html\n * Text Domain:       lw-settings-demo\n *\n * @package LW_Settings_Demo\n *\/\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\nconst LWSD_OPTION = 'lwsd_settings';   \/\/ Row in wp_options.\nconst LWSD_GROUP  = 'lwsd_notice_bar'; \/\/ Option group, sent as option_page.\nconst LWSD_PAGE   = 'lwsd-notice-bar'; \/\/ Menu slug and settings page ID.\n\n\/**\n * Default values of the bundled option.\n *\n * @return array\n *\/\nfunction lwsd_default_settings() {\n\treturn array(\n\t\t'enabled'  =&gt; false,\n\t\t'message'  =&gt; '',\n\t\t'position' =&gt; 'bottom',\n\t);\n}\n\n\/**\n * Returns the stored settings merged with the defaults.\n *\n * @return array\n *\/\nfunction lwsd_get_settings() {\n\t$settings = get_option( LWSD_OPTION );\n\n\tif ( ! is_array( $settings ) ) {\n\t\t$settings = array();\n\t}\n\n\treturn wp_parse_args( $settings, lwsd_default_settings() );\n}\n\n\/**\n * Registers the option on init, so admin screens and REST requests both know it.\n *\/\nfunction lwsd_register_setting() {\n\tregister_setting(\n\t\tLWSD_GROUP,\n\t\tLWSD_OPTION,\n\t\tarray(\n\t\t\t'type'              =&gt; 'object',\n\t\t\t'label'             =&gt; __( 'Notice bar', 'lw-settings-demo' ),\n\t\t\t'description'       =&gt; __( 'Visibility, text and position of the notice bar.', 'lw-settings-demo' ),\n\t\t\t'sanitize_callback' =&gt; 'lwsd_sanitize_settings',\n\t\t\t'default'           =&gt; lwsd_default_settings(),\n\t\t\t'show_in_rest'      =&gt; array(\n\t\t\t\t'schema' =&gt; array(\n\t\t\t\t\t'type'       =&gt; 'object',\n\t\t\t\t\t'properties' =&gt; array(\n\t\t\t\t\t\t'enabled'  =&gt; array(\n\t\t\t\t\t\t\t'type' =&gt; 'boolean',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t'message'  =&gt; array(\n\t\t\t\t\t\t\t'type'      =&gt; 'string',\n\t\t\t\t\t\t\t'maxLength' =&gt; 200,\n\t\t\t\t\t\t),\n\t\t\t\t\t\t'position' =&gt; array(\n\t\t\t\t\t\t\t'type' =&gt; 'string',\n\t\t\t\t\t\t\t'enum' =&gt; array( 'top', 'bottom' ),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t);\n}\nadd_action( 'init', 'lwsd_register_setting' );\n\n\/**\n * Sanitizes the whole settings array.\n *\n * Runs for options.php and for REST writes, because both end in update_option().\n * Must be idempotent: on the first save of a missing option it runs twice.\n *\n * @param mixed $input Raw value.\n * @return array\n *\/\nfunction lwsd_sanitize_settings( $input ) {\n\t$defaults = lwsd_default_settings();\n\n\tif ( ! is_array( $input ) ) {\n\t\treturn $defaults;\n\t}\n\n\t$message  = isset( $input['message'] ) ? sanitize_text_field( $input['message'] ) : '';\n\t$position = isset( $input['position'] ) ? sanitize_key( $input['position'] ) : '';\n\n\t$output = array(\n\t\t'enabled'  =&gt; ! empty( $input['enabled'] ),\n\t\t'message'  =&gt; mb_substr( $message, 0, 200 ),\n\t\t'position' =&gt; in_array( $position, array( 'top', 'bottom' ), true ) ? $position : $defaults['position'],\n\t);\n\n\tif ( $output['enabled'] &amp;&amp; '' === $output['message'] ) {\n\t\t$output['enabled'] = false;\n\n\t\t\/\/ add_settings_error() lives in wp-admin\/includes\/template.php,\n\t\t\/\/ which a REST request does not load.\n\t\tif ( function_exists( 'add_settings_error' ) ) {\n\t\t\tadd_settings_error(\n\t\t\t\tLWSD_OPTION,\n\t\t\t\t'lwsd_empty_message',\n\t\t\t\t__( 'The notice bar stays off until a message is entered.', 'lw-settings-demo' ),\n\t\t\t\t'warning'\n\t\t\t);\n\t\t}\n\t}\n\n\treturn $output;\n}\n\n\/**\n * Adds the page below Settings and loads its script only there.\n *\/\nfunction lwsd_add_settings_page() {\n\t$hook_suffix = add_options_page(\n\t\t__( 'Notice bar', 'lw-settings-demo' ),\n\t\t__( 'Notice bar', 'lw-settings-demo' ),\n\t\t'manage_options',\n\t\tLWSD_PAGE,\n\t\t'lwsd_render_settings_page'\n\t);\n\n\tif ( $hook_suffix ) {\n\t\tadd_action( 'load-' . $hook_suffix, 'lwsd_load_settings_page' );\n\t}\n}\nadd_action( 'admin_menu', 'lwsd_add_settings_page' );\n\n\/**\n * Runs only when the settings page is requested.\n *\/\nfunction lwsd_load_settings_page() {\n\tadd_action( 'admin_enqueue_scripts', 'lwsd_enqueue_admin_script' );\n}\n\n\/**\n * Enqueues the REST demo script.\n *\/\nfunction lwsd_enqueue_admin_script() {\n\twp_enqueue_script(\n\t\t'lwsd-settings-rest',\n\t\tplugins_url( 'js\/settings-rest.js', __FILE__ ),\n\t\tarray( 'wp-api-fetch', 'wp-i18n' ),\n\t\t'1.0.0',\n\t\tarray( 'in_footer' =&gt; true )\n\t);\n\twp_set_script_translations( 'lwsd-settings-rest', 'lw-settings-demo' );\n}\n\n\/**\n * Declares section and fields. Only needed on admin screens.\n *\/\nfunction lwsd_register_fields() {\n\tadd_settings_section(\n\t\t'lwsd_section_main',\n\t\t__( 'Display', 'lw-settings-demo' ),\n\t\t'lwsd_render_section',\n\t\tLWSD_PAGE\n\t);\n\n\tadd_settings_field(\n\t\t'lwsd_enabled',\n\t\t__( 'Show notice bar', 'lw-settings-demo' ),\n\t\t'lwsd_render_enabled_field',\n\t\tLWSD_PAGE,\n\t\t'lwsd_section_main',\n\t\tarray( 'label_for' =&gt; 'lwsd_enabled' )\n\t);\n\n\tadd_settings_field(\n\t\t'lwsd_message',\n\t\t__( 'Message', 'lw-settings-demo' ),\n\t\t'lwsd_render_message_field',\n\t\tLWSD_PAGE,\n\t\t'lwsd_section_main',\n\t\tarray( 'label_for' =&gt; 'lwsd_message' )\n\t);\n\n\tadd_settings_field(\n\t\t'lwsd_position',\n\t\t__( 'Position', 'lw-settings-demo' ),\n\t\t'lwsd_render_position_field',\n\t\tLWSD_PAGE,\n\t\t'lwsd_section_main',\n\t\tarray( 'label_for' =&gt; 'lwsd_position' )\n\t);\n}\nadd_action( 'admin_init', 'lwsd_register_fields' );\n\n\/**\n * Section intro.\n *\/\nfunction lwsd_render_section() {\n\techo '&lt;p&gt;' . esc_html__( 'All three values are stored together in one option.', 'lw-settings-demo' ) . '&lt;\/p&gt;';\n}\n\n\/**\n * Checkbox field.\n *\/\nfunction lwsd_render_enabled_field() {\n\t$settings = lwsd_get_settings();\n\n\tprintf(\n\t\t'&lt;input type=\"checkbox\" id=\"lwsd_enabled\" name=\"%1$s[enabled]\" value=\"1\" %2$s \/&gt;',\n\t\tesc_attr( LWSD_OPTION ),\n\t\tchecked( $settings['enabled'], true, false )\n\t);\n}\n\n\/**\n * Text field.\n *\/\nfunction lwsd_render_message_field() {\n\t$settings = lwsd_get_settings();\n\n\tprintf(\n\t\t'&lt;input type=\"text\" class=\"regular-text\" id=\"lwsd_message\" name=\"%1$s[message]\" value=\"%2$s\" maxlength=\"200\" \/&gt;',\n\t\tesc_attr( LWSD_OPTION ),\n\t\tesc_attr( $settings['message'] )\n\t);\n}\n\n\/**\n * Select field.\n *\/\nfunction lwsd_render_position_field() {\n\t$settings = lwsd_get_settings();\n\t$choices  = array(\n\t\t'top'    =&gt; __( 'Top of the page', 'lw-settings-demo' ),\n\t\t'bottom' =&gt; __( 'Bottom of the page', 'lw-settings-demo' ),\n\t);\n\n\tprintf( '&lt;select id=\"lwsd_position\" name=\"%s[position]\"&gt;', esc_attr( LWSD_OPTION ) );\n\n\tforeach ( $choices as $value =&gt; $label ) {\n\t\tprintf(\n\t\t\t'&lt;option value=\"%1$s\" %2$s&gt;%3$s&lt;\/option&gt;',\n\t\t\tesc_attr( $value ),\n\t\t\tselected( $settings['position'], $value, false ),\n\t\t\tesc_html( $label )\n\t\t);\n\t}\n\n\techo '&lt;\/select&gt;';\n}\n\n\/**\n * Settings page. The form posts to options.php, not to this page.\n *\/\nfunction lwsd_render_settings_page() {\n\tif ( ! current_user_can( 'manage_options' ) ) {\n\t\treturn;\n\t}\n\t?&gt;\n\t&lt;div class=\"wrap\"&gt;\n\t\t&lt;h1&gt;&lt;?php echo esc_html( get_admin_page_title() ); ?&gt;&lt;\/h1&gt;\n\t\t&lt;form action=\"options.php\" method=\"post\"&gt;\n\t\t\t&lt;?php\n\t\t\tsettings_fields( LWSD_GROUP );\n\t\t\tdo_settings_sections( LWSD_PAGE );\n\t\t\tsubmit_button();\n\t\t\t?&gt;\n\t\t&lt;\/form&gt;\n\t\t&lt;p&gt;\n\t\t\t&lt;button type=\"button\" class=\"button\" id=\"lwsd-rest-toggle\"&gt;&lt;?php esc_html_e( 'Toggle via REST API', 'lw-settings-demo' ); ?&gt;&lt;\/button&gt;\n\t\t\t&lt;span id=\"lwsd-rest-status\" aria-live=\"polite\"&gt;&lt;\/span&gt;\n\t\t&lt;\/p&gt;\n\t&lt;\/div&gt;\n\t&lt;?php\n}\n\n\/**\n * Prints the notice bar on the front end.\n *\n * @param string $position Where the calling hook prints: top or bottom.\n *\/\nfunction lwsd_print_notice( $position ) {\n\t$settings = lwsd_get_settings();\n\n\tif ( ! $settings['enabled'] || $position !== $settings['position'] || '' === $settings['message'] ) {\n\t\treturn;\n\t}\n\n\tprintf(\n\t\t'&lt;div class=\"lwsd-notice lwsd-notice-%1$s\" role=\"status\"&gt;%2$s&lt;\/div&gt;',\n\t\tesc_attr( $position ),\n\t\tesc_html( $settings['message'] )\n\t);\n}\n\n\/**\n * Top position, needs a theme that calls wp_body_open().\n *\/\nfunction lwsd_print_top_notice() {\n\tlwsd_print_notice( 'top' );\n}\nadd_action( 'wp_body_open', 'lwsd_print_top_notice' );\n\n\/**\n * Bottom position.\n *\/\nfunction lwsd_print_bottom_notice() {\n\tlwsd_print_notice( 'bottom' );\n}\nadd_action( 'wp_footer', 'lwsd_print_bottom_notice' );\n\n\/**\n * Creates the option with an explicit autoload value.\n *\/\nfunction lwsd_activate() {\n\t\/\/ Read on every front-end request, so autoload it on purpose.\n\tadd_option( LWSD_OPTION, lwsd_default_settings(), '', true );\n}\nregister_activation_hook( __FILE__, 'lwsd_activate' );<\/code><\/pre>\n<h3>Warum register_setting an init h\u00e4ngt<\/h3>\n<p>Das Beispiel im Plugin Handbook registriert Einstellungen an <code>admin_init<\/code>. Dieser Hook feuert in <code>wp-admin\/admin.php<\/code>, und ein REST-Aufruf an <code>\/wp-json\/<\/code> l\u00e4dt diese Datei nie. Eine nur dort registrierte Einstellung funktioniert im Formular, fehlt aber in <code>\/wp\/v2\/settings<\/code>. Der Core registriert seine eigenen Einstellungen an <code>rest_api_init<\/code>, und das Beispiel im Developer Blog f\u00fcr React-basierte Einstellungsseiten nutzt <code>init<\/code>. Das Plugin folgt dem: <code>init<\/code> deckt <code>options.php<\/code>, die Einstellungsseite, REST-Aufrufe und das Frontend ab, wo zus\u00e4tzlich der registrierte Standardwert greift. Abschnitte und Felder sind dagegen reines Admin-Layout und bleiben an <code>admin_init<\/code>.<\/p>\n<h3>Ein Sanitize-Callback f\u00fcr zwei Wege<\/h3>\n<p><code>register_setting()<\/code> h\u00e4ngt den Callback an den Filter <code>sanitize_option_lwsd_settings<\/code>. Diesen Filter wendet <code>sanitize_option()<\/code> an: <code>update_option()<\/code> ruft <code>sanitize_option()<\/code> bei jedem Schreibvorgang auf, unabh\u00e4ngig davon, ob der Wert aus <code>options.php<\/code> oder aus der REST-API stammt. Daraus ergeben sich zwei Anforderungen.<\/p>\n<p>Erstens ist <code>add_settings_error()<\/code> in <code>wp-admin\/includes\/template.php<\/code> definiert, einer Datei, die ein gew\u00f6hnlicher REST-Aufruf nicht l\u00e4dt. Die Pr\u00fcfung mit <code>function_exists()<\/code> stellt deshalb sicher, dass die Funktion vor dem Aufruf verf\u00fcgbar ist. Zweitens muss der Callback idempotent sein: Wendet man ihn erneut auf sein eigenes Ergebnis an, muss dasselbe Ergebnis herauskommen. Fehlt die Option, bereinigt <code>update_option()<\/code> einmal und <code>add_option()<\/code> ein zweites Mal. Das Anlegen der Zeile bei der Aktivierung vermeidet diesen Doppeldurchlauf im Normalbetrieb. Der Callback darf sich darauf aber nicht verlassen.<\/p>\n<h3>Kein Aufruf von settings_errors() auf der Seite<\/h3>\n<p>Bei Seiten unter \u201eEinstellungen\u201c bindet <code>admin-header.php<\/code> die Datei <code>options-head.php<\/code> ein, und die ruft <code>settings_errors()<\/code> von sich aus auf. Ein zus\u00e4tzlicher Aufruf im Seiten-Callback w\u00fcrde jede Meldung doppelt ausgeben. Das Handbook-Beispiel verwendet eine Men\u00fcseite auf oberster Ebene und ruft <code>settings_errors()<\/code> deshalb selbst auf. Ein weiteres Detail aus <code>options.php<\/code>: Die Meldung \u201eDie Einstellungen wurden gespeichert.\u201c kommt nur hinzu, wenn keine andere Meldung registriert ist. Die Warnung aus dem Sanitize-Callback ersetzt sie, obwohl die \u00fcbrigen Werte gespeichert wurden.<\/p>\n<h2>Was beim Speichern passiert<\/h2>\n<p>Das Formular sendet an <code>options.php<\/code>, nicht an die Einstellungsseite. Die folgenden Schritte stammen direkt aus dem Core-Quelltext und gelten f\u00fcr jede Seite, die auf der Settings API aufbaut.<\/p>\n<figure class=\"lw-diagram\">\n<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/wp-settings-api-de.png?v=20260930\" width=\"1120\" height=\"580\" alt=\"Vom Formularfeld bis wp_options: der Speicherweg der Settings API\"><figcaption>options.php pr\u00fcft Capability, Nonce und Freigabeliste; REST m\u00fcndet bei update_option(), wo der Sanitize-Callback l\u00e4uft<\/figcaption><\/figure>\n<ol>\n<li><code>settings_fields()<\/code> gibt vier versteckte Felder aus: <code>option_page<\/code> mit dem Gruppennamen, <code>action<\/code> mit dem Wert <code>update<\/code>, die Nonce <code>_wpnonce<\/code> f\u00fcr die Aktion <code>lwsd_notice_bar-options<\/code> und <code>_wp_http_referer<\/code> mit der Adresse, zu der <code>options.php<\/code> nach dem Speichern zur\u00fcckleitet.<\/li>\n<li><code>options.php<\/code> bestimmt die n\u00f6tige Capability: <code>manage_options<\/code>, \u00e4nderbar \u00fcber den Filter <code>option_page_capability_{$option_page}<\/code>. Fehlt sie, endet die Anfrage mit Status 403.<\/li>\n<li><code>check_admin_referer( $option_page . '-options' )<\/code> pr\u00fcft die Nonce.<\/li>\n<li>Die Gruppe muss auf der Freigabeliste stehen, die <code>register_setting()<\/code> bef\u00fcllt und der Filter <code>allowed_options<\/code> erweitern kann. Andernfalls bricht <code>wp_die()<\/code> ab.<\/li>\n<li>F\u00fcr jede registrierte Option wird der Wert aus <code>$_POST<\/code> gelesen, bei skalaren Werten getrimmt, mit <code>wp_unslash()<\/code> behandelt und an <code>update_option()<\/code> \u00fcbergeben.<\/li>\n<li><code>update_option()<\/code> f\u00fchrt <code>sanitize_option()<\/code> und damit den Callback aus, danach die Filter <code>pre_update_option<\/code>. Hat sich der Wert nicht ge\u00e4ndert, wird nichts geschrieben; sonst landet der serialisierte Wert in der Zeile.<\/li>\n<li>Die gesammelten Meldungen wandern f\u00fcr 30 Sekunden in das Transient <code>settings_errors<\/code>, danach folgt eine Weiterleitung mit <code>settings-updated=true<\/code>. Nach der Weiterleitung liest <code>settings_errors()<\/code> das Transient und gibt die Meldungen aus.<\/li>\n<\/ol>\n<p>Schritt zwei hat eine praktische Folge. Ein Plugin, das seinen Men\u00fcpunkt Redakteuren mit der Capability <code>edit_pages<\/code> zeigt, scheitert beim Speichern, solange der Filter <code>option_page_capability_lwsd_notice_bar<\/code> weiterhin <code>manage_options<\/code> verlangt. Men\u00fc, Seiten-Callback und Speichern m\u00fcssen dieselbe Capability voraussetzen. Warum eine Nonce allein nie die Rechtepr\u00fcfung ersetzt, vertieft <a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/sanitizing-escaping-nonces-capabilities-plugin-review\/\">ein eigener Beitrag dieser Reihe zu Sanitizing, Escaping, Nonces und Capabilities<\/a>.<\/p>\n<h2>register_setting und der REST-Endpunkt \/wp\/v2\/settings<\/h2>\n<p>Registrierte Einstellungen erscheinen standardm\u00e4\u00dfig nicht in der REST-API. F\u00fcr skalare Typen gen\u00fcgt <code>show_in_rest<\/code> mit dem Wert <code>true<\/code>. Bei <code>array<\/code> und <code>object<\/code> braucht die Option ein Schema unter <code>show_in_rest.schema<\/code>: F\u00fcr Arrays ohne <code>items<\/code> meldet der Core einen <code>_doing_it_wrong<\/code>-Hinweis (seit 5.4), f\u00fcr Objekte legen <code>properties<\/code> fest, welche Schl\u00fcssel existieren. Der Controller baut das endg\u00fcltige Schema aus <code>type<\/code>, <code>label<\/code> (als <code>title<\/code>), <code>description<\/code> und <code>default<\/code> und legt das Schema aus <code>show_in_rest<\/code> dar\u00fcber. Zugelassen sind nur die Typen <code>number<\/code>, <code>integer<\/code>, <code>string<\/code>, <code>boolean<\/code>, <code>array<\/code> und <code>object<\/code>; alle anderen Einstellungen werden stillschweigend \u00fcbergangen.<\/p>\n<p>Der Controller in <code>class-wp-rest-settings-controller.php<\/code> beantwortet einige Fragen, die die Referenzseiten offenlassen:<\/p>\n<ul>\n<li><strong>Berechtigung:<\/strong> Lesen und Schreiben verlangen beide <code>manage_options<\/code>. Der Endpunkt eignet sich also nicht, um Einstellungen an anonyme Besucher auszuliefern; daf\u00fcr ist eine eigene Route mit eigenem <code>permission_callback<\/code> n\u00f6tig.<\/li>\n<li><strong>Name:<\/strong> Der Schl\u00fcssel in der Antwort entspricht dem Optionsnamen, sofern <code>show_in_rest<\/code> keinen anderen <code>name<\/code> setzt.<\/li>\n<li><strong>Strenge Objekte:<\/strong> Der Controller erg\u00e4nzt rekursiv <code>additionalProperties: false<\/code> bei jedem Objekt ohne eigene Regel. Unbekannte Schl\u00fcssel in einer Anfrage werden abgelehnt. Enth\u00e4lt der gespeicherte Wert Schl\u00fcssel, die das Schema nicht kennt, etwa aus einer \u00e4lteren Plugin-Version, liefert GET f\u00fcr die gesamte Einstellung <code>null<\/code>.<\/li>\n<li><strong>Ersetzen statt Zusammenf\u00fchren:<\/strong> Ein POST reicht das gesendete Objekt unver\u00e4ndert an <code>update_option()<\/code> weiter. Fehlende Eigenschaften bleiben nicht erhalten; im Beispiel f\u00fcllt der Sanitize-Callback sie mit Standardwerten, und <code>enabled<\/code> wird stillschweigend zu <code>false<\/code>.<\/li>\n<li><strong>null l\u00f6scht:<\/strong> <code>null<\/code> als Wert ruft <code>delete_option()<\/code> auf, es sei denn, der gespeicherte Wert ist ung\u00fcltig; dann verweigert der Controller mit <code>rest_invalid_stored_value<\/code> und Status 500.<\/li>\n<li><strong>Antwort:<\/strong> Nach dem Update ruft der Controller erneut <code>get_item()<\/code> auf. Die Antwort zeigt also den Wert nach der Bereinigung, nicht den gesendeten.<\/li>\n<\/ul>\n<h2>Lesen und Schreiben mit apiFetch<\/h2>\n<p>Das Skript liegt in <code>js\/settings-rest.js<\/code> und wird nur auf der Einstellungsseite geladen: Das Plugin h\u00e4ngt <code>admin_enqueue_scripts<\/code> erst im Hook <code>load-{$hook_suffix}<\/code> ein, dessen Suffix <code>add_options_page()<\/code> zur\u00fcckgibt. F\u00fcr die Authentifizierung reicht die Abh\u00e4ngigkeit <code>wp-api-fetch<\/code>. Der Core registriert zu diesem Handle zwei Inline-Skripte, eines f\u00fcr die Basis-URL der REST-API und eines f\u00fcr die Nonce-Middleware. Die Anfrage tr\u00e4gt damit ohne zus\u00e4tzlichen Code die zum Login-Cookie passende <code>wp_rest<\/code>-Nonce.<\/p>\n<pre><code>\/* global wp *\/\n( function () {\n\t'use strict';\n\n\tconst { __ } = wp.i18n;\n\tconst button = document.getElementById( 'lwsd-rest-toggle' );\n\tconst status = document.getElementById( 'lwsd-rest-status' );\n\n\tif ( ! button || ! status ) {\n\t\treturn;\n\t}\n\n\tbutton.addEventListener( 'click', async () =&gt; {\n\t\tbutton.disabled = true;\n\n\t\ttry {\n\t\t\t\/\/ GET returns every setting registered with show_in_rest, keyed by name.\n\t\t\tconst settings = await wp.apiFetch( { path: '\/wp\/v2\/settings' } );\n\t\t\tconst current = settings.lwsd_settings;\n\n\t\t\tif ( ! current ) {\n\t\t\t\t\/\/ null: the stored value does not match the registered schema.\n\t\t\t\tstatus.textContent = __( 'Stored value does not match the schema.', 'lw-settings-demo' );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t\/\/ The endpoint replaces the whole object, so send every property.\n\t\t\tconst saved = await wp.apiFetch( {\n\t\t\t\tpath: '\/wp\/v2\/settings',\n\t\t\t\tmethod: 'POST',\n\t\t\t\tdata: {\n\t\t\t\t\tlwsd_settings: { ...current, enabled: ! current.enabled },\n\t\t\t\t},\n\t\t\t} );\n\n\t\t\t\/\/ The response already contains the sanitized value.\n\t\t\tstatus.textContent = saved.lwsd_settings.enabled\n\t\t\t\t? __( 'Notice bar is on.', 'lw-settings-demo' )\n\t\t\t\t: __( 'Notice bar is off.', 'lw-settings-demo' );\n\t\t} catch ( error ) {\n\t\t\tstatus.textContent = error.message || __( 'Request failed.', 'lw-settings-demo' );\n\t\t} finally {\n\t\t\tbutton.disabled = false;\n\t\t}\n\t} );\n} )();<\/code><\/pre>\n<p>Die Schaltfl\u00e4che liest das gesamte Einstellungsobjekt, kehrt <code>enabled<\/code> um und schickt das Objekt vollst\u00e4ndig zur\u00fcck. Die Antwort zeigt den Punkt aus dem vorigen Abschnitt: Ist das Textfeld leer, schaltet der Sanitize-Callback die Hinweisleiste wieder ab, und die Statuszeile meldet eine ausgeschaltete Leiste. Das Formular dar\u00fcber aktualisiert sich nicht selbst und zeigt den neuen Stand erst nach dem Neuladen.<\/p>\n<p>Dasselbe Muster tr\u00e4gt auch eine Einstellungsseite in React. Der WordPress Developer Blog zeigt eine Seite auf Basis von <code>@wordpress\/components<\/code>, die die Option mit <code>apiFetch( { path: '\/wp\/v2\/settings' } )<\/code> l\u00e4dt und per POST speichert, registriert an <code>init<\/code> mit Schema. Die PHP-Seite bleibt gleich: <code>register_setting()<\/code> mit Schema und Sanitize-Callback ist weiterhin der einzige Ort, an dem Typ, Standardwert und Bereinigung festgelegt sind. Nur das Rendern des Formulars und <code>options.php<\/code> fallen weg.<\/p>\n<h2>Grenzen und offene Punkte<\/h2>\n<ul>\n<li>Der Code wurde am Core-Quelltext (Branch 7.1 und trunk) gepr\u00fcft und auf einer Testinstallation mit WordPress 7.1.2 und PHP 8.4 ausgef\u00fchrt: Die Aktivierung legt die Option mit Autoload <code>on<\/code> an, ein erstes Speichern \u00fcber das Formular nach dem L\u00f6schen der Option mit <code>auto<\/code>. Speichern \u00fcber das Formular und \u00fcber <code>\/wp\/v2\/settings<\/code> hinterlie\u00df keine Eintr\u00e4ge im Debug-Log, und Plugin Check 2.1.0 bem\u00e4ngelte nur die fehlende <code>readme.txt<\/code>.<\/li>\n<li>Wie <code>auto<\/code> in k\u00fcnftigen Versionen behandelt wird, ist offen; die Dev Note zu 6.6 l\u00e4sst ausdr\u00fccklich Raum f\u00fcr eine \u00c4nderung.<\/li>\n<li>Beim Entfernen des Plugins bleibt seine Option bisher in der Datenbank. Das geh\u00f6rt in <code>uninstall.php<\/code>, Thema <a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/plugin-lebenszyklus-deinstallation\/\">eines eigenen Beitrags dieser Reihe zum Plugin-Lebenszyklus<\/a>.<\/li>\n<li>Die obere Position setzt voraus, dass das Theme <code>wp_body_open()<\/code> aufruft; sonst funktioniert nur die untere.<\/li>\n<li>Netzwerkeinstellungen in Multisite und <code>register_setting()<\/code> im Zusammenspiel mit Seiten der Netzwerkverwaltung wurden nicht untersucht.<\/li>\n<li>Im trunk (7.2 Alpha) wurde <code>settings_errors()<\/code> auf <code>wp_admin_notice()<\/code> umgestellt. Diese Version ist nicht ver\u00f6ffentlicht; der Beitrag beschreibt das Verhalten von 7.1.<\/li>\n<\/ul>\n<div class=\"lw-faq\">\n<h2>Fragen und Antworten<\/h2>\n<h3>Reicht register_setting() an admin_init, wenn die Einstellung nur im Formular gebraucht wird?<\/h3>\n<p>F\u00fcr das Formular ja: <code>options.php<\/code> l\u00e4uft durch <code>wp-admin\/admin.php<\/code>, dort feuert <code>admin_init<\/code>. Sobald die Einstellung aber in <code>\/wp\/v2\/settings<\/code> erscheinen soll, fehlt sie, weil ein REST-Aufruf <code>admin_init<\/code> nicht ausl\u00f6st. Au\u00dferdem greift der registrierte Standardwert von <code>get_option()<\/code> im Frontend nur, wenn die Registrierung dort ebenfalls l\u00e4uft. <code>init<\/code> deckt alle drei F\u00e4lle ab.<\/p>\n<h3>Warum liefert GET \/wp\/v2\/settings f\u00fcr eine Array- oder Objekt-Option pl\u00f6tzlich null?<\/h3>\n<p>Der Controller pr\u00fcft den gespeicherten Wert gegen das Schema und gibt bei einem Fehler <code>null<\/code> aus. H\u00e4ufigste Ursache sind Schl\u00fcssel, die das Schema nicht kennt, etwa aus einer fr\u00fcheren Plugin-Version: Objekte ohne eigene Regel erhalten automatisch <code>additionalProperties: false<\/code>. Abhilfe schafft eine Migration des gespeicherten Werts oder ein erweitertes Schema. Ein POST mit <code>null<\/code> wird in diesem Zustand mit <code>rest_invalid_stored_value<\/code> abgelehnt.<\/p>\n<h3>L\u00e4sst sich \u00fcber \/wp\/v2\/settings nur ein einzelner Schl\u00fcssel eines Objekts \u00e4ndern?<\/h3>\n<p>Nein. Der Controller \u00fcbergibt das gesendete Objekt unver\u00e4ndert an <code>update_option()<\/code> und ersetzt damit den gesamten Wert. Ein Client muss das Objekt zuerst lesen, den Schl\u00fcssel \u00e4ndern und das vollst\u00e4ndige Objekt zur\u00fcckschicken. Wer echte Teilaktualisierungen braucht, baut eine eigene Route mit <code>permission_callback<\/code>.<\/p>\n<h3>Gen\u00fcgt die Nonce aus settings_fields() als Schutz der Einstellungsseite?<\/h3>\n<p>Nein. Eine WordPress-Nonce hilft beim Schutz vor CSRF. Sie ist zeitlich begrenzt und an eine Aktion und Benutzersitzung gebunden. Sie belegt aber nicht die Herkunft aus dem eigenen Formular und ersetzt weder Authentifizierung noch Berechtigungspr\u00fcfung. Die Berechtigung pr\u00fcft <code>options.php<\/code> getrennt davon \u00fcber <code>manage_options<\/code> oder den Wert des Filters <code>option_page_capability_{$option_page}<\/code>. Wer die Capability im Men\u00fc senkt, muss diesen Filter ebenfalls anpassen, sonst scheitert das Speichern.<\/p>\n<\/div>\n<div class=\"lw-quellen\">\n<h2>Quellen<\/h2>\n<ul>\n<li><a href=\"https:\/\/developer.wordpress.org\/plugins\/settings\/settings-api\/\" target=\"_blank\" rel=\"noopener noreferrer\">Plugin Handbook: Settings API<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/plugins\/settings\/using-settings-api\/\" target=\"_blank\" rel=\"noopener noreferrer\">Plugin Handbook: Using Settings API<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/plugins\/settings\/custom-settings-page\/\" target=\"_blank\" rel=\"noopener noreferrer\">Plugin Handbook: Custom Settings Page<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/plugins\/settings\/options-api\/\" target=\"_blank\" rel=\"noopener noreferrer\">Plugin Handbook: Options API<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/register_setting\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: register_setting()<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/settings_fields\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: settings_fields()<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/add_option\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: add_option()<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/wp_set_option_autoload\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: wp_set_option_autoload()<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/rest-api\/reference\/settings\/\" target=\"_blank\" rel=\"noopener noreferrer\">REST API Handbook: Settings<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-api-fetch\/\" target=\"_blank\" rel=\"noopener noreferrer\">Block Editor Handbook: @wordpress\/api-fetch<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/news\/2024\/03\/how-to-use-wordpress-react-components-for-plugin-pages\/\" target=\"_blank\" rel=\"noopener noreferrer\">WordPress Developer Blog: How to use WordPress React components for plugin pages<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/make.wordpress.org\/core\/2016\/10\/26\/registering-your-settings-in-wordpress-4-7\/\" target=\"_blank\" rel=\"noopener noreferrer\">Make WordPress Core: Registering your settings in WordPress 4.7<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/make.wordpress.org\/core\/2023\/10\/17\/new-option-functions-in-6-4\/\" target=\"_blank\" rel=\"noopener noreferrer\">Make WordPress Core: New option functions in 6.4<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/make.wordpress.org\/core\/2024\/06\/18\/options-api-disabling-autoload-for-large-options\/\" target=\"_blank\" rel=\"noopener noreferrer\">Make WordPress Core: Options API: Disabling autoload for large options<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/7.1\/src\/wp-admin\/options.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: wp-admin\/options.php (branch 7.1)<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/7.1\/src\/wp-admin\/options-head.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: wp-admin\/options-head.php (branch 7.1)<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/7.1\/src\/wp-includes\/option.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: wp-includes\/option.php (branch 7.1)<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/7.1\/src\/wp-includes\/rest-api\/endpoints\/class-wp-rest-settings-controller.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: class-wp-rest-settings-controller.php (branch 7.1)<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/7.1\/src\/wp-admin\/includes\/plugin.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: wp-admin\/includes\/plugin.php (branch 7.1)<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/7.1\/src\/wp-admin\/includes\/template.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: wp-admin\/includes\/template.php (branch 7.1)<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/7.1\/src\/wp-includes\/script-loader.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: wp-includes\/script-loader.php (branch 7.1)<\/a>, abgerufen am 29.09.2026<\/li>\n<li><a href=\"https:\/\/api.wordpress.org\/core\/version-check\/1.7\/\" target=\"_blank\" rel=\"noopener noreferrer\">WordPress.org version check API (current release)<\/a>, abgerufen am 29.09.2026<\/li>\n<\/ul>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Ein vollst\u00e4ndiges Plugin mit einer geb\u00fcndelten Option, einer Seite unter \u201eEinstellungen\u201c und REST-Zugriff \u00fcber \/wp\/v2\/settings, dazu der Weg jedes Formularfelds durch options.php.<\/p>\n","protected":false},"author":1,"featured_media":20925,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[92644],"tags":[92274,92048,92235,92277],"class_list":["post-20873","post","type-post","status-publish","format-standard","hentry","category-tutorials-de-wordpress-plugins-tricks-de","tag-php-de","tag-rest-api-de","tag-tutorial-de","tag-wordpress-de"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Einstellungsseiten mit der Settings API und register_setting | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"Settings API Schritt f\u00fcr Schritt: register_setting mit Schema, geb\u00fcndelte Option, bewusstes Autoload, options.php und apiFetch auf \/wp\/v2\/settings.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Einstellungsseiten mit der Settings API und register_setting | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"Settings API Schritt f\u00fcr Schritt: register_setting mit Schema, geb\u00fcndelte Option, bewusstes Autoload, options.php und apiFetch auf \/wp\/v2\/settings.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-14T10:16:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20873-einstellungsseiten-settings-api-register-setting.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Lukas Wojcik\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Lukas Wojcik\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"20 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/\"},\"author\":{\"name\":\"Lukas Wojcik\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"Einstellungsseiten mit der Settings API und register_setting\",\"datePublished\":\"2026-10-14T10:16:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/\"},\"wordCount\":2360,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20873-einstellungsseiten-settings-api-register-setting.png\",\"keywords\":[\"PHP\",\"REST API\",\"Tutorial\",\"WordPress\"],\"articleSection\":[\"Tutorials\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/\",\"name\":\"Einstellungsseiten mit der Settings API und register_setting | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20873-einstellungsseiten-settings-api-register-setting.png\",\"datePublished\":\"2026-10-14T10:16:00+00:00\",\"description\":\"Settings API Schritt f\u00fcr Schritt: register_setting mit Schema, geb\u00fcndelte Option, bewusstes Autoload, options.php und apiFetch auf \\\/wp\\\/v2\\\/settings.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20873-einstellungsseiten-settings-api-register-setting.png\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20873-einstellungsseiten-settings-api-register-setting.png\",\"width\":1200,\"height\":630,\"caption\":\"Einstellungsseiten mit der Settings API und register_setting\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/wordpress-plugins-tricks-de\\\/tutorials-de-wordpress-plugins-tricks-de\\\/einstellungsseiten-settings-api-register-setting\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Einstellungsseiten mit der Settings API und register_setting\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Einstellungsseiten mit der Settings API und register_setting | Lukas Wojcik","description":"Settings API Schritt f\u00fcr Schritt: register_setting mit Schema, geb\u00fcndelte Option, bewusstes Autoload, options.php und apiFetch auf \/wp\/v2\/settings.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/","og_locale":"en_US","og_type":"article","og_title":"Einstellungsseiten mit der Settings API und register_setting | Lukas Wojcik","og_description":"Settings API Schritt f\u00fcr Schritt: register_setting mit Schema, geb\u00fcndelte Option, bewusstes Autoload, options.php und apiFetch auf \/wp\/v2\/settings.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-10-14T10:16:00+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20873-einstellungsseiten-settings-api-register-setting.png","type":"image\/png"}],"author":"Lukas Wojcik","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Lukas Wojcik","Est. reading time":"20 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/"},"author":{"name":"Lukas Wojcik","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"Einstellungsseiten mit der Settings API und register_setting","datePublished":"2026-10-14T10:16:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/"},"wordCount":2360,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20873-einstellungsseiten-settings-api-register-setting.png","keywords":["PHP","REST API","Tutorial","WordPress"],"articleSection":["Tutorials"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/","url":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/","name":"Einstellungsseiten mit der Settings API und register_setting | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20873-einstellungsseiten-settings-api-register-setting.png","datePublished":"2026-10-14T10:16:00+00:00","description":"Settings API Schritt f\u00fcr Schritt: register_setting mit Schema, geb\u00fcndelte Option, bewusstes Autoload, options.php und apiFetch auf \/wp\/v2\/settings.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20873-einstellungsseiten-settings-api-register-setting.png","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20873-einstellungsseiten-settings-api-register-setting.png","width":1200,"height":630,"caption":"Einstellungsseiten mit der Settings API und register_setting"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/wordpress-plugins-tricks-de\/tutorials-de-wordpress-plugins-tricks-de\/einstellungsseiten-settings-api-register-setting\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Einstellungsseiten mit der Settings API und register_setting"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20873","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=20873"}],"version-history":[{"count":1,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20873\/revisions"}],"predecessor-version":[{"id":21780,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20873\/revisions\/21780"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/20925"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=20873"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=20873"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=20873"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}