StrapiConnect 1.1.2

dotnet add package StrapiConnect --version 1.1.2
                    
NuGet\Install-Package StrapiConnect -Version 1.1.2
                    
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="StrapiConnect" Version="1.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="StrapiConnect" Version="1.1.2" />
                    
Directory.Packages.props
<PackageReference Include="StrapiConnect" />
                    
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 StrapiConnect --version 1.1.2
                    
#r "nuget: StrapiConnect, 1.1.2"
                    
#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 StrapiConnect@1.1.2
                    
#: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=StrapiConnect&version=1.1.2
                    
Install as a Cake Addin
#tool nuget:?package=StrapiConnect&version=1.1.2
                    
Install as a Cake Tool

StrapiConnect

StrapiConnect is a lightweight .NET library that simplifies integration with a Strapi CMS backend using a resilient, pre-configured HttpClient. It includes built-in support for authentication and transient-fault handling using Polly.

Features

  • 📦 Easily registers a typed HttpClient for IStrapiConnect
  • 🔒 Supports Bearer Token authentication from config
  • 🔁 Polly retry policy with exponential backoff
  • 🚫 Validates BaseUrl config at startup
  • 💡 Clean DI extension for Program.cs or Startup.cs

Installation

Install via NuGet:

dotnet add package StrapiConnect

Usage

  1. Add Configuration to appsettings.json
"StrapiConnect": {
  "BaseUrl": "https://your-strapi-instance/api",
  "ApiKey": "your-secret-api-token"
}
  1. Register the Service in Program.cs
builder.Services.AddStrapiService(builder.Configuration);
  1. Inject and Use IStrapiService
using StrapiConnect;

var strapiConnect = new StrapiConnect("http://localhost:1337/api");
var request = new FindManyRequest("articles");
var result = await strapiConnect.ExecuteAsync<ArticleResponse>(request);
Console.WriteLine(result);

Strapi 5 Support

StrapiConnect works with both Strapi v4 and Strapi 5, and defaults to the native Strapi 5 response shape.

Response format (v4 vs v5)

By default no Strapi-Response-Format header is sent, so a Strapi 5 server replies in its native flattened shape: fields sit directly on data alongside documentId, with no attributes wrapper. To ask for the legacy v4 shape instead, pass StrapiResponseFormat.V4:

using StrapiConnect.Enums;

// Native Strapi 5 response — the default
var result = await strapiConnect.ExecuteAsync<ArticleResponse>(
    "http://localhost:1337/api", request);

// Legacy v4 response (fields under data.attributes, relations wrapped in { data: ... })
var legacy = await strapiConnect.ExecuteAsync<ArticleResponse>(
    "http://localhost:1337/api", request, StrapiResponseFormat.V4);

⚠️ Upgrading to 2.0.0 — breaking change

Versions 1.0.12 – 1.1.1 sent Strapi-Response-Format: v4 by default. From 2.0.0 the default is native v5. (Version 1.0.11 and earlier sent no header at all, so 2.0.0 restores that behaviour.)

If your models are shaped like the v4 response — a Data.Attributes wrapper, and relations as { data: { id, attributes } } — add StrapiResponseFormat.V4 to your calls to keep the old shape:

await strapiConnect.ExecuteAsync<T>(baseUrl, request, StrapiResponseFormat.V4);

If your models are flat (documentId, title, cover.url at the top level), you need no change — and if you were on 1.0.12–1.1.1, this fixes nested relations that were silently binding to null.

Fetch a single document by documentId

Strapi 5 addresses single records by their string documentId:

var request = new FindByDocumentIdRequest("articles", "znrlzntu9ei5onjvwfaalu2v");
var article = await strapiConnect.ExecuteAsync<ArticleResponse>(
    "http://localhost:1337/api", request, StrapiResponseFormat.V5);

FindByIdRequest(contentType, int id) remains available for v4-style numeric ids.

Create / update with relations

The client now supports POST / PUT / DELETE. Relations are managed with RelationBuilder (Connect / Disconnect / Set, with optional positioning), following the Strapi 5 relations API:

// Create an article and link categories
var create = new CreateRequest("articles")
    .WithBody(new RequestBody()
        .Field("title", "Hello world")
        .Relation("categories", new RelationBuilder().Connect("cat1").Connect("cat2")));

await strapiConnect.ExecuteAsync<ArticleResponse>(
    "http://localhost:1337/api", create, StrapiResponseFormat.V5);

// Update: connect with ordering, and disconnect another relation
var update = new UpdateRequest("articles", "znrlzntu9ei5onjvwfaalu2v")
    .WithBody(new RequestBody()
        .Relation("categories", new RelationBuilder()
            .Connect("cat3", RelationPosition.Start())
            .Disconnect("cat1")));

await strapiConnect.ExecuteAsync<ArticleResponse>(
    "http://localhost:1337/api", update, StrapiResponseFormat.V5);

// Replace all relations (cannot be combined with connect/disconnect)
var replace = new UpdateRequest("articles", "znrlzntu9ei5onjvwfaalu2v")
    .WithBody(new RequestBody()
        .Relation("categories", new RelationBuilder().Set("cat2", "cat3")));

// Delete by documentId (204 No Content returns null)
var delete = new DeleteRequest("articles", "znrlzntu9ei5onjvwfaalu2v");
await strapiConnect.ExecuteAsync<object>("http://localhost:1337/api", delete);

Positioning options: RelationPosition.Before(id), RelationPosition.After(id), RelationPosition.Start(), RelationPosition.End() (default).

Populate (and deep populate)

Strapi 5's native populate=* is only one level deep, so nested components/relations/media are not returned by it. There are three ways to get deeper data:

// 1. Everything, one level
request.PopulateAll();                               // populate=*

