mattermost-community/focalboard · error

Block2Card fail

Error message

Block2Card fail: %w

What it means

GetCardsForBoard fetches all card blocks of a board and converts each Block back into a Card with model.Block2Card. If any single block fails conversion (e.g. it is not a card-type block or its fields do not unmarshal into Card), this error wraps the cause and the whole call fails, returning no cards.

Solutions

  1. Unwrap the error to identify which block/field failed conversion; fix or remove the malformed block row.
  2. Skip-and-log malformed blocks instead of failing the whole listing (requires a source change to continue the loop on error).
  3. Verify no non-card block types are being returned by the store query filtering.
  4. Run a data-integrity check/migration if boards were imported from an older version.

Example fix

// before
if card, err := model.Block2Card(b); err != nil {
    return nil, fmt.Errorf("Block2Card fail: %w", err)
} else {
    cards = append(cards, card)
}
// after
card, err := model.Block2Card(b)
if err != nil {
    a.logger.Warn("skipping invalid card block", mlog.String("blockID", b.ID), mlog.Err(err))
    continue
}
cards = append(cards, card)
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure blocks are card-typed before conversion
for _, blk := range blocks {
    if blk.Type != model.TypeCard {
        return fmt.Errorf("non-card block %s (%s) in card query", blk.ID, blk.Type)
    }
}

Type guard

func isCardBlock(b *model.Block) bool {
    return b != nil && b.Type == model.TypeCard
}

Try / catch

cards, err := app.GetCardsForBoard(boardID, viewID)
if err != nil {
    if strings.Contains(err.Error(), "Block2Card fail") {
        return fmt.Errorf("board %s contains a malformed card block: %w", boardID, errors.Unwrap(err))
    }
    return err
}

Prevention

When it happens

Trigger: App.GetCardsForBoard on a board whose block list contains a block that cannot be converted: wrong block type slipped into card-type filtering, corrupted/legacy block JSON missing required card fields, or schema drift after version upgrades.

Common situations: Boards imported from older Focalboard versions with legacy block payloads; manual DB edits producing invalid card blocks; a bug storing non-card blocks under card queries; corrupted fields after failed migrations.

Related errors


AI-assisted analysis of mattermost-community/focalboard@a84bbb65e3 (2026-08-30). Data as JSON: /api/errors/b46d48e7d16c223b. Report an issue: GitHub.

Appendix: source

Thrown at server/app/cards.go:57

func (a *App) GetCardsForBoard(boardID string, page int, perPage int) ([]*model.Card, error) {
	opts := model.QueryBlocksOptions{
		BoardID:   boardID,
		BlockType: model.TypeCard,
		Page:      page,
		PerPage:   perPage,
	}

	blocks, err := a.store.GetBlocks(opts)
	if err != nil {
		return nil, err
	}

	cards := make([]*model.Card, 0, len(blocks))
	for _, blk := range blocks {
		b := blk
		if card, err := model.Block2Card(b); err != nil {
			return nil, fmt.Errorf("Block2Card fail: %w", err)
		} else {
			cards = append(cards, card)
		}
	}
	return cards, nil
}

func (a *App) PatchCard(cardPatch *model.CardPatch, cardID string, userID string, disableNotify bool) (*model.Card, error) {
	blockPatch, err := model.CardPatch2BlockPatch(cardPatch)
	if err != nil {
		return nil, err
	}

	newBlock, err := a.PatchBlockAndNotify(cardID, blockPatch, userID, disableNotify)
	if err != nil {
		return nil, fmt.Errorf("cannot patch card %s: %w", cardID, err)
	}

View on GitHub (pinned to a84bbb65e3)