A modern .NET client for the Storyblok Headless CMS API. This project is a C# port of the official storyblok-js-client, maintaining feature parity while providing a strongly-typed interface for .NET applications. It supports both Storyblok's Content Delivery API (v2) and Management API (v1).
As a personal project be kind! This is shared as is and is not for production.
This project is a port of the storyblok-js-client JavaScript library, created and maintained by the Storyblok team (@storyblok). We are grateful for their excellent work which serves as the foundation for this .NET implementation.
Additionally, we've included the richtext implementation of richtext Javascript library, used to interact with the richtext components.
License: This project, like the original storyblok-js-client, is licensed under MIT.
Original Client Repository: https://github.com/storyblok/storyblok-js-client Original Richtext Repository: https://github.com/storyblok/richtext
This library is based on the official storyblok-js-client JavaScript library and follows the same core principles and features. We've tried to carefully translated the JavaScript implementation to idiomatic C#, taking advantage of .NET's type system and modern features while maintaining compatibility with Storyblok's APIs. Key aspects we've preserved include:
- Same configuration options and initialization patterns
- Compatible caching mechanisms
- Equivalent rate limiting and retry logic
- Parallel rich text rendering capabilities
- Similar helper utilities and extension methods
- 🚀 Built for modern .NET (targets .NET 9.0)
- 💪 Strongly typed story content
- 🔄 Automatic rate limiting and retry handling
- 💾 Configurable caching
- 🔍 Rich text rendering
- 🔒 Thread-safe operations
- 📦 Available as a NuGet package
- 🔗 Dependency injection friendly
- 🏎️ Async/await support
- 💡Support for both published and draft content
- 💡Comprehensive story querying and filtering
- 💡Rich text rendering with customizable schema
- 💡Built-in caching with memory and custom providers
- 💡Rate limiting and retry handling
- 💡Blazor component integration
- 💡Image optimization
- 💡HTML sanitization
Install via Github packages - NuGet:
dotnet add package StoryblokSharpThe NuGet package is hosted in this GitHub repository's package registry.
Retrieve a single story by its slug:
// Get a published storyvarstory=await_client.GetStoryAsync<HomeContent>("home",newStoryQueryParameters{Version="published"});// Get a draft storyvardraftStory=await_client.GetStoryAsync<HomeContent>("home",newStoryQueryParameters{Version="draft"});// Get a story in a specific languagevargermanStory=await_client.GetStoryAsync<HomeContent>("home",newStoryQueryParameters{Version="published",Language="de"});Retrieve multiple stories with filtering and pagination:
// Get all published storiesvarstories=await_client.GetStoriesAsync<BlogPost>(newStoryQueryParameters{Version="published",PerPage=10,Page=1});// Get stories with specific tagsvartaggedStories=await_client.GetStoriesAsync<BlogPost>(newStoryQueryParameters{WithTag="featured",SortBy="created_at:desc"});// Get all stories (handles pagination automatically)varallStories=await_client.GetAllAsync<BlogPost>("cdn/stories",newStoryQueryParameters{StartsWith="blog/"});The StoryQueryParameters class provides various filtering and sorting options:
varparameters=newStoryQueryParameters{// Version controlVersion="published",// or "draft"// PaginationPerPage=10,Page=1,// FilteringStartsWith="blog/",WithTag="featured",SearchTerm="tutorial",ExcludingFields="body,image",// SortingSortBy="created_at:desc",// LanguageLanguage="en",FallbackLang="de",// RelationsResolveLinks="1",ResolveRelations=new[]{"author","categories"},ResolveLevel=2};You can create strongly-typed models for your content:
publicclassBlogPost{publicstringTitle{get;set;}publicstringSlug{get;set;}publicRichTextFieldContent{get;set;}publicAssetFeaturedImage{get;set;}publicDateTimePublishedDate{get;set;}publicstring[]Tags{get;set;}}// Use the typed modelvarresponse=await_client.GetStoryAsync<BlogPost>("my-blog-post");vartitle=response.Story.Content.Title;varcontent=response.Story.Content.Content;// Configure servicesservices.AddStoryblokClient(builder =>builder.WithAccessToken("your_access_token").WithCache(options =>options.WithType(CacheType.Memory).WithDefaultExpiration(TimeSpan.FromMinutes(5))).WithMaxRetries(3).WithRateLimit(5));// Inject and use the clientpublicclassMyService{privatereadonlyIStoryblokClient_client;publicMyService(IStoryblokClientclient){_client=client;}publicasyncTask<Story<T>>GetStoryAsync<T>()whereT:class{varparameters=newStoryQueryParameters{Version="published",Language="en"};varresponse=await_client.GetStoryAsync<T>("home",parameters);returnresponse.Story;}}The client can be configured with various options:
services.AddStoryblokClient(builder =>builder.WithAccessToken("your_access_token").WithOAuthToken("your_oauth_token")// For management API.WithRegion(Region.EU).WithHttps().WithMaxRetries(3).WithTimeout(30).WithRateLimit(5).WithHeaders(newDictionary<string,string>{["Custom-Header"]="Value"}).WithCache(options =>options.WithType(CacheType.Memory).WithDefaultExpiration(TimeSpan.FromMinutes(5))).WithCustomCache(newYourCustomCacheProvider()).WithRichTextSchema(newYourCustomSchema()).WithComponentResolver((type,props)=>$"<div>{type}</div>").WithResponseInterceptor(async response =>{// Custom response handlingreturnresponse;}));The library includes a powerful rich text renderer with support for custom resolvers:
services.Configure<RichTextOptions>(options =>{options.OptimizeImages=true;options.KeyedResolvers=true;options.InvalidNodeHandling=InvalidNodeStrategy.Remove;options.MarkSortPriority=new[]{MarkTypes.Bold,MarkTypes.Italic,MarkTypes.Link};});StoryblokSharp provides seamless integration with Blazor components:
// Register Blazor component resolverservices.AddBlazorComponentResolver();// Register a componentservices.AddStoryblokComponent<HeroComponent>("hero");// Create a Blazor componentpublicclassHeroComponent:ComponentBase,IComponent{[Parameter]publicstringTitle{get;set;}[Parameter]publicstringSubtitle{get;set;}protectedoverridevoidBuildRenderTree(RenderTreeBuilderbuilder){builder.OpenElement(0,"div");builder.AddAttribute(1,"class","hero");builder.OpenElement(2,"h1");builder.AddContent(3,Title);builder.CloseElement();builder.OpenElement(4,"p");builder.AddContent(5,Subtitle);builder.CloseElement();builder.CloseElement();}}// Use in Storyblok content
@{varstory=awaitStoryblokClient.GetStoryAsync<dynamic>("home");varcomponent=story.Story.Content.hero;}<StoryblokComponentType="hero"Props="@component"/>Configure image optimization options:
services.Configure<RichTextOptions>(options =>{options.OptimizeImages=true;options.ImageOptions=newImageOptimizationOptions{Width=800,Height=600,Loading="lazy",Class="optimized-image",SrcSet=new[]{newSrcSetEntry{Width=400},newSrcSetEntry{Width=800},newSrcSetEntry{Width=1200}},Sizes=new[]{"(max-width: 400px) 100vw","(max-width: 800px) 50vw","800px"},Filters=newImageFilters{Quality=80,Format="webp",Grayscale=false}}});The library includes built-in HTML sanitization:
services.Configure<HtmlSanitizerOptions>(options =>{options.AllowedTags.Add("custom-tag");options.AllowedAttributes["a"].Add("rel");options.AllowedProtocols.Add("tel");});Contributions are welcome! Please read our Contributing Guide for details.
This project is licensed under the MIT License - see the LICENSE file for details.
You can customize story queries with various parameters:
varparameters=newStoryQueryParameters{Version="draft",// 'draft' or 'published'Language="en",// Language codeResolveLinks="story",// How to resolve linksResolveRelations=new[]{"author"},// Relations to resolveExcludingFields="long_text",// Fields to excludeSort_by="position:desc",// Sort orderStartsWith="blog/",// Filter by pathWithTag="featured",// Filter by tagPage=1,// Page numberPerPage=10// Items per page};varstories=await_storyblok.GetStoriesAsync<BlogPostContent>(parameters);The library includes built-in HTML sanitization:
services.Configure<HtmlSanitizerOptions>(options =>{options.AllowedTags.Add("custom-tag");options.AllowedAttributes["a"].Add("rel");options.AllowedProtocols.Add("tel");});Contributions are welcome! Please read our Contributing Guide for details.
This project is licensed under the MIT License - see the LICENSE file for details.