Extending Mockifyr
Mockifyr is a library as well as a server. When a stub cannot express what you need, you register your own code against one of the extension seams.
Registration
Everything is wired through AddMockifyr(cfg => …), whose argument is a MockifyrExtensions builder.
builder.Services.AddMockifyr(cfg => cfg .AddMatcher("tenant-quota", new TenantQuotaMatcher()) .AddTemplateHelper("shout", args => args[0]?.ToString()?.ToUpperInvariant() ?? "") .AddServeEventListener(new AuditListener()) .AddResponseTransformer(new SignatureTransformer()) .AddAdminApiExtension(new QuotaAdminApi()));| Builder method | Registers |
|---|---|
AddMatcher(name, matcher) | A named custom request matcher |
AddServeEventListener(listener) | A listener fired after a request is served |
AddTemplateHelper(name, render) | A Handlebars helper usable as {{name …}} |
AddAdminApiExtension(extension) | Custom endpoints under /__admin/ext/… |
AddResponseTransformer(transformer) | A transformer applied to the rendered response |
Why the seams look like this
The core engine has zero external dependencies, does no I/O, and never references a transport. Delay,
fault and proxy are not things the engine performs — they are directives the engine emits and the
facade carries out, and every outbound call leaves through IServeEventListener.
That is the reason the seams split the way they do: a matcher or a transformer is a pure function of its input, while anything that talks to the network is a listener. If your extension needs to make an HTTP call, it belongs on the listener seam, not inside a matcher.
Custom matchers
A matcher registered with AddMatcher is reached from stub JSON by name:
{ "request": { "method": "GET", "urlPath": "/reports", "customMatcher": { "name": "tenant-quota", "parameters": { "max": 100 } } }, "response": { "status": 429 }}The built-in graphql-body-matcher is registered the same way, and is a working example of
the shape.
Admin API extensions
IAdminApiExtension exposes a RoutePrefix. Requests to /__admin/ext/{prefix}/… are routed to the
extension whose prefix equals the first path segment under /__admin/ext/. An unknown prefix is a
404.
curl http://localhost:8080/__admin/ext/quota/statusThese routes are host-level, not tenant-scoped — see multi-tenancy.
The public seams
All declared in Mockifyr.Core.
| Interface | Purpose |
|---|---|
IResponseTransformer | Transform a rendered response |
IResponseDefinitionTransformer | Transform a response definition before rendering |
ITemplateHelper | A named template helper |
ITemplateHelperProvider | Supplies a set of helpers |
ITemplateModelProvider | Adds data to the template model |
IMatcherRegistry | Register and resolve named matchers |
IAdminApiExtension | Custom /__admin/ext/… endpoints |
IMappingsLoader | Load stub mappings from a custom source |
IStubPersistence | Persist stubs to a custom backing store |
IEnvironmentPersistence | Persist environment keys |
Not wired yet
Present in the public surface but not currently invoked. Implementing them has no effect, so do not build against them:
IResponseDefinitionTransformerITemplateModelProviderIRequestFilter- Template-helper hash arguments (
{{helper key=value}}) and helper providers (ITemplateHelperProvider) — a helper registered throughAddTemplateHelperreceives positional arguments only.
Registered through DI, not the builder
There is no builder method for IMappingsLoader or IStubPersistence. Register those as ordinary
dependency-injection services; the host resolves them from the container.
In the dashboard
The dashboard has an Extensions page. It is a static, searchable reference of the built-in capabilities — matchers, helpers, transformers — not an editor. Extensions are registered in code at startup; nothing on that page changes what the host has loaded.
Related
- Request matching — what
customMatchersits alongside. - Template helper reference — the built-in helper set.
- Webhooks — the shipped
IServeEventListener. - Persistence — the built-in stub persistence providers.