Wordpress - Where to store plugin settings fields

It depends on how you are going to use the stored data.

If you want to run complex queries against the values, use a custom table with indexes optimized for those queries.

If you will always just fetch all the values for a given object, create a non-public custom post type, and store the data as post meta.

Do not store the data in a serialized string, like the options API does it. This is a nightmare when you want to change it per command line SQL, because the serialized format is PHP specific.

The "Settings API" imposes some very outdated markup, and the code for the registration and storage is rather counter-intuitive. I wouldn't recommend to use it.


Where to store plugin settings fields?

Options table FTW. It's cached and easy to do CRUD.

Settings API or Options API?

Basically, you can use Options API without Settings API but you cannot use Settings API without Options API. Even when you just need to add some fields to a existing WordPress page, you still need get_option() to retrieve data for your view template.

But by using a existing WordPress page, your data will be fragmented and hard to retrieve/maintain because it's stored with different option_name. It might confuse end-users as well.

When using Options API only, as the author of the plugin, you can add news sections/fields whenever you want but other people can't. Because view template is hardcoded without hooks which work like do_settings_sections() and do_settings_fields(). Of course, you can you use do_action() but it's will be much more complicated.

Using Options API gives more freedom to developer to handle all validation is incorrect. Settings API has sanitize_callback which also allow developer to do whatever they want with input data.

So, why don't use both of them?

For example, let say a setting page using both Settings API and Options API with option_group is my_app_group and option_name is my_app:

$options = get_option('my_app');

?><div class="wrap">
    <h1><?= __('Plugin Settings', 'textdomain') ?></h1>
    <form class="form-table" method="post" action="options.php">
        <?php settings_fields('my_app_group') ?>
        <table>
            <tr>
                <td>
                    <label for="my_app[name]">
                        <?= __('App Name', 'textdomain') ?>
                    </label>
                </td>
                <td>
                    <input type="text" name="my_app[name]" value="<?= $options['name'] ?>">
                </td>
            </tr>
            <tr>
                <td>
                    <label for="my_app[app_key]">
                        <?= __('App Key', 'textdomain') ?>
                    </label>
                </td>
                <td>
                    <input type="text" name="my_app[app_key]" value="<?= $options['app_key'] ?>">
                </td>
            </tr>
            <?php do_settings_fields('my_app_group', 'default') ?>
        </table>
        <?php do_settings_sections('my_app_group') ?>
        <?php submit_button() ?>
    </form>
</div><?php

Now, all data is stored in options table under my_app option name then it's easy to retrieve/maintain. Other developers can also add new sections/fields to your plugin too.