Frank 7.2.0

dotnet add package Frank --version 7.2.0
                    
NuGet\Install-Package Frank -Version 7.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Frank" Version="7.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Frank" Version="7.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Frank" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Frank --version 7.2.0
                    
#r "nuget: Frank, 7.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Frank@7.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Frank&version=7.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Frank&version=7.2.0
                    
Install as a Cake Tool

Frank

NuGet Version GitHub Release Date Build status

F# computation expressions, or builders, for configuring the Microsoft.AspNetCore.Hosting.IWebHostBuilder and defining routes for HTTP resources using Microsoft.AspNetCore.Routing.

This project was inspired by @filipw's Building Microservices with ASP.NET Core (without MVC).


Packages

Package Description NuGet
Frank Core computation expressions for WebHost and routing NuGet
Frank.Auth Resource-level authorization extensions NuGet
Frank.OpenApi Native OpenAPI document generation with F# type schemas NuGet
Frank.Datastar Datastar SSE integration for reactive hypermedia NuGet
Frank.Analyzers F# Analyzers for compile-time error detection NuGet

Features

  • WebHostBuilder - computation expression for configuring WebHost
  • ResourceBuilder - computation expression for configuring resources (routing)
  • No pre-defined view engine - use your preferred view engine implementation, e.g. Falco.Markup, Oxpecker.ViewEngine, or Hox
  • Easy extensibility - just extend the Builder with your own methods!

Basic Example

module Program

open System.IO
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Routing
open Microsoft.AspNetCore.Routing.Internal
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Logging
open Frank
open Frank.Builder

let home =
    resource "/" {
        name "Home"

        get (fun (ctx:HttpContext) ->
            ctx.Response.WriteAsync("Welcome!"))
    }

[<EntryPoint>]
let main args =
    webHost args {
        useDefaults

        logging (fun options-> options.AddConsole().AddDebug())

        plugWhen isDevelopment DeveloperExceptionPageExtensions.UseDeveloperExceptionPage
        plugWhenNot isDevelopment HstsBuilderExtensions.UseHsts

        plugBeforeRouting HttpsPolicyBuilderExtensions.UseHttpsRedirection
        plugBeforeRouting StaticFileExtensions.UseStaticFiles

        resource home
    }

    0

Middleware Pipeline

Frank provides two middleware operations with different positions in the ASP.NET Core pipeline:

Request → plugBeforeRouting → UseRouting → plug → Endpoints → Response

plugBeforeRouting

Use for middleware that must run before routing decisions are made:

  • HttpsRedirection - redirect before routing
  • StaticFiles - serve static files without routing overhead
  • ResponseCompression - compress all responses
  • ResponseCaching - cache before routing
webHost args {
    plugBeforeRouting HttpsPolicyBuilderExtensions.UseHttpsRedirection
    plugBeforeRouting StaticFileExtensions.UseStaticFiles
    resource myResource
}

plug

Use for middleware that needs routing information (e.g., the matched endpoint):

  • Authentication - may need endpoint metadata
  • Authorization - requires endpoint to check policies
  • CORS - may use endpoint-specific policies
webHost args {
    plug AuthenticationBuilderExtensions.UseAuthentication
    plug AuthorizationAppBuilderExtensions.UseAuthorization
    resource protectedResource
}

Conditional Middleware

Both plugWhen and plugWhenNot run in the plug position (after routing):

webHost args {
    plugWhen isDevelopment DeveloperExceptionPageExtensions.UseDeveloperExceptionPage
    plugWhenNot isDevelopment HstsBuilderExtensions.UseHsts
    resource myResource
}

Conditional Before-Routing Middleware

Both plugBeforeRoutingWhen and plugBeforeRoutingWhenNot run in the plugBeforeRouting position (before routing):

let isDevelopment (app: IApplicationBuilder) =
    app.ApplicationServices
        .GetService<IWebHostEnvironment>()
        .IsDevelopment()

webHost args {
    // Only redirect to HTTPS in production
    plugBeforeRoutingWhenNot isDevelopment HttpsPolicyBuilderExtensions.UseHttpsRedirection

    // Only serve static files locally in development (CDN in production)
    plugBeforeRoutingWhen isDevelopment StaticFileExtensions.UseStaticFiles

    resource myResource
}

Frank.Auth

Frank.Auth provides resource-level authorization for Frank applications, integrating with ASP.NET Core's built-in authorization infrastructure.

Installation

dotnet add package Frank.Auth

Protecting Resources

Add authorization requirements directly to resource definitions:

open Frank.Builder
open Frank.Auth

// Require any authenticated user
let dashboard =
    resource "/dashboard" {
        name "Dashboard"
        requireAuth
        get (fun ctx -> ctx.Response.WriteAsync("Welcome to Dashboard"))
    }

// Require a specific claim
let adminPanel =
    resource "/admin" {
        name "Admin"
        requireClaim "role" "admin"
        get (fun ctx -> ctx.Response.WriteAsync("Admin Panel"))
    }

// Require a role
let engineering =
    resource "/engineering" {
        name "Engineering"
        requireRole "Engineering"
        get (fun ctx -> ctx.Response.WriteAsync("Engineering Portal"))
    }

// Reference a named policy
let reports =
    resource "/reports" {
        name "Reports"
        requirePolicy "CanViewReports"
        get (fun ctx -> ctx.Response.WriteAsync("Reports"))
    }

// Compose requirements (AND semantics — all must pass)
let sensitive =
    resource "/api/sensitive" {
        name "Sensitive"
        requireAuth
        requireClaim "scope" "admin"
        requireRole "Engineering"
        get (fun ctx -> ctx.Response.WriteAsync("Sensitive data"))
    }

Application Wiring

Configure authentication and authorization services using Frank's builder syntax:

[<EntryPoint>]
let main args =
    webHost args {
        useDefaults

        useAuthentication (fun auth ->
            // Configure your authentication scheme here
            auth)

        useAuthorization

        authorizationPolicy "CanViewReports" (fun policy ->
            policy.RequireClaim("scope", "reports:read") |> ignore)

        resource dashboard
        resource adminPanel
        resource reports
    }
    0

Authorization Patterns

Pattern Operation Behavior
Authenticated user requireAuth 401 if unauthenticated, 200 if authenticated
Claim (single value) requireClaim "type" "value" 403 if claim missing or wrong value
Claim (multiple values) requireClaim "type" ["a"; "b"] 200 if user has any listed value (OR)
Role requireRole "Admin" 403 if user not in role
Named policy requirePolicy "PolicyName" Delegates to registered policy
Multiple requirements Stack multiple require* AND semantics — all must pass
No requirements (default) Publicly accessible, zero overhead

Frank.OpenApi

Frank.OpenApi provides native OpenAPI document generation for Frank applications, with first-class support for F# types and declarative metadata using computation expressions.

Installation

dotnet add package Frank.OpenApi

HandlerBuilder Computation Expression

Define handlers with embedded OpenAPI metadata using the handler computation expression:

open Frank.Builder
open Frank.OpenApi

type Product = { Name: string; Price: decimal }
type CreateProductRequest = { Name: string; Price: decimal }

let createProductHandler =
    handler {
        name "createProduct"
        summary "Create a new product"
        description "Creates a new product in the catalog"
        tags [ "Products"; "Admin" ]
        produces typeof<Product> 201
        accepts typeof<CreateProductRequest>
        handle (fun (ctx: HttpContext) -> task {
            let! request = ctx.Request.ReadFromJsonAsync<CreateProductRequest>()
            let product = { Name = request.Name; Price = request.Price }
            ctx.Response.StatusCode <- 201
            do! ctx.Response.WriteAsJsonAsync(product)
        })
    }

let productsResource =
    resource "/products" {
        name "Products"
        post createProductHandler
    }

HandlerBuilder Operations

Operation Description
name "operationId" Sets the OpenAPI operationId
summary "text" Brief summary of the operation
description "text" Detailed description
tags [ "Tag1"; "Tag2" ] Categorize endpoints
produces typeof<T> statusCode Define response type and status code
produces typeof<T> statusCode ["content/type"] Response with content negotiation
producesEmpty statusCode Empty responses (204, 404, etc.)
accepts typeof<T> Define request body type
accepts typeof<T> ["content/type"] Request with content negotiation
handle (fun ctx -> ...) Handler function (supports Task, Task<'a>, Async<unit>, Async<'a>)

F# Type Schema Generation

Frank.OpenApi automatically generates JSON schemas for F# types:

// F# records with required and optional fields
type User = {
    Id: Guid
    Name: string
    Email: string option  // Becomes nullable in schema
}

// Discriminated unions (anyOf/oneOf)
type Response =
    | Success of data: string
    | Error of code: int * message: string

// Collections
type Products = {
    Items: Product list
    Tags: Set<string>
    Metadata: Map<string, string>
}

WebHostBuilder Integration

Enable OpenAPI document generation in your application:

[<EntryPoint>]
let main args =
    webHost args {
        useDefaults
        useOpenApi  // Adds /openapi/v1.json endpoint

        resource productsResource
    }
    0

The OpenAPI document will be available at /openapi/v1.json.

Content Negotiation

Define multiple content types for requests and responses:

handler {
    name "getProduct"
    produces typeof<Product> 200 [ "application/json"; "application/xml" ]
    accepts typeof<ProductQuery> [ "application/json"; "application/xml" ]
    handle (fun ctx -> task { (* ... *) })
}

Backward Compatibility

Frank.OpenApi is fully backward compatible with existing Frank applications. You can:

  • Mix HandlerDefinition and plain RequestDelegate handlers in the same resource
  • Add OpenAPI metadata incrementally without changing existing code
  • Use the library only where you need API documentation

Frank.Datastar

Frank.Datastar provides seamless integration with Datastar, enabling reactive hypermedia applications using Server-Sent Events (SSE).

Version 7.1.0 features a native SSE implementation with zero external dependencies, delivering high-performance Server-Sent Events directly via ASP.NET Core's IBufferWriter<byte> API. Supports .NET 8.0, 9.0, and 10.0.

Installation

dotnet add package Frank.Datastar

Example

open Frank.Builder
open Frank.Datastar

let updates =
    resource "/updates" {
        name "Updates"

        datastar (fun ctx -> task {
            // SSE stream starts automatically
            do! Datastar.patchElements "<div id='status'>Loading...</div>" ctx
            do! Task.Delay(500)
            do! Datastar.patchElements "<div id='status'>Complete!</div>" ctx
        })
    }

// With explicit HTTP method
let submit =
    resource "/submit" {
        name "Submit"

        datastar HttpMethods.Post (fun ctx -> task {
            let! signals = Datastar.tryReadSignals<FormData> ctx
            match signals with
            | ValueSome data ->
                do! Datastar.patchElements $"<div id='result'>Received: {data.Name}</div>" ctx
            | ValueNone ->
                do! Datastar.patchElements "<div id='error'>Invalid data</div>" ctx
        })
    }

Available Operations

  • Datastar.patchElements - Update HTML elements in the DOM
  • Datastar.patchSignals - Update client-side signals
  • Datastar.removeElement - Remove elements by CSS selector
  • Datastar.executeScript - Execute JavaScript on the client
  • Datastar.tryReadSignals<'T> - Read and deserialize signals from request

Each operation also has a WithOptions variant for advanced customization.


Frank.Analyzers

Frank.Analyzers provides compile-time static analysis to catch common mistakes in Frank applications.

Installation

dotnet add package Frank.Analyzers

Available Analyzers

FRANK001: Duplicate HTTP Handler Detection

Detects when multiple handlers for the same HTTP method are defined on a single resource. Only the last handler would be used at runtime, so this is almost always a mistake.

// This will produce a warning:
resource "/example" {
    name "Example"
    get (fun ctx -> ctx.Response.WriteAsync("First"))   // Warning: FRANK001
    get (fun ctx -> ctx.Response.WriteAsync("Second"))  // This one takes effect
}

IDE Integration

Frank.Analyzers works with:

  • Ionide (VS Code)
  • Visual Studio with F# support
  • JetBrains Rider

Warnings appear inline as you type, helping catch issues before you even compile.


Building

Make sure the following requirements are installed in your system:

dotnet build

Sample Applications

The sample/ directory contains several example applications:

Sample Description
Sample Basic Frank application
Frank.OpenApi.Sample Product Catalog API demonstrating OpenAPI document generation
Frank.Datastar.Basic Datastar integration with minimal HTML
Frank.Datastar.Hox Datastar with Hox view engine
Frank.Datastar.Oxpecker Datastar with Oxpecker.ViewEngine
Frank.Falco Frank with Falco.Markup
Frank.Giraffe Frank with Giraffe.ViewEngine
Frank.Oxpecker Frank with Oxpecker.ViewEngine

License

Apache 2.0

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Frank:

Package Downloads
Frank.Datastar

Datastar SSE integration for Frank web framework with F# computation expression support. Native SSE implementation with no external dependencies, supports .NET 8.0/9.0/10.0.

Frank.Auth

Resource-level authorization extensions for Frank web framework

Frank.OpenApi