// 2. Explicit nesting (relations / single components) — any depth
request.Populate("author").Populate("avatar").PopulateAll();
// => populate[author][populate][avatar][populate]=*

// 3. Dynamic zones (e.g. `blocks`) — MUST use the per-component `on` form.
//    Strapi returns HTTP 400 for populate[blocks][populate][<field>]=...
var blocks = request.Populate("blocks");
blocks.OnComponent("blocks.hero").PopulateAll();
blocks.OnComponent("blocks.feature-block").Populate("features").PopulateAll();
// => populate[blocks][on][blocks.hero][populate]=*
//  & populate[blocks][on][blocks.feature-block][populate][features][populate]=*

Or, if your Strapi has the strapi-plugin-populate-deep plugin installed, populate everything to a depth in one call:

request.SetPopulateLevel(5);      // ?pLevel=5  (no-op if the plugin isn't installed)
Combining populate directives

Strapi has no "populate everything, plus this one relation narrowed" form: once you name relations, only those are populated. So these combinations resolve to one side, and it is worth knowing which:

You wrote You get Why
SetPopulateLevel(5) and Populate("cover")… pLevel=5 only — every populate[…] is discarded pLevel and explicit populate are mutually exclusive in the URL builder
PopulateAll() and Populate("cover")… the named relations only populate[*]=* reads as an attribute literally named *; ignored before Strapi 5.37, a 400 from 5.37 on
node.PopulateAll() and node.Populate("child")… the named child only same reason, one level down
zone.OnComponent(…) and zone.Populate("child")… the on form only Strapi rejects named nested populate on a dynamic zone

The first row is the easy one to trip over: adding Populate(...) to a request that already calls SetPopulateLevel has no effect at all. Pick one strategy per request.

Draft & Publish (status)

Strapi 5 removed the v4 publicationState parameter and replaced it with status. Use SetStatus to choose the draft or published version. With no call the Strapi default (published) applies.

using StrapiConnect.Enums;

request.SetStatus(StrapiStatus.Draft);       // ?status=draft
request.SetStatus(StrapiStatus.Published);   // ?status=published

Localization (locale)

request.SetLocale("en");      // ?locale=en

Filtering, sorting & pagination

// Equality emits the canonical Strapi filter form (a bare `field=value` is ignored
// by Strapi), and values are URL-encoded automatically.
request.Equal("slug", "my-post");                         // filters[slug][$eq]=my-post
request.Filter(FilterType.ContainsCaseInsensitive, "title", "hello"); // filters[title][$containsi]=hello
request.Filter(FilterType.Between, "price", "10,20");     // filters[price][$between][0]=10&[1]=20
request.Filter(FilterType.In, "category.slug", "tours");  // filters[category][slug][$in][0]=tours

// Multiple sorts produce distinct indices (sort[0], sort[1], …)
request.Sort("title", SortDirection.Ascending);           // sort[0]=title:asc
request.Sort("createdAt", SortDirection.Descending);      // sort[1]=createdAt:desc

request.SetPage(1);
request.SetPageSize(10);

Supported operators (via FilterType): $eq, $ne, $lt, $lte, $gt, $gte, $in, $notIn, $contains, $notContains, $startsWith, $endsWith, $null, $notNull, the case-insensitive variants $eqi/$nei/$containsi/$notContainsi/ $startsWithi/$endsWithi, and $between.

Error handling

By default a failed request (transport error, non-success status, or deserialization failure) is logged and returns default (null). To make failures throw instead, pass throwOnError: true; a StrapiRequestException (wrapping the original error) is raised:

var result = await strapiConnect.ExecuteAsync<ArticleResponse>(
    "http://localhost:1337/api", request, StrapiResponseFormat.V5, throwOnError: true);

Built-in Resilience with Polly

  • Retries 3 times on transient errors
  • Supports HttpStatusCode.TooManyRequests and SocketException
  • Exponential backoff (2s, 4s, 8s)

Releasing to NuGet

scripts/publish-nuget.sh sets the package version, builds, tests, packs and pushes the package in one step:

# Validate the whole pipeline without publishing (no API key needed)
./scripts/publish-nuget.sh 1.0.1 --dry-run

# Publish, then tag the release
NUGET_API_KEY=oy2... ./scripts/publish-nuget.sh 1.0.1 --tag

The version argument must be valid SemVer (1.0.1, 1.2.0-beta.1). It is written into src/StrapiConnect/StrapiConnect.csproj as <Version>; on a dry run or any failure the original value is restored, and after a successful push the bump is kept so you can commit it.

Option Purpose
--api-key <key> NuGet API key (default $NUGET_API_KEY)
--source <url> Push target (default $NUGET_SOURCE or nuget.org)
--output <dir> .nupkg output directory (default artifacts/nuget)
--configuration <cfg> Build configuration (default Release)
--skip-tests Skip dotnet test
--keep-version Publish the version already in the .csproj
--dry-run Build and pack only, no push
--tag Create and push git tag v<version> after publishing

Run ./scripts/publish-nuget.sh --help for the full reference. The Release NuGet Package GitHub Actions workflow remains available for publishing from CI.

Product Compatible and additional computed target framework versions.
.NET 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 was computed.  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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.2 65 8/25/2026
1.1.1 87 8/24/2026
1.0.12 203 5/14/2026
1.0.11 1,622 9/19/2025
1.0.10 240 8/13/2025
1.0.9 219 7/14/2025
1.0.8 259 6/2/2025
1.0.7 617 5/24/2025
1.0.6 181 5/23/2025
1.0.5 196 5/23/2025
1.0.4 238 5/22/2025
1.0.3 232 5/22/2025
1.0.2 237 5/22/2025
1.0.1 233 5/22/2025
1.0.0 232 5/22/2025