There are a few libraries which provide config pages for userscripts:
1) GM_config

2) MonkeyConfig

3) GM_registerMenuCommand Submenu JS Module
The usage varies per library, but typically you grant the permissions they need such as GM_getValue and GM_setValue, and require the library via the @require directive, e.g.:
// ==UserScript==
// @name My Userscript
// @description An example userscript with a config page
// @version 0.0.1
// @require https://www.example.com/lib/myconfig.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// ==/UserScript==
const config = new MyConfig({ ... })
You then register a menu command which opens the config page/dialog, e.g.:
GM_registerMenuCommand('Configure My Userscript!', () => {
config.open()
})
In the case of MonkeyConfig, it can register the command for you:
const config = new MonkeyConfig({
title: 'Configure My Userscript!',
menuCommand: true,
// ...
})
For advanced uses, the configurator may allow listeners to be registered for the close/cancel/save events, as well as providing control over the CSS, and other options. Detailed instructions can be found on the GM_config wiki and the MonkeyConfig homepage.