{"ArticleId":null,"Name":"Application settings reference (appsettings.json)","Content":"\n\u003Cp\u003ESettings that belong to the server rather than to the store are read at start-up from \u003Ccode\u003EApp_Data/appsettings.json\u003C/code\u003E. This article describes every section and key of the file as shipped with GrandNode 2.4, plus the few settings that are read from configuration but are not in the file. Changes take effect after a restart.\u003C/p\u003E\n\n\u003Ch2 id=\u0022overriding\u0022\u003EHow to set them\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EThe file\u003C/strong\u003E \u003Ccode\u003EApp_Data/appsettings.json\u003C/code\u003E. It allows comments.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EEnvironment variables\u003C/strong\u003E override the file. The path is joined with double underscores: \u003Ccode\u003ESecurity:ForceUseHTTPS\u003C/code\u003E becomes \u003Ccode\u003ESecurity__ForceUseHTTPS=true\u003C/code\u003E; array items are numbered, e.g. \u003Ccode\u003ESecurity__SanitizerAllowedIframeHosts__0=youtube.com\u003C/code\u003E. This is the normal way in Docker and Kubernetes - keep secrets in a Secret, not in the file.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECommand-line arguments\u003C/strong\u003E override both. \u003Ccode\u003E--appsettings=\u0026lt;name\u0026gt;\u003C/code\u003E additionally loads \u003Ccode\u003EApp_Data/\u0026lt;name\u0026gt;/appsettings.json\u003C/code\u003E on top of the main file - handy for several configurations side by side.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAzure App Configuration\u003C/strong\u003E: with \u003Ccode\u003EAzure:AppConfiguration\u003C/code\u003E set, keys are also read from that service (see \u003Ca href=\u0022#azure\u0022\u003EAzure\u003C/a\u003E).\u003C/li\u003E\n\u003Cli\u003EThe admin, store owner and vendor panels are hosted inside the storefront application by default. Each can also run as a separate application (\u003Ccode\u003EGrand.Web.Admin\u003C/code\u003E, \u003Ccode\u003EGrand.Web.Store\u003C/code\u003E, \u003Ccode\u003EGrand.Web.Vendor\u003C/code\u003E) with its own \u003Ccode\u003EApp_Data/appsettings.json\u003C/code\u003E; then keep the shared values - database, \u003Ccode\u003ECustomer\u003C/code\u003E, \u003Ccode\u003ESecurity\u003C/code\u003E cookies, \u003Ccode\u003ERedis\u003C/code\u003E, \u003Ccode\u003EMediaPath\u003C/code\u003E - identical in all of them.\u003C/li\u003E\n\u003C/ul\u003E\n\n\u003Ch2 id=\u0022application\u0022\u003EApplication\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EDisplayFullErrorStack\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EShow full exception details on error pages in production. Always on in Development. Never enable on a public store.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EStaticFilesCacheControl\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Epublic,max-age=31536000\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EThe \u003Ccode\u003ECache-Control\u003C/code\u003E header of static files (CSS, JS, images).\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EMediaPath\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003Eempty\u003C/td\u003E\u003Ctd\u003EDirectory for files written at runtime - see \u003Ca href=\u0022#media-path\u0022\u003EMediaPath\u003C/a\u003E below.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EUseSessionStateTempDataProvider\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EKeep TempData in the session instead of a cookie.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ESeoFriendlyUrlsForLanguagesEnabled\u003C/code\u003E, \u003Ccode\u003ESeoFriendlyUrlsDefaultCode\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E, \u003Ccode\u003Een\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ELanguage code as the first part of every URL (\u003Ccode\u003E/de/...\u003C/code\u003E); the default code is used in route patterns. See \u003Ca href=\u0022/configuration-languages-translations\u0022\u003ELanguages and translations\u003C/a\u003E.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAllowNonAsciiCharInHeaders\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EEncode response headers as UTF-8. Needed only for unusual integrations.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EMaxRequestBodySize\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003E10485760\u003C/code\u003E (10 MB)\u003C/td\u003E\u003Ctd\u003ELargest accepted request body in bytes, including uploads. Raise it for large imports or videos (max 2147483648); keep it bounded so anonymous forms cannot buffer huge requests.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EEnableContextLoggingMiddleware\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EAdds the current customer and store to every log entry - useful when reading logs.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch3 id=\u0022media-path\u0022\u003EMediaPath: shared runtime files\u003C/h3\u003E\n\u003Cp\u003ESome files are written by the running application: custom CSS and JS, files uploaded through the HTML editor (\u003Ccode\u003Eassets/images/uploaded\u003C/code\u003E), picture thumbnails, pictures stored on disk, \u003Ccode\u003Esitemap*.xml\u003C/code\u003E and the push notification service worker (\u003Ccode\u003Efirebase-messaging-sw.js\u003C/code\u003E). By default they go to \u003Ccode\u003Ewwwroot\u003C/code\u003E, so with several instances each instance has its own copy. Set \u003Ccode\u003EApplication:MediaPath\u003C/code\u003E to a directory and they are written there instead:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe media path is served at the site root \u003Cstrong\u003Eahead of\u003C/strong\u003E \u003Ccode\u003Ewwwroot\u003C/code\u003E: a file written at runtime replaces the copy that shipped with the build, and files that only exist in \u003Ccode\u003Ewwwroot\u003C/code\u003E are still served from there.\u003C/li\u003E\n\u003Cli\u003EWith several instances, point every instance at the \u003Cstrong\u003Esame shared volume\u003C/strong\u003E (in Kubernetes a ReadWriteMany volume such as Azure Files, EFS or NFS). Then every instance serves the same sitemap, custom CSS and uploaded images.\u003C/li\u003E\n\u003Cli\u003EA relative path is resolved against the application directory. The application refuses to start if the path is the application directory, one of its parents, or \u003Ccode\u003EApp_Data\u003C/code\u003E itself - those would publish \u003Ccode\u003Eappsettings.json\u003C/code\u003E and \u003Ccode\u003ESettings.cfg\u003C/code\u003E. Use a dedicated directory, for example \u003Ccode\u003E/app/media\u003C/code\u003E (created and writable in the official Docker image) or \u003Ccode\u003EApp_Data/media\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EThumbnails are read only from the media path once it is set and are regenerated there.\u003C/li\u003E\n\u003Cli\u003ENot covered: generated PDF files (\u003Ccode\u003Eassets/files/exportimport\u003C/code\u003E) stay in \u003Ccode\u003Ewwwroot\u003C/code\u003E.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u003Cstrong\u003ESwitching an existing installation\u003C/strong\u003E: \u003Cem\u003Emove\u003C/em\u003E (not copy) \u003Ccode\u003Ewwwroot/assets/custom\u003C/code\u003E, \u003Ccode\u003Ewwwroot/assets/images/uploaded\u003C/code\u003E, \u003Ccode\u003Ewwwroot/sitemap*.xml\u003C/code\u003E, \u003Ccode\u003Ewwwroot/firebase-messaging-sw.js\u003C/code\u003E and, with pictures stored on disk, the \u003Ccode\u003Ewwwroot/assets/images/*_0.*\u003C/code\u003E originals to the same paths under the media path, then stop mounting the old \u003Ccode\u003Ewwwroot/assets/images\u003C/code\u003E volume. A file left behind in \u003Ccode\u003Ewwwroot\u003C/code\u003E keeps being served after it is deleted in the admin, because deletes happen only on the media path. Without the move, each instance serves its own sitemap until the sitemap task runs again, and its own push service worker until the push settings are saved again. With \u003Ccode\u003EDirectory\u003C/code\u003E set, both sides carry it as a prefix (\u003Ccode\u003Ewwwroot/{Directory}/assets/custom\u003C/code\u003E \u2192 \u003Ccode\u003E{MediaPath}/{Directory}/assets/custom\u003C/code\u003E).\u003C/p\u003E\n\n\u003Ch2 id=\u0022customer\u0022\u003ECustomer\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ERegisterCustomersPerStore\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E: an e-mail (or username) is unique across the whole installation. \u003Ccode\u003Etrue\u003C/code\u003E: unique per store - the same e-mail can register separately in each store, and store managers can manage their store\u0027s customers. Decide before going live: switching on data with the same e-mail in several stores can make accounts unreachable. Back-office accounts (administrators) have no store and stay globally unique. Must be identical in every host of the installation.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022database\u0022\u003EConnectionStrings and Database\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EConnectionStrings:Mongodb\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003Eempty\u003C/td\u003E\u003Ctd\u003EThe MongoDB connection string. When set (usually as the environment variable \u003Ccode\u003EConnectionStrings__Mongodb\u003C/code\u003E) it takes precedence over \u003Ccode\u003EApp_Data/Settings.cfg\u003C/code\u003E, and the installation wizard does not ask for the database or write \u003Ccode\u003ESettings.cfg\u003C/code\u003E.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EConnectionStrings:Provider\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003Enot in the file\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003E0\u003C/code\u003E = MongoDB (default).\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EDatabase:UseLiteDb\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EUse the embedded LiteDB file database instead of MongoDB - for trials and development, not for production.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EDatabase:Singleton\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ELiteDB only: one shared database instance instead of one per request.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EDatabase:LiteDbConnectionString\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003EFilename=App_Data/database.db\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ELiteDB file, used by the installation.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022security\u0022\u003ESecurity\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EUseForwardedHeaders\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ETrust \u003Ccode\u003EX-Forwarded-For\u003C/code\u003E/\u003Ccode\u003EX-Forwarded-Proto\u003C/code\u003E from a reverse proxy, load balancer or ingress, so links use the public https address and the client IP is right.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EForceUseHTTPS\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ETreat every request as HTTPS - when the proxy cannot send forwarded headers.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAllowedHosts\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003E*\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EHost filtering: host names the application answers to (semicolon-separated).\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EUseDefaultSecurityHeaders\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EAdds \u003Ccode\u003EX-Content-Type-Options\u003C/code\u003E, \u003Ccode\u003EStrict-Transport-Security\u003C/code\u003E, \u003Ccode\u003EX-Frame-Options: Deny\u003C/code\u003E, \u003Ccode\u003EX-XSS-Protection\u003C/code\u003E, \u003Ccode\u003EReferrer-Policy\u003C/code\u003E and a basic \u003Ccode\u003EContent-Security-Policy\u003C/code\u003E to every response.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EUseHsts\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ESend the HSTS header.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EUseHttpsRedirection\u003C/code\u003E, \u003Ccode\u003EHttpsRedirectionRedirect\u003C/code\u003E, \u003Ccode\u003EHttpsRedirectionHttpsPort\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E, \u003Ccode\u003E308\u003C/code\u003E, \u003Ccode\u003E443\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ERedirect HTTP to HTTPS in the application, with this status code and port.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EKeyPersistenceLocation\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003Eempty (= \u003Ccode\u003EApp_Data/DataProtectionKeys\u003C/code\u003E)\u003C/td\u003E\u003Ctd\u003EWhere the data protection keys (sign-in cookies, form tokens) are stored. Must be persistent, and shared by all instances - or use Redis or Azure instead.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ECookieAuthExpires\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003E8760\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ELifetime of the sign-in cookie in hours.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ECookiePrefix\u003C/code\u003E, \u003Ccode\u003ECookieClaimsIssuer\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003E.Grand.\u003C/code\u003E, \u003Ccode\u003Egrandnode\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ECookie name prefix and claims issuer. Changing either signs everyone out.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ECookieSecurePolicyAlways\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ESend cookies only over HTTPS. Turn on when the store runs fully on HTTPS.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ECookieSameSite\u003C/code\u003E, \u003Ccode\u003ECookieSameSiteExternalAuth\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003ELax\u003C/code\u003E, \u003Ccode\u003ENone\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ESameSite mode of the cookies (Unspecified, None, Lax, Strict); the second applies to external sign-in (Google, Facebook), which needs cross-site cookies.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAuthorizeAdminMenu\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EAdditionally check access to admin controllers and actions against the permissions configured on the admin menu items.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPasswordHashKey\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003Eempty\u003C/td\u003E\u003Ctd\u003EA server-side secret (\u0022pepper\u0022) mixed into password hashes. Recommended; set it once before going live and keep it out of the database. Changing it later invalidates existing hashes - those customers must reset their password.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPasswordHashIterations\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003E210000\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EPBKDF2 iterations for new hashes. Stored with each hash, so it can be raised later.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ESanitizerAllowedIframeHosts\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EYouTube, youtube-nocookie, Vimeo, Google\u003C/td\u003E\u003Ctd\u003EHosts whose iframes survive HTML sanitisation of admin-edited content. \u003Ccode\u003E*.\u003C/code\u003E matches subdomains; remove the key to use the defaults, set \u003Ccode\u003E[]\u003C/code\u003E to block all iframes.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPictureImportAllowedPrivateHosts\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003E[]\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EInternal hosts that picture URLs in Excel imports may point to; any other host must resolve to a public address. Prevents an import file from probing your internal network.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EEnableHtmlSanitization\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EEmergency switch: \u003Ccode\u003Efalse\u003C/code\u003E accepts HTML unsanitised when the sanitiser wrongly rejects legitimate content. Switch it back on as soon as possible.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022cache\u0022\u003ECache\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EDefaultCacheTimeMinutes\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003E60\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EHow long cached data lives in each instance\u0027s memory. With several instances and no Redis pub/sub, this is how long other instances can serve stale data.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022extensions\u0022\u003EExtensions (plugins)\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EUseRoslynScripts\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EEnables C# scripts compiled at runtime (System \u2192 Developer tools \u2192 Roslyn compiler). For developers only.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EDisableUploadExtensions\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EForbid uploading plugins through the admin. Recommended in production and with several instances.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPluginSkipLoadingPattern\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003Eempty\u003C/td\u003E\u003Ctd\u003EA regular expression; plugin DLLs whose file name matches are not loaded - for example to rule out a broken plugin without deleting it.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPluginShadowCopy\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ELoad plugins from copies, so the files can be replaced while the application runs. Set \u003Ccode\u003Efalse\u003C/code\u003E when several processes share one plugin folder.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EClearPluginShadowDirectoryOnStartup\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EPresent in the file but not used by the current code.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EInstalledPlugins\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003Eempty\u003C/td\u003E\u003Ctd\u003EComma-separated system names of installed plugins. When set, it replaces \u003Ccode\u003EApp_Data/InstalledPlugins.cfg\u003C/code\u003E - a declarative list for containers.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022access-control\u0022\u003EAccessControl and Performance\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAccessControl:IgnoreAcl\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ESkip customer group restrictions on products, categories and pages - faster when you do not use them.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAccessControl:IgnoreStoreLimitations\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ESkip \u0022limited to stores\u0022 checks - only for single-store installations.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPerformance:UseResponseCompression\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ECompress responses (Brotli, gzip). Turn off if IIS or the proxy already compresses.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPerformance:IgnoreUsePoweredByMiddleware\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E removes the middleware that adds the \u0022powered by\u0022 response header.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022feature-management\u0022\u003EFeatureManagement\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EGrand.Module.Installer\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EThe installation wizard. Can be switched off after installation.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EGrand.Module.Migration\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ERuns upgrade migrations at start-up. Keep it on.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EGrand.Module.ScheduledTasks\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EThe scheduled task runner. Every run is claimed by exactly one instance, so it can stay on everywhere.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EGrand.Module.Api\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EThe REST API (Backend and Frontend). See \u003Ca href=\u0022/developers-scalar-api-playground\u0022\u003EAPI configuration and the Scalar playground\u003C/a\u003E.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003EAs environment variables the module names keep their dots: \u003Ccode\u003EFeatureManagement__Grand.Module.Api=true\u003C/code\u003E.\u003C/p\u003E\n\n\u003Ch2 id=\u0022redis\u0022\u003ERedis\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ERedisPubSubEnabled\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EPublish cache removals on a Redis channel so every instance clears the same keys from its memory. Required with more than one instance. Redis carries only these messages; it is not used as a distributed cache.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ERedisPubSubConnectionString\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003E127.0.0.1:6379,allowAdmin=true\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EStackExchange.Redis connection string (host, port, password, ssl...).\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003ERedisPubSubChannel\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Echannel\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EThe channel name - the same for all instances of one store, different per environment.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPersistKeysToRedis\u003C/code\u003E, \u003Ccode\u003EPersistKeysToRedisUrl\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EStore the data protection keys in Redis, shared by all instances.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003ESetup and a real test: \u003Ca href=\u0022/getting-started-docker-kubernetes\u0022\u003ERunning in production: Docker and Kubernetes\u003C/a\u003E and \u003Ca href=\u0022/developers-aspire-local-testing\u0022\u003ELocal multi-instance testing with .NET Aspire\u003C/a\u003E.\u003C/p\u003E\n\n\u003Ch2 id=\u0022url-rewrite\u0022\u003EUrlRewrite\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EDefault\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EUseUrlRewrite\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EApply the rules in \u003Ccode\u003EApp_Data/UrlRewrite.xml\u003C/code\u003E (IIS URL Rewrite format) - redirects after a migration, www handling. See \u003Ca href=\u0022/seo-urls-and-redirects\u0022\u003EFriendly URLs and redirects\u003C/a\u003E.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EUrlRewriteHttpsOptions\u003C/code\u003E, \u003Ccode\u003EUrlRewriteHttpsOptionsStatusCode\u003C/code\u003E, \u003Ccode\u003EUrlRewriteHttpsOptionsPort\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E, \u003Ccode\u003E303\u003C/code\u003E, \u003Ccode\u003E443\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ERedirect HTTP to HTTPS through the rewrite middleware, with this status code and port.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EUrlRedirectToHttpsPermanent\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EUse a permanent (301) redirect to HTTPS.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022azure\u0022\u003EAzure\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAzureBlobStorageConnectionString\u003C/code\u003E, \u003Ccode\u003EAzureBlobStorageContainerName\u003C/code\u003E, \u003Ccode\u003EAzureBlobStorageEndPoint\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EStore pictures in Azure Blob Storage instead of the database or disk.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPersistKeysToAzureBlobStorage\u003C/code\u003E, \u003Ccode\u003EPersistKeysAzureBlobStorageConnectionString\u003C/code\u003E, \u003Ccode\u003EDataProtectionContainerName\u003C/code\u003E, \u003Ccode\u003EDataProtectionBlobName\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EStore the data protection keys in a blob (the blob must exist).\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPersistKeysToAzureKeyVault\u003C/code\u003E, \u003Ccode\u003EKeyIdentifier\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EAdditionally protect the keys with an Azure Key Vault key.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAppConfiguration\u003C/code\u003E, \u003Ccode\u003EAppKeyPrefix\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ERead configuration from Azure App Configuration (connection string); with a prefix, only keys starting with it are read and the prefix is removed.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022amazon\u0022\u003EAmazon\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAmazonAwsAccessKeyId\u003C/code\u003E, \u003Ccode\u003EAmazonAwsSecretAccessKey\u003C/code\u003E, \u003Ccode\u003EAmazonBucketName\u003C/code\u003E, \u003Ccode\u003EAmazonRegion\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EStore pictures in Amazon S3. All four must be set.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAmazonDistributionDomainName\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EA CloudFront domain to serve the pictures from.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022external-auth\u0022\u003EFacebookSettings and GoogleSettings\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EFacebookSettings:AppId\u003C/code\u003E/\u003Ccode\u003EAppSecret\u003C/code\u003E and \u003Ccode\u003EGoogleSettings:ClientId\u003C/code\u003E/\u003Ccode\u003EClientSecret\u003C/code\u003E are the credentials of the Facebook and Google sign-in plugins (Plugins \u2192 External authentication methods). Keep the secrets in environment variables.\u003C/p\u003E\n\n\u003Ch2 id=\u0022api\u0022\u003EFrontendAPI and BackendAPI\u003C/h2\u003E\n\u003Cp\u003EEvery key of both sections - \u003Ccode\u003EEnabled\u003C/code\u003E, \u003Ccode\u003ESecretKey\u003C/code\u003E, the Validate* options, token lifetimes, \u003Ccode\u003EJsonContentType\u003C/code\u003E, \u003Ccode\u003ESystemModel\u003C/code\u003E - is described in \u003Ca href=\u0022/developers-scalar-api-playground\u0022\u003EAPI configuration and the Scalar playground\u003C/a\u003E. The API also reads \u003Ccode\u003EAllowedHostOrigins\u003C/code\u003E (a list of origins allowed by CORS in production), which is not in the file.\u003C/p\u003E\n\n\u003Ch2 id=\u0022logging\u0022\u003ELogging and ApplicationInsights\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ELogging:LogLevel\u003C/code\u003E sets the level per category: \u003Ccode\u003EDefault\u003C/code\u003E, \u003Ccode\u003ESystem\u003C/code\u003E, or any namespace, e.g. \u003Ccode\u003ELogging__LogLevel__Grand.Infrastructure.Caching.Redis=Debug\u003C/code\u003E to see cache invalidation messages.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EApplicationInsights:ConnectionString\u003C/code\u003E sends logs, traces and metrics to Azure Application Insights. Any OpenTelemetry collector can be used instead with \u003Ccode\u003EOTEL_EXPORTER_OTLP_ENDPOINT\u003C/code\u003E.\u003C/li\u003E\n\u003C/ul\u003E\n\n\u003Ch2 id=\u0022not-in-file\u0022\u003ESettings that are not in the file\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EKey\u003C/th\u003E\u003Cth\u003EWhat it does\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EASPNETCORE_ENVIRONMENT\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003EProduction\u003C/code\u003E (default) or \u003Ccode\u003EDevelopment\u003C/code\u003E. Development shows full errors, enables the API documentation and serves the panels\u0027 static files from the build output.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EDirectory\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EA per-installation subdirectory: \u003Ccode\u003ESettings.cfg\u003C/code\u003E and \u003Ccode\u003EInstalledPlugins.cfg\u003C/code\u003E are read from \u003Ccode\u003EApp_Data/{Directory}/\u003C/code\u003E, and media files from \u003Ccode\u003E{Directory}/\u003C/code\u003E under \u003Ccode\u003Ewwwroot\u003C/code\u003E and the media path.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003Eappsettings\u003C/code\u003E (command line)\u003C/td\u003E\u003Ctd\u003ELoads \u003Ccode\u003EApp_Data/{value}/appsettings.json\u003C/code\u003E as an additional file.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EConnectionStrings:Provider\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EDatabase provider, \u003Ccode\u003E0\u003C/code\u003E = MongoDB.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EAllowedHostOrigins\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003ECORS origins for the API in production.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022tips\u0022\u003ETips\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EKeep secrets (connection string, API keys, the password pepper, social sign-in secrets) out of the file in your repository: environment variables or a secret store.\u003C/li\u003E\n\u003Cli\u003EWhen you upgrade, compare your \u003Ccode\u003Eappsettings.json\u003C/code\u003E with the new version\u0027s file - new keys (like \u003Ccode\u003EMediaPath\u003C/code\u003E in 2.4) only take their default until you add them.\u003C/li\u003E\n\u003Cli\u003EAfter changing settings, restart and check \u003Cstrong\u003ESystem \u2192 System information\u003C/strong\u003E.\u003C/li\u003E\n\u003C/ul\u003E\n\n\u003Ch2 id=\u0022related\u0022\u003ERelated\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/getting-started-docker-kubernetes\u0022\u003ERunning in production: Docker and Kubernetes\u003C/a\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/system-maintenance\u0022\u003EMaintenance tools and logs\u003C/a\u003E\u003C/li\u003E\n\u003C/ul\u003E\n","ParentCategoryId":"6abdec6d83d2816248229f33","SeName":"system-appsettings","MetaKeywords":null,"MetaDescription":"Every key of GrandNode appsettings.json - database, MediaPath, security, cache, Redis, plugins, storage, API, logging - and how to override it.","MetaTitle":null,"AllowComments":false,"Captcha":{"ReCaptchaChallengeField":null,"ReCaptchaResponseField":null,"ReCaptchaResponseValue":null,"ReCaptchaResponse":null},"RelatedArticles":[],"CategoryBreadcrumb":[{"Name":"System and maintenance","Description":null,"IsCurrent":false,"Children":null,"Parent":null,"SeName":"docs-system","Id":"6abdec6d83d2816248229f33","UserFields":[]}],"AddNewComment":{"CommentText":null,"DisplayCaptcha":false,"Id":null,"UserFields":[]},"Comments":[],"Id":"6abdec6d83d2816248229f41","UserFields":[]}