StrapiConnect 1.1.1
See the version list below for details.
dotnet add package StrapiConnect --version 1.1.1
NuGet\Install-Package StrapiConnect -Version 1.1.1
<PackageReference Include="StrapiConnect" Version="1.1.1" />
<PackageVersion Include="StrapiConnect" Version="1.1.1" />
<PackageReference Include="StrapiConnect" />
paket add StrapiConnect --version 1.1.1
#r "nuget: StrapiConnect, 1.1.1"
#:package StrapiConnect@1.1.1
#addin nuget:?package=StrapiConnect&version=1.1.1
#tool nuget:?package=StrapiConnect&version=1.1.1
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
HttpClientforIStrapiConnect - 🔒 Supports Bearer Token authentication from config
- 🔁 Polly retry policy with exponential backoff
- 🚫 Validates
BaseUrlconfig at startup - 💡 Clean DI extension for
Program.csorStartup.cs
Installation
Install via NuGet:
dotnet add package StrapiConnect
Usage
- Add Configuration to appsettings.json
"StrapiConnect": {
"BaseUrl": "https://your-strapi-instance/api",
"ApiKey": "your-secret-api-token"
}
- Register the Service in Program.cs
builder.Services.AddStrapiService(builder.Configuration);
- 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. The defaults are backward compatible: existing code keeps requesting the legacy v4 response shape, so no changes are required when upgrading the package.
Response format (v4 vs v5)
By default the client sends the Strapi-Response-Format: v4 header, so a Strapi 5
server replies in the legacy shape (fields nested under data.attributes). To opt
into the native Strapi 5 flattened shape (fields directly on data, with
documentId), pass StrapiResponseFormat.V5:
using StrapiConnect.Enums;
// Native Strapi 5 response (flattened, no `attributes` wrapper)
var result = await strapiConnect.ExecuteAsync<ArticleResponse>(
"http://localhost:1337/api", request, StrapiResponseFormat.V5);
// Legacy v4 response (default — equivalent to the call above without the enum)
var legacy = await strapiConnect.ExecuteAsync<ArticleResponse>(
"http://localhost:1337/api", request, StrapiResponseFormat.V4);
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)
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 | Versions 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. |
-
net9.0
- Microsoft.Extensions.Http.Polly (>= 9.0.8)
- Serilog.AspNetCore (>= 9.0.0)
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 | 129 | 8/25/2026 |
| 1.1.1 | 94 | 8/24/2026 |
| 1.0.12 | 203 | 5/14/2026 |
| 1.0.11 | 1,631 | 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 |