{"id":13779369,"url":"https://github.com/demystifyfp/FsConfig","last_synced_at":"2025-05-11T12:33:19.426Z","repository":{"id":48454259,"uuid":"115850069","full_name":"demystifyfp/FsConfig","owner":"demystifyfp","description":"FsConfig is a F# library for reading configuration data from environment variables and AppSettings with type safety.","archived":false,"fork":false,"pushed_at":"2023-10-24T18:00:25.000Z","size":479,"stargazers_count":165,"open_issues_count":4,"forks_count":17,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-04T22:07:51.401Z","etag":null,"topics":["12-factor","appsettings","configuration","configuration-management","environment-variables","fsharp","functional-programming","generic-programming"],"latest_commit_sha":null,"homepage":"https://www.demystifyfp.com/FsConfig/","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/demystifyfp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"ko_fi":"tamizhvendan"}},"created_at":"2017-12-31T07:41:46.000Z","updated_at":"2025-04-23T09:19:03.000Z","dependencies_parsed_at":"2024-01-07T00:08:27.337Z","dependency_job_id":null,"html_url":"https://github.com/demystifyfp/FsConfig","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/demystifyfp%2FFsConfig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/demystifyfp%2FFsConfig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/demystifyfp%2FFsConfig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/demystifyfp%2FFsConfig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/demystifyfp","download_url":"https://codeload.github.com/demystifyfp/FsConfig/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253567581,"owners_count":21928856,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["12-factor","appsettings","configuration","configuration-management","environment-variables","fsharp","functional-programming","generic-programming"],"created_at":"2024-08-03T18:01:04.458Z","updated_at":"2025-05-11T12:33:19.072Z","avatar_url":"https://github.com/demystifyfp.png","language":"F#","readme":"# FsConfig\n\nFsConfig is a F# library for reading configuration data from environment variables and AppSettings with type safety\n\n[![Nuget](https://img.shields.io/nuget/dt/FsConfig.svg)](https://www.nuget.org/packages/FsConfig)\n[![Build master](https://github.com/demystifyfp/FsConfig/actions/workflows/build.yml/badge.svg)](https://github.com/demystifyfp/FsConfig/actions/workflows/build.yml)\n\n## Why FsConfig?\n\nTo understand FsConfig, let's have a look at an use case from the [FsTweet](https://github.com/demystifyfp/FsTweet) application.\n\nThe FsTweet application follows [The Twelve-Factor App](https://12factor.net/config) guideline for managing the configuration data. During the application bootstrap, it retrieves its ten configuration parameters from their respective environment variables.\n\n```fsharp\nopen System\n\nlet main argv =\n\n  let fsTweetConnString = \n   Environment.GetEnvironmentVariable  \"FSTWEET_DB_CONN_STRING\"\n\n  let serverToken =\n    Environment.GetEnvironmentVariable \"FSTWEET_POSTMARK_SERVER_TOKEN\"\n\n  let senderEmailAddress =\n    Environment.GetEnvironmentVariable \"FSTWEET_SENDER_EMAIL_ADDRESS\"\n\n  let env = \n    Environment.GetEnvironmentVariable \"FSTWEET_ENVIRONMENT\"\n\n  let streamConfig : GetStream.Config = {\n      ApiKey = \n        Environment.GetEnvironmentVariable \"FSTWEET_STREAM_KEY\"\n      ApiSecret = \n        Environment.GetEnvironmentVariable \"FSTWEET_STREAM_SECRET\"\n      AppId = \n        Environment.GetEnvironmentVariable \"FSTWEET_STREAM_APP_ID\"\n  }\n\n  let serverKey = \n    Environment.GetEnvironmentVariable \"FSTWEET_SERVER_KEY\"\n\n  let port = \n    Environment.GetEnvironmentVariable \"PORT\" |\u003e uint16\n\n  // ...\n```\n\nThough the code snippet does the job, there are some shortcomings.\n\n1. The code is verbose.\n2. There is no error handling to deal with the absence of values or wrong values.\n3. Explicit type casting\n\nWith the help of FsConfig, we can overcome these limitations by specifying the configuration data as a F# Record type.\n\n```fsharp\ntype StreamConfig = {\n  Key : string\n  Secret : string\n  AppId : string\n}\n\n[\u003cConvention(\"FSTWEET\")\u003e]\ntype Config = {\n\n  DbConnString : string\n  PostmarkServerToken : string\n  SenderEmailAddress : string\n  ServerKey : string\n  Environment : string\n\n  [\u003cCustomName(\"PORT\")\u003e]\n  Port : uint16\n  Stream : StreamConfig\n}\n```\n\nAnd then read all the associated environment variables in a single function call with type safety and error handling!\n\n```fsharp\nlet main argv =\n\n  let config = \n    match EnvConfig.Get\u003cConfig\u003e() with\n    | Ok config -\u003e config\n    | Error error -\u003e \n      match error with\n      | NotFound envVarName -\u003e \n        failwithf \"Environment variable %s not found\" envVarName\n      | BadValue (envVarName, value) -\u003e\n        failwithf \"Environment variable %s has invalid value %s\" envVarName value\n      | NotSupported msg -\u003e \n        failwith msg\n```\n\n## Supported Data Types\n\nFsConfig supports the following data types and leverages their respective `TryParse` function to do the type conversion.\n\n* `Int16`, `Int32`, `Int64`, `UInt16`, `UInt32`, `UInt64`\n* `Byte`, `SByte`\n* `Single`, `Double`, `Decimal`\n* `Char`, `String`\n* `Bool`\n* `TimeSpan`, `DateTimeOffset`, `DateTime`\n* `Guid`\n* `Enum`\n* `list` of all the above types\n* `option` of all the above types\n\n### Option Type\n\nFsConfig allows us to specify optional configuration parameters using the `option` type. In the previous example, if the configuration parameter `Port` is optional, we can define it like \n\n```diff\ntype Config = {\n   ...\n-  Port : uint16\n+  Port : uint16 option\n}\n```\n\n### Discriminated Union Type\n\nFsConfig supports Discriminated Union Types that has cases alone. \n\n```diff\ntype Color =\n| Red\n| Green\n| Blue \n\ntype Config = {\n  ConsoleColor : Color\n}\n```\n\n\u003e With this configuration declaration, FsConfig read the environment variable `CONSOLE_COLOR` and populates the `ConsoleColor` field of type `Color`.\n\n\u003e List of Discriminated Union Types also supported!\n\n### List Type\n\nFsConfig also supports `list` type, and it expects comma separated individual values. \n\nFor example, to get mulitple ports, we can define the config as \n\n```fsharp\ntype Config = {\n  Port : uint16 list\n}\n```\n\nand then pass the value `8084,8085,8080` using the environment variable `PORT`.\n\nThe default separator for the list can be changed if needed using the `ListSeparator` attribute.\n\n```fsharp\n  [\u003cConvention(\"MYENV\")\u003e]\n  type CustomListSeparatorSampleConfig = {\n    ProcessNames : string list\n    [\u003cListSeparator(';')\u003e]\n    ProcessIds : uint16 list\n    [\u003cListSeparator('|')\u003e]\n    PipedFlow : int list    \n  }\n  ```\n\n\u003e With this configuration declaration, FSConfig would be able to read the following entries from App.settings.\n```xml\n  \u003cadd key=\"MYENVProcessNames\" value=\"conhost.exe,gitter.exe\"/\u003e\n  \u003cadd key=\"MYENVProcessIds\" value=\"4700;15680\"/\u003e\n  \u003cadd key=\"MYENVPipedFlow\" value=\"4700|15680|-1\" /\u003e\n```\n\nA definition similar to the one shown below will allow parsing of standalone lists.\n```fsharp\n  type IntListUsingSemiColonsConfig = {\n    [\u003cListSeparator(';')\u003e]\n    IntListUp : int list\n  }\n```\n\n\u003e E.g. an environment containing\n```ini\nINT_LIST_UP=42;43;44 \n```\n\n### Record Type\n\nAs shown in the initial example, FsConfig allows us to group similar configuration into a record type.\n\n```fsharp\ntype AwsConfig = {\n  AccessKeyId : string\n  DefaultRegion : string\n  SecretAccessKey : string\n}\n\ntype Config = {\n  Aws : AwsConfig\n}\n```\n\n\u003e With this configuration declaration, FsConfig read the environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_DEFAULT_REGION` and populates the `Aws` field of type `AwsConfig`.\n\n\n### Default Value\n\nIf you'd like to use a default value in the absence of a field value, you can make use of the `DefaultValue` attribute.\n\n```fsharp\ntype Config = {\n  [\u003cDefaultValue(\"8080\")\u003e]\n  HttpServerPort : int16\n  [\u003cDefaultValue(\"Server=localhost;Port=5432;Database=FsTweet;User Id=postgres;Password=test;\")\u003e]\n  DbConnectionString: string\n}\n```\n\n## Environment Variable Name Convention \u0026 Customization\n\nBy default, FsConfig follows Underscores with uppercase convention, as in `UPPER_CASE`, for deriving the environment variable name. \n\n```fsharp\ntype Config = {\n  ServerKey : string\n}\n```\n\n\u003e Using this configuration declaration, FsConfig read the environment variable `SERVER_KEY` and populates the `ServerKey` field\n\n\nTo specify a custom prefix in the environment variables, we can make use of the `Convention` attribute.\n\n```fsharp\n[\u003cConvention(\"FSTWEET\")\u003e]\ntype Config = {\n  ServerKey : string\n}\n```\n\n\u003e For this configuration declaration, FsConfig read the environment variable `FSTWEET_SERVER_KEY` and populates the `ServerKey` field.\n\nWe can also override the separator character `_` using the `Convention` attribute's optional field `Separator`\n\n```fsharp\n[\u003cConvention(\"FSTWEET\", Separator=\"-\")\u003e]\ntype Config = {\n  ServerKey : string\n}\n```\n\n\u003e In this case, FsConfig derives the environment variable name as `FSTWEET-SERVER-KEY`.\n\n\nIf an environment variable name is not following a convention, we can override the environment variable name at the field level using the `CustomName` attribute.\n\n```fsharp\ntype Config = {\n  [\u003cCustomName(\"MY_SERVER_KEY\")\u003e]\n  ServerKey : string\n}\n```\n\n\u003e Here, FsConfig uses the environment variable name `MY_SERVER_KEY` to get the ServerKey.\n\n\nWe can also merely customise (or control) the environment variable name generation by passing an higher-order function while calling the `Get` function\n\n```fsharp\nopen FsConfig\n\n// Prefix -\u003e string -\u003e string\nlet lowerCaseConfigNameCanonicalizer (Prefix prefix) (name : string) = \n  let lowerCaseName = name.ToLowerInvariant()\n  if String.IsNullOrEmpty prefix then \n    name.ToLowerInvariant()\n  else\n    sprintf \"%s-%s\" (prefix.ToLowerInvariant()) lowerCaseName\n\n\n[\u003cConvention(\"FSTWEET\")\u003e]\ntype Config = {\n  ServerKey : string\n}\n\nlet main argv =\n  let config = \n    match EnvConfig.Get\u003cConfig\u003e lowerCaseConfigNameCanonicalizer with\n    | Ok config -\u003e config\n    | Error error -\u003e failwithf \"Error : %A\" error\n``` \n\n\u003e FsConfig computes the environment variable name as `fstweet-server-key` in this scenario.\n\n## Getting Individual Environment Variables\n\nFsConfig also supports reading value directly by explicitly specifying the environment variable name\n\n```fsharp\nEnvConfig.Get\u003cdecimal\u003e \"MY_APP_INITIAL_BALANCE\" // Result\u003cdecimal, ConfigParseError\u003e\n```\n\n## App Config\n\nFsConfig supports App Config for both DotNet Core and Non DotNet Core Applications. \n\n* [DotNet Core Applications](#dotnet-core-configuration-supported-from-v200-or-above) (Supported from V2.0.0 or above)\n\n* [Non DotNet Core Applications](#appsettings-only-supported-in-v006-or-below) (Only Supported in V0.0.6 or below)\n\n### DotNet Core Configuration (Supported from V2.0.0 or above)\n\nFsConfig abstracts the configuration provider by depending on [IConfigurationRoot](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.iconfigurationroot).\n\n```fsharp\nlet configurationRoot : IConfigurationRoot = // ...\nlet appConfig = new AppConfig(configurationRoot)\n```\n\nAfter creating an instance `appConfig` (of type `AppConfig` from FsConfig), you can use it to read the configuration values as below\n\n```fsharp\n// Reading Primitive\nlet result = \n  appConfig.Get\u003cint\u003e \"processId\" // Result\u003cint, ConfigParseError\u003e\n\n// A Sample Record\ntype SampleConfig = {\n  ProcessId : int\n  ProcessName : string\n}\n\n// Reading a Record type\nlet result = \n  appConfig.Get\u003cSampleConfig\u003e () // Result\u003cSampleConfig, ConfigParseError\u003e\n\n// A Sample Nested Record\ntype AwsConfig = {\n  AccessKeyId : string\n  DefaultRegion : string\n  SecretAccessKey : string\n}\n\ntype Config = {\n  MagicNumber : int\n  Aws : AwsConfig\n}\n\n// Reading a Nested Record type\nlet result = \n  appConfig.Get\u003cConfig\u003e () // Result\u003cConfig, ConfigParseError\u003e\n```\n\nRefer below for creating `configurationRoot` based on the file type and using FsConfig to read the values.  \n\n#### JSON\n\n```json\n{\n  \"processId\" : \"123\",\n  \"processName\" : \"FsConfig\",\n  \"magicNumber\" : 42,\n  \"aws\" : {\n    \"accessKeyId\" : \"Id-123\",\n    \"defaultRegion\" : \"us-east-1\",\n    \"secretAccessKey\" : \"secret123\"\n  },\n  \"colors\" : \"Red,Green\"\n}\n```\n\nThis JSON file can be read using \n\n```fsharp\n// Requires NuGet package\n// Microsoft.Extensions.Configuration.Json\nlet configurationRoot =  \n  ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())\n    .AddJsonFile(\"settings.json\").Build()\n\nlet appConfig = new AppConfig(configurationRoot)\nlet result = \n  appConfig.Get\u003cConfig\u003e () // Result\u003cConfig, ConfigParseError\u003e\n```\n\n#### XML\n\n```xml\n\u003cSettings\u003e\n  \u003cProcessId\u003e123\u003c/ProcessId\u003e\n  \u003cProcessName\u003eFsConfig\u003c/ProcessName\u003e\n  \u003cMagicNumber\u003e42\u003c/MagicNumber\u003e\n  \u003cAws\u003e\n    \u003cAccessKeyId\u003eId-123\u003c/AccessKeyId\u003e\n    \u003cDefaultRegion\u003eus-east-1\u003c/DefaultRegion\u003e\n    \u003cSecretAccessKey\u003esecret123\u003c/SecretAccessKey\u003e\n  \u003c/Aws\u003e\n  \u003cColors\u003eRed,Green\u003c/Colors\u003e\n\u003c/Settings\u003e\n```\n\nThis XML file can be read using \n\n```fsharp\n// Requires NuGet package\n// Microsoft.Extensions.Configuration.Xml\nlet configurationRoot =  \n  ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())\n    .AddXmlFile(\"settings.xml\").Build()\n\nlet appConfig = new AppConfig(configurationRoot)\nlet result = \n  appConfig.Get\u003cConfig\u003e () // Result\u003cConfig, ConfigParseError\u003e\n```\n\n#### INI\n\n```ini\nProcessId=123\nProcessName=FsConfig\nMagicNumber=42\nColors=Red,Green\n\n[Aws]\nAccessKeyId=Id-123\nDefaultRegion=us-east-1\nSecretAccessKey=secret123\n```\n\nThis INI file can be read using \n\n```fsharp\n// Requires NuGet package\n// Microsoft.Extensions.Configuration.Ini\nlet configurationRoot =  \n  ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())\n    .AddIniFile(\"settings.ini\").Build()\n\nlet appConfig = new AppConfig(configurationRoot)\nlet result = \n  appConfig.Get\u003cConfig\u003e () // Result\u003cConfig, ConfigParseError\u003e\n```\n\n\n### appSettings (Only Supported in V0.0.6 or below)\n\nWe can read the `appSettings` values using the `AppConfig` type instead of `EnvConfig` type. \n\nFsConfig uses the exact name of the field to derive the `appSettings` key name and doesn't use any separator by default.\n\n```fsharp\ntype AwsConfig = {\n  AccessKeyId : string\n  DefaultRegion : string\n  SecretAccessKey : string\n}\n\ntype Config = {\n  Port : uint16\n  Aws : AwsConfig\n}\n\nlet main argv =\n  let config = \n    match AppConfig.Get\u003cConfig\u003e() with\n    | Ok config -\u003e config\n    | Error error -\u003e failwithf \"Error : %A\" error\n```\n\n\u003e The above code snippet looks for `appSettings` values with the name `Port`, `AwsAccessKeyId`, `AwsDefaultRegion`, `AwsSecretAccessKey` and populates the respective fields.\n\n\nAll the customisation that we have seen for `EnvConfig` is applicable for `AppConfig` as well.\n\n## How FsConfig Works\n\nIf you are curious to know how FsConfig works and its internals then you might be interested in my blog post, [Generic Programming Made Easy](https://www.demystifyfp.com/fsharp/blog/generic-programming-made-easy/) that deep dives into the initial implementation of FsConfig.\n\n## Feedback\n\n\u003e We all need people who will give us feedback. That's how we improve - Bill Gates.\n\nYour suggestions/feedback are welcome!\n\n## Acknowledgements\n\nThe idea of FsConfig is inspired by [Kelsey Hightower](https://twitter.com/kelseyhightower)'s golang library [envconfig](https://github.com/kelseyhightower/envconfig). \n\nFsConfig uses [Eirik Tsarpalis](https://twitter.com/eiriktsarpalis)'s [TypeShape](https://github.com/eiriktsarpalis/TypeShape) library for generic programming. \n\n\n## Maintainer(s)\n\n- [@tamizhvendan](https://github.com/tamizhvendan)\n","funding_links":["https://ko-fi.com/tamizhvendan"],"categories":["Configuration"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdemystifyfp%2FFsConfig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdemystifyfp%2FFsConfig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdemystifyfp%2FFsConfig/lists"}