Bwfc Data Migration logo

Bwfc Data Migration

Organization
FritzAndFriends
bwfc-data-migration

**WORKFLOW SKILL** — Migrate Web Forms data access and architecture to Blazor Server. Covers EF6→EF Core with IDbContextFactory, Session→SessionShim, Global.asax→Program.cs, Web.config→appsettings.json, DataSource controls→service injection. WHEN: "migrate EF6", "session state to services", "Global.asax to Program.cs", "data access migration", "SelectMethod to delegate". INVOKES: dotnet CLI for EF migrations. FOR SINGLE OPERATIONS: use bwfc-migration for markup, bwfc-identity-migration for auth.

Overview

PublisherFritzAndFriends
RepositoryBlazorWebFormsComponents
Skill namebwfc-data-migration
Stars
449
Forks
77
Bundled files
2
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 2 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by FritzAndFriends on GitHub. Read the source before you install it.

Installation

Install the Bwfc Data Migration AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/FritzAndFriends/BlazorWebFormsComponents.git /tmp/BlazorWebFormsComponents
mkdir -p .claude/skills
cp -r /tmp/BlazorWebFormsComponents/migration-toolkit/skills/bwfc-data-migration .claude/skills/bwfc-data-migration
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Bwfc Data Migration in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Bwfc Data Migration on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Bwfc Data Migration is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Web Forms Data Access & Architecture Migration

Overview

Covers data access and architecture migration — the Layer 2/3 decisions requiring project-specific judgment.

Related: /bwfc-migration (markup), /bwfc-identity-migration (auth)

When to Use This Skill

  • Convert SelectMethod string → SelectHandler delegate, replace DataSource controls with service injection
  • Migrate Entity Framework 6 → EF Core
  • Convert Session/ViewState/Application state to Blazor patterns
  • Migrate Global.asaxProgram.cs, Web.configappsettings.json
  • Replace HTTP Handlers/Modules with middleware

Critical Rules

Session State Migration

Use SessionShim (Default — Works Everywhere)

Pages inheriting WebFormsPageBase get a Session property backed by SessionShim. SessionShim works in BOTH SSR and interactive modes:

  • SSR: Reads/writes to ASP.NET Core ISession (cookie-backed)
  • Interactive: Uses in-memory ConcurrentDictionary scoped per circuit

Original Web Forms:

csharp
Session["CartId"] = Guid.NewGuid().ToString();
var cartId = Session["CartId"].ToString();
Session["payment_amt"] = 99.99m;

Migrated Blazor (IDENTICAL):

csharp
Session["CartId"] = Guid.NewGuid().ToString();
var cartId = Session["CartId"].ToString();
Session["payment_amt"] = 99.99m;

No IHttpContextAccessor. No Minimal API. No cookies. Just Session["key"].

For non-page components, inject SessionShim directly:

razor
@inject SessionShim Session

@code {
    protected override void OnInitialized()
    {
        var userId = Session["UserId"]?.ToString() ?? "guest";
    }
}

When to Upgrade Beyond SessionShim

Only consider alternatives when you need cross-tab or cross-server persistence:

ProtectedBrowserStorage — For data that must survive page refreshes:

csharp
@inject ProtectedSessionStorage SessionStorage

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        var result = await SessionStorage.GetAsync<ShoppingCart>("cart");
        cart = result.Success ? result.Value! : new ShoppingCart();
    }
}

Database-backed — For shopping carts that persist across sessions:

csharp
public class CartService(IDbContextFactory<AppDbContext> factory)
{
    public async Task<Cart> GetCartAsync(string userId)
    {
        using var db = factory.CreateDbContext();
        return await db.Carts
            .Include(c => c.Items)
            .FirstOrDefaultAsync(c => c.UserId == userId) ?? new Cart();
    }
}

Scoped services — When the pattern doesn't fit key-value storage:

csharp
public class WizardStateService
{
    public int CurrentStep { get; set; }
    public FormData Data { get; set; } = new();
    public bool IsComplete => CurrentStep == 5 && Data.IsValid();
}

// Program.cs
builder.Services.AddScoped<WizardStateService>();

Progression model:

  1. Start with SessionShim (zero migration cost)
  2. Move to scoped services if you need typed, structured state
  3. Move to database if you need persistence across circuits/sessions

1. Entity Framework 6 → EF Core

Web Forms: EF6 with DbContext instantiated directly in code-behind or via SelectMethod string binding. Blazor: EF Core 10.0.3 (latest .NET 10) with IDbContextFactory registered in DI.