OpenAPI document generation extensions for Frank web framework

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
7.2.0 136 2/10/2026
7.1.0 112 2/8/2026
7.0.0-build.0 46 2/6/2026
6.5.0 111 2/5/2026
6.4.1 116 2/4/2026
6.4.1-build.0 48 2/4/2026
6.4.0 91 2/2/2026
6.4.0-build.0 45 2/2/2026
6.3.0 447 3/15/2025
6.2.0 5,457 11/18/2020
6.1.0 1,892 6/11/2020
6.0.0 913 6/2/2020
5.0.5 1,072 1/7/2019
5.0.4 1,020 1/6/2019
5.0.3 1,074 1/5/2019
5.0.2 1,074 1/5/2019
5.0.1 1,058 1/5/2019
5.0.0 1,053 1/5/2019
4.0.0 1,835 3/28/2018
Loading failed

### New in 7.2.0 (Released 2026-02-10)
**Frank.OpenApi - Native OpenAPI Document Generation Support**
- **New Library:** Frank.OpenApi extension library for declarative OpenAPI metadata
- **HandlerBuilder CE:** Computation expression for defining handlers with embedded OpenAPI metadata:
- `name` — operationId for the endpoint
- `summary` / `description` — operation documentation
- `tags` — endpoint categorization
- `produces typeof<T> statusCode [contentTypes]` — response types with optional content negotiation
- `producesEmpty statusCode` — empty responses (204, 404, etc.)
- `accepts typeof<T> [contentTypes]` — request types with optional content negotiation
- `handle` — supports Task, Task<'a>, Async<unit>, Async<'a>
- **ResourceBuilder Extensions:** All HTTP method operations (`get`, `post`, `put`, `delete`, `patch`, `head`, `options`) accept HandlerDefinition
- **F# Type Schemas:** Automatic JSON Schema generation for F# types via FSharpSchemaTransformer:
- Records with required and optional fields
- Discriminated unions with anyOf/oneOf
- Collections (list, Set, Map)
- Option types as nullable
- **WebHostBuilder Integration:** `useOpenApi` operation to enable OpenAPI document generation at `/openapi/v1.json`
- **Content Negotiation:** Full support for multiple content types (application/json, application/xml, etc.)
- **No Breaking Changes:** Per-handler metadata via method-specific conventions — fully backward compatible
- **Multi-Targeting:** Supports .NET 10.0 (LTS)
- **Core Fix:** Added MethodInfo to endpoint metadata for OpenAPI discovery (required by ASP.NET Core's EndpointMetadataApiDescriptionProvider)
**Example Usage:**
```fsharp
handler {
name "createProduct"
summary "Create a new product"
tags [ "Products"; "Admin" ]
produces typeof<Product> 201
accepts typeof<CreateProductRequest>
handle (fun ctx -> async { return! createProduct ctx })
}
```
### New in 7.1.0 (Released 2026-02-07)
**Frank.Datastar - Native SSE Implementation & Stream-Based HTML Generation**
- **Performance:** Replaced StarFederation.Datastar.FSharp dependency with native SSE implementation using `IBufferWriter<byte>` for zero-copy buffer writing
- **Zero External Dependencies:** Frank.Datastar now has no external NuGet dependencies beyond framework references and Frank core
- **Multi-Targeting Restored:** Supports .NET 8.0, 9.0, and 10.0 (`net8.0;net9.0;net10.0`)
- **API Compatibility:** Zero breaking changes — seamless upgrade from 7.0.x with identical public API surface
- **Performance Optimizations:**
- Pre-allocated byte arrays for SSE field prefixes (no runtime UTF-8 encoding)
- Zero-allocation string segmentation via `StringTokenizer` for multi-line payloads
- Direct buffer writing without intermediate copies
- Per-event flushing for immediate delivery
- **ADR Compliance:** Full conformance to Datastar SDK ADR specification for SSE message format
- **Added:** `Attributes` field to `ExecuteScriptOptions` for custom script tag attributes (additive, non-breaking)
- **Public API:** `ServerSentEventGenerator` now public for advanced SSE event construction
- **Stream-Based Overloads:** Added stream-based SSE operations for zero-allocation HTML rendering:
- All SSE operations now have stream-based overloads accepting `TextWriter -> Task` writer functions
- `streamPatchElements`, `streamPatchSignals`, `streamRemoveElement`, `streamExecuteScript` module functions
- Eliminates full HTML string materialization — 50%+ allocation reduction in high-throughput scenarios (1000+ events/sec)
- Compatible with view engines supporting `TextWriter` output (e.g., Hox `Render.toTextWriter`)
- String-based API remains unchanged for backward compatibility
- Internal `SseDataLineWriter` handles SSE line-splitting transparently
### New in 7.0.0 (Released 2026-02-05)
- **Breaking:** Added `Metadata` field to `ResourceSpec` and `AddMetadata` to `ResourceBuilder` for composable endpoint metadata conventions
- Added `plugBeforeRoutingWhen` for conditional middleware before routing when condition is true
- Added `plugBeforeRoutingWhenNot` for conditional middleware before routing when condition is false
- Added **Frank.Auth** library for resource-level authorization:
- `requireAuth` — require authenticated user
- `requireClaim` — require a specific claim type and value(s)
- `requireRole` — require a specific role
- `requirePolicy` — require a named authorization policy
- `useAuthentication` / `useAuthorization` — configure auth services and middleware on the web host
- `authorizationPolicy` — define named authorization policies on the web host
### New in 6.5.0 (Released 2026-02-04)
- Fixed middleware pipeline ordering: `plug` middleware now runs after `UseRouting` and before `UseEndpoints`
- Added `plugBeforeRouting` for middleware that must run before routing (e.g., StaticFiles, HttpsRedirection)
- Added middleware ordering tests
### New in 6.4.1 (Released 2026-02-04)
- Add Frank.Analyzers to assist with validating resource definitions
- Added additional Frank.Datastar helpers to use more StarFederation.Datastar options
### New in 6.4.0 (Released 2026-02-02)
- Updated to target net8.0, net9.0, and net10.0
- Add Frank.Datastar
- Updated samples and added samples for Frank.Datastar
### New in 6.3.0 (Released 2025-03-14)
- Updated to target net8.0 and net9.0
- Updated examples
### New in 6.2.0 (Released 2020-11-18)
- Updated samples
### New in 6.1.0 (Released 2020-06-11)
- Encapsulate `IHostBuilder` and expose option to use web builder defaults with `useDefaults`.
- Server application can now be simply a standard console application. See [samples](https://github.com/frank-fs/frank/tree/master/sample).
### New in 6.0.0 (Released 2020-06-02)
- Update to .NET Core 3.1
- Use Endpoint Routing
- Pave the way for built-in generation of Open API spec
### New in 5.0.0 (Released 2019-01-05)
- Starting over based on ASP.NET Core Routing and Hosting
- New MIT license
- Computation expression for configuring IWebHostBuilder
- Computation expression for specifying HTTP resources
- Sample using simple ASP.NET Core web application
- Sample using standard Giraffe template web application
### New in 4.0.0 - (Released 2018/03/27)
- Update to .NETStandard 2.0 and .NET 4.6.1
- Now more easily used with Azure Functions or ASP.NET Core
### New in 3.1.1 - (Released 2014/12/07)
- Use FSharp.Core from NuGet
### New in 3.1.0 - (Released 2014/10/13)
- Remove dependency on F#x
- Signatures remain equivalent, but some type aliases have been removed.
### New in 3.0.19 - (Released 2014/10/13)
- Merge all implementations into one file and add .fsi signature
### New in 3.0.18 - (Released 2014/10/12)
- Use Paket for package management
- FSharp.Core 4.3.1.0
- NOTE: Jumped to 3.0.18 due to bad build script configuration
### New in 3.0.0 - (Released 2014/05/24)
- Updated dependencies to Web API 2.1 and .NET 4.5
### New in 2.0.3 - (Released 2014/02/07)
- Add SourceLink to link to GitHub sources (courtesy Cameron Taggart).
### New in 2.0.2 - (Released 2014/01/26)
- Remove FSharp.Core.3 as a package dependency.
### New in 2.0.0 - (Released 2014/01/07)
- Generate documentation with every release
- Fix a minor bug in routing (leading '/' was not stripped)
- Reference FSharp.Core.3 NuGet package
- Release assembly rather than current source packages:
- FSharp.Net.Http
- FSharp.Web.Http
- Frank
- Adopt the FSharp.ProjectScaffold structure
### New in 1.1.1 - (Released 2014/01/01)
- Correct spacing and specify additional types in HttpContent extensions.
### New in 1.1.0 - (Released 2014/01/01)
- Remove descriptor-based implementation.
### New in 1.0.2 - (Released 2013/12/10)
- Restore Frank dependency on FSharp.Web.Http. Otherwise, devs will have to create their own routing mechanisms. A better solution is on its way.
### New in 1.0.1 - (Released 2013/12/10)
- Change Web API dependency to Microsoft.AspNet.WebApi.Core.
### New in 1.0.0 - (Released 2013/12/10)
- First official release.
- Use an Option type for empty content.