Step 1: Detect the provider. The L1 script's Find-DatabaseProvider function reads Web.config <connectionStrings> and scaffolds the correct EF Core package. Check the L1 output's [DatabaseProvider] review item for the detected provider and connection string. Use these values in your Program.cs configuration — do not guess or substitute.

CRITICAL: Preserve the original database provider. Examine the Web Forms project's Web.config connection strings and EF configuration to identify the database provider (SQL Server, PostgreSQL, MySQL, SQLite, Oracle, etc.). The migrated Blazor application MUST use the same database provider — do NOT switch providers unless explicitly requested by the user.

⚠️ NEVER default to SQLite. The most common Web Forms database is SQL Server (often LocalDB for dev). If you see System.Data.SqlClient or (LocalDB) in connection strings, use Microsoft.EntityFrameworkCore.SqlServer — NOT Microsoft.EntityFrameworkCore.Sqlite. SQLite is ONLY appropriate if the original application specifically used System.Data.SQLite.

Database Provider Detection & Migration

Step 1: Identify the original provider from the Web Forms project:

Web.config IndicatorOriginal ProviderEF Core Package
providerName="System.Data.SqlClient"SQL ServerMicrosoft.EntityFrameworkCore.SqlServer
providerName="System.Data.SQLite"SQLiteMicrosoft.EntityFrameworkCore.Sqlite
providerName="Npgsql" or Server=...;Port=5432PostgreSQLNpgsql.EntityFrameworkCore.PostgreSQL
providerName="MySql.Data.MySqlClient"MySQLPomelo.EntityFrameworkCore.MySql or MySql.EntityFrameworkCore
providerName="Oracle.ManagedDataAccess.Client"OracleOracle.EntityFrameworkCore

Step 2: Install the matching EF Core provider package in the Blazor project:

bash
# Example for SQL Server
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 10.0.3

# Example for PostgreSQL
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL --version 10.0.3

# Example for MySQL (Pomelo)
dotnet add package Pomelo.EntityFrameworkCore.MySql --version 10.0.3

Step 3: Configure the matching provider in Program.cs:

csharp
// SQL Server — matches System.Data.SqlClient
options.UseSqlServer(connectionString)

// PostgreSQL — matches Npgsql
options.UseNpgsql(connectionString)

// MySQL — matches MySql.Data.MySqlClient
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))

// SQLite — matches System.Data.SQLite
options.UseSqlite(connectionString)

Install matching EF Core packages for .NET 10: Microsoft.EntityFrameworkCore, the provider-specific package (see table above), .Tools, and .Design.

csharp
// Web Forms — direct DbContext in code-behind
public IQueryable<Product> GetProducts()
{
    var db = new ProductContext();
    return db.Products;
}
csharp
// Blazor — Program.cs (use the provider that matches the original Web Forms database)
builder.Services.AddDbContextFactory<ProductContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
    // ↑ Replace with UseNpgsql(), UseMySql(), UseSqlite(), etc. to match original provider
csharp
// Blazor — Service layer
public class ProductService(IDbContextFactory<ProductContext> factory)
{
    public async Task<List<Product>> GetProductsAsync()
    {
        using var db = factory.CreateDbContext();
        return await db.Products.ToListAsync();
    }

    public async Task<Product?> GetProductAsync(int id)
    {
        using var db = factory.CreateDbContext();
        return await db.Products.FindAsync(id);
    }
}

Critical: Use IDbContextFactory, NOT AddDbContext, for Blazor Server. Blazor circuits are long-lived — a single DbContext accumulates stale data and tracking issues.

EF6 → EF Core API Changes

EF6EF CoreNotes
using System.Data.Entity;using Microsoft.EntityFrameworkCore;Namespace change
DbModelBuilder in OnModelCreatingModelBuilderSame concepts, different API
HasRequired() / HasOptional()Navigation properties + IsRequired()Simpler relationship config
Database.SetInitializer(...)Database.EnsureCreated() or MigrationsDifferent init strategy
db.Products.Include("Category")db.Products.Include(p => p.Category)Prefer lambda includes
WillCascadeOnDelete(false).OnDelete(DeleteBehavior.Restrict)Cascade config
.HasDatabaseGeneratedOption(...).ValueGeneratedOnAdd()Key generation

Connection String Migration

xml
<!-- Web Forms — Web.config -->
<connectionStrings>
  <add name="DefaultConnection"
       connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=MyApp;Integrated Security=True"
       providerName="System.Data.SqlClient" />
</connectionStrings>
json
// Blazor — appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=MyApp;Integrated Security=True"
  }
}

2. DataSource Controls → Service Injection

Web Forms DataSource controls have no BWFC equivalent. Replace with injected services.

xml
<!-- Web Forms — declarative data binding -->
<asp:SqlDataSource ID="ProductsDS" runat="server"
    ConnectionString="<%$ ConnectionStrings:DefaultConnection %>"
    SelectCommand="SELECT * FROM Products" />
<asp:GridView DataSourceID="ProductsDS" runat="server" />
razor
@* Blazor — service injection *@
@inject IProductService ProductService

<GridView Items="products" ItemType="Product" AutoGenerateColumns="true" />

@code {
    private List<Product> products = new();

    protected override async Task OnInitializedAsync()
    {
        products = await ProductService.GetProductsAsync();
    }
}

Service Registration Pattern

csharp
// Program.cs — use the provider that matches the original Web Forms database
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
builder.Services.AddBlazorWebFormsComponents(); // ⚠️ REQUIRED — registers BWFC services
builder.Services.AddDbContextFactory<ProductContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
    // ↑ Match the original provider: UseNpgsql(), UseMySql(), UseSqlite(), etc.

builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<ICategoryService, CategoryService>();
builder.Services.AddScoped<IOrderService, OrderService>();

// ... after builder.Build() ...
app.UseBlazorWebFormsComponents(); // ⚠️ REQUIRED — .aspx URL rewriting middleware. BEFORE MapRazorComponents.
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

SelectMethod String → SelectHandler Delegate Conversion

BWFC's DataBoundComponent<ItemType> has a native SelectMethod parameter of type SelectHandler<ItemType> — a delegate with signature (int maxRows, int startRowIndex, string sortByExpression, out int totalRowCount) → IQueryable<ItemType>. When set, OnAfterRenderAsync automatically calls it to populate Items. This is the native BWFC data-binding pattern that mirrors how Web Forms did it.

Option A — Preserve SelectMethod as delegate (recommended):

Web Forms SelectMethodBWFC SelectMethod Delegate
SelectMethod="GetProducts"SelectMethod="@productService.GetProducts" (if signature matches SelectHandler<T>)
SelectMethod="GetProduct"SelectMethod="@productService.GetProduct" (or use DataItem for single-record controls)

Option B — Items binding (ONLY when original used DataSource, NOT SelectMethod):

⚠️ Use Option B ONLY when the original Web Forms markup used DataSource/DataBind(), NOT when it used SelectMethod. If the original had SelectMethod="GetProducts", you MUST use Option A above.

Web Forms SelectMethodBlazor Service Call
SelectMethod="GetProducts"products = await ProductService.GetProductsAsync(); then Items="@products"
SelectMethod="GetProduct"product = await ProductService.GetProductAsync(id); then DataItem="@product"

CRUD methods (no BWFC parameter equivalent — wire to service calls in event handlers):

Web Forms MethodBlazor Service Call
InsertMethod="InsertProduct"await ProductService.InsertAsync(product);
UpdateMethod="UpdateProduct"await ProductService.UpdateAsync(product);
DeleteMethod="DeleteProduct"await ProductService.DeleteAsync(id);

3. Session, ViewState, and Application State Migration

Web Forms: Session["key"], ViewState["key"], Application["key"] dictionaries. Blazor: SessionShim (auto-registered by AddBlazorWebFormsComponents()), component fields, and singleton services.

Session["key"] → SessionShim (Zero-Change Migration)

No code changes needed. WebFormsPageBase.Session delegates to SessionShim automatically:

csharp
// Web Forms — works IDENTICALLY in Blazor via SessionShim
Session["ShoppingCart"] = cart;
var cart = (ShoppingCart)Session["ShoppingCart"];

// SessionShim also supports typed access:
var cart = Session.Get<ShoppingCart>("ShoppingCart");
Session.Set("ShoppingCart", cart);

How SessionShim works:

  • SSR mode: Backed by ASP.NET Core ISession (cookie-persisted)
  • Interactive mode: In-memory ConcurrentDictionary scoped per circuit
  • Seamless: Automatically switches based on render mode

For non-page components:

razor
@inject SessionShim Session

@code {
    private string GetUserId() => Session["UserId"]?.ToString() ?? "guest";
}

ViewState["key"] → Component Fields

ViewState is component-instance state. Use normal C# fields/properties:

csharp
// Web Forms
ViewState["CurrentPage"] = pageIndex;
var page = (int)ViewState["CurrentPage"];

// Blazor
private int currentPage;

Application["key"] → Singleton Services

Application-wide state becomes singleton services:

csharp
// AppStateService.cs
public class AppStateService
{
    private readonly ConcurrentDictionary<string, object> _state = new();
    public void Set(string key, object value) => _state[key] = value;
    public T? Get<T>(string key) => _state.TryGetValue(key, out var val) ? (T)val : default;
}

// Program.cs
builder.Services.AddSingleton<AppStateService>();

State Storage Options

Web FormsBlazor EquivalentScope
Session["key"]Scoped servicePer-circuit (lost on disconnect)
Session["key"] (persistent)ProtectedSessionStorageBrowser session tab
Application["key"]Singleton serviceApp-wide
Cache["key"]IMemoryCache or IDistributedCacheConfigurable
ViewState["key"]Component fields/propertiesPer-component
TempData["key"]ProtectedSessionStorageOne read
CookiesProtectedLocalStorage or HTTP endpointsBrowser

ProtectedSessionStorage Example

razor
@inject ProtectedSessionStorage SessionStorage

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var result = await SessionStorage.GetAsync<ShoppingCart>("cart");
            cart = result.Success ? result.Value! : new ShoppingCart();
        }
    }

    private async Task SaveCart()
    {
        await SessionStorage.SetAsync("cart", cart);
    }
}

Note: ProtectedSessionStorage only works after the first render (it requires JS interop). Always check in OnAfterRenderAsync, not OnInitializedAsync.


Reference Documents

Architecture migration patterns (Global.asax, Web.config, routes, handlers, enhanced navigation) are in the child document:

  • ARCHITECTURE-TRANSFORMS.md Global.asax → Program.cs, Web.config → appsettings.json, route table → @page directives, HTTP handlers/modules → middleware, third-party integrations → HttpClient, files to create during migration, and Blazor enhanced navigation workarounds.

Common Data Migration Gotchas

DbContext Lifetime — CRITICAL

Blazor Server circuits are long-lived. Always use IDbContextFactory and create short-lived DbContext instances per operation.

WRONG — IQueryable returned from disposed context:

csharp
private IQueryable<Product> GetProducts(int categoryId)
{
    using var db = DbFactory.CreateDbContext();
    return db.Products.Where(p => p.CategoryId == categoryId); // Context disposed before query executes!
}

RIGHT — materialize inside using block:

csharp
private IQueryable<Product> GetProducts(int categoryId)
{
    using var db = DbFactory.CreateDbContext();
    var results = db.Products
        .Where(p => p.CategoryId == categoryId)
        .ToList(); // Execute query NOW while context is alive
    return results.AsQueryable(); // Return materialized data as IQueryable
}

For SelectHandler delegates, the delegate is invoked by BWFC infrastructure AFTER your method returns. You MUST materialize:

csharp
// BWFC SelectHandler delegate — MUST materialize
private IQueryable<Product> SelectProducts(int maxRows, int startRowIndex, 
    string sortByExpression, out int totalRowCount)
{
    using var db = DbFactory.CreateDbContext();
    totalRowCount = db.Products.Count();
    
    var results = db.Products
        .OrderBy(p => p.Name)
        .Skip(startRowIndex)
        .Take(maxRows)
        .ToList(); // CRITICAL — materialize NOW
        
    return results.AsQueryable();
}

No Page-Level Transaction Scope

Web Forms SelectMethod runs inside a page lifecycle. Blazor doesn't have this. Use explicit transaction scopes in services if needed:

csharp
using var db = factory.CreateDbContext();
using var transaction = await db.Database.BeginTransactionAsync();
// ... operations
await transaction.CommitAsync();

Async All the Way

Web Forms SelectMethod returns IQueryable synchronously. Blazor services should be async:

csharp
// WRONG: return db.Products.ToList();
// RIGHT: return await db.Products.ToListAsync();

ConfigurationManager Shim Available

ConfigurationManager.AppSettings["key"] works via BWFC's ConfigurationManager shim. Call app.UseConfigurationManagerShim() in Program.cs to bind it to IConfiguration. For new code, prefer injecting IConfiguration or using the Options pattern.

Static Helpers with HttpContext

Web Forms often has static helper classes that access HttpContext.Current. These must be refactored to accept dependencies via constructor injection.

ThreadAbortException Dead Code Warning

Web Forms throws ThreadAbortException when Response.Redirect(url, true) is called with endResponse=true. Blazor does not throw this exception — ResponseShim.Redirect() silently ignores the endResponse parameter. Any catch (ThreadAbortException) blocks become dead code after migration. Review and remove them. Code that runs AFTER Response.Redirect(url, true) will execute in Blazor (unlike Web Forms where execution stopped).


❌ Common Anti-Patterns to Avoid

DO NOT Create Minimal API Endpoints for Page Actions

Minimal APIs are for real HTTP endpoints (REST APIs, webhooks), NOT for migrating Web Forms page actions.

WRONG:

csharp
// Program.cs — creating API endpoint for a page action
app.MapPost("/api/cart/add", async (CartItem item, CartService cart) =>
{
    cart.Add(item);
    return Results.Ok();
});

// Cart.razor — calling the API
await Http.PostAsJsonAsync("/api/cart/add", item);

RIGHT:

csharp
// Cart.razor — just call the service directly
@inject CartService CartService

<button @onclick="() => CartService.Add(item)">Add to Cart</button>

When Minimal APIs ARE appropriate:

  • External REST API consumed by mobile apps, SPAs, or third parties
  • Webhooks from payment processors, GitHub, etc.
  • Form POST endpoints for authentication (login/logout/register) — these need HTTP context for cookies

When they are NOT appropriate:

  • Replacing button click handlers in migrated Web Forms pages
  • Working around Session["key"] access — use SessionShim instead
  • "Because HttpContext is null" — you don't need HttpContext for most operations

DO NOT Use IHttpContextAccessor to Access Session

You already have Session via WebFormsPageBase or @inject SessionShim.

WRONG:

csharp
@inject IHttpContextAccessor HttpContextAccessor

var session = HttpContextAccessor.HttpContext?.Session;
var cartId = session?.GetString("CartId");

RIGHT:

csharp
@inherits WebFormsPageBase

var cartId = Session["CartId"]?.ToString();

DO NOT Replace Session with Cookies

If the original Web Forms code used Session["key"], use SessionShim. Don't invent cookie-based workarounds.

WRONG:

csharp
// Creating cookie-based cart ID because "Session doesn't work in Blazor"
Response.Cookies.Append("CartId", Guid.NewGuid().ToString());
var cartId = Request.Cookies["CartId"];

RIGHT:

csharp
// SessionShim handles the storage — just use Session
Session["CartId"] = Guid.NewGuid().ToString();
var cartId = Session["CartId"]?.ToString();

DO NOT Use HttpContext.Current.Session

There is no HttpContext.Current in ASP.NET Core. Use the Session property.

WRONG:

csharp
HttpContext.Current.Session["UserId"] = userId;

RIGHT:

csharp
Session["UserId"] = userId; // From WebFormsPageBase or injected SessionShim

Error SignatureRecipe File
CS1503: SelectMethod ... 'string' to 'SelectHandler'../bwfc-migration/recipes/selectmethod-string-binding.md
CS7036: no argument ... 'options' of 'XxxContext'../bwfc-migration/recipes/new-dbcontext-to-di.md
CS0246: 'IDatabaseInitializer'../bwfc-migration/recipes/database-seed-initializer.md

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Bwfc Data Migration AI skill do?

**WORKFLOW SKILL** — Migrate Web Forms data access and architecture to Blazor Server. Covers EF6→EF Core with IDbContextFactory, Session→SessionShim, Global.asax→Program.cs, Web.config→appsettings.json, DataSource controls→service injection. WHEN: "migrate EF6", "session state to services", "Global.asax to Program.cs", "data access migration", "SelectMethod to delegate". INVOKES: dotnet CLI for EF migrations. FOR SINGLE OPERATIONS: use bwfc-migration for markup, bwfc-identity-migration for auth.

Why use Bwfc Data Migration on TypingMind?

Because you install it once and use it with any model. Bwfc Data Migration is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Bwfc Data Migration in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/FritzAndFriends/BlazorWebFormsComponents/tree/dev/migration-toolkit/skills/bwfc-data-migration. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Bwfc Data Migration?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Bwfc Data Migration?

As many as you like. As long as a model supports skills, you can use Bwfc Data Migration with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Bwfc Data Migration AI skill free?

Yes. It is published on GitHub by FritzAndFriends under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