Text[250] for a field that will never hold more than 20 characters is not harmless. Every table has a record size budget as well as a column budget, and every mismatch between a source field and a target field is a future "the length of the string is X, but it must be less than or equal to Y" at posting time. How to choose types and lengths, how record size actually works, and the patterns that stop overflow errors before they reach a user.
Two habits account for most of the data-model trouble we find in per-tenant extensions. The first is Text[250] on every text field, because it was the old default and nobody thought about it. The second is copying a value from one field to another without checking the two fields are the same size. The first quietly eats a budget that the table cannot get back. The second produces one of the most common runtime errors in Business Central, usually at posting time, usually in front of a user. Both are avoidable at design time, and this is how.
| Type | Use it for | Not for |
|---|---|---|
Code[n] | Identifiers and keys: numbers, codes, references. Stored upper-case, trimmed, sorted as a key. Primary keys are almost always Code. | Free text (it will be upper-cased), anything with meaningful case or leading spaces |
Text[n] | Descriptions, names, addresses, notes up to a sensible limit. Case preserved. | Identifiers you will filter or join on (use Code); long documents (use Blob or Media) |
Integer / BigInteger | Counts, sequence numbers (Entry No. is Integer; use BigInteger only when you will genuinely exceed 2.1 billion) | Money or quantities (no decimals) |
Decimal | Amounts, quantities, prices, percentages. Set DecimalPlaces and AutoFormatType so it displays as the base application does. | Identifiers |
Date, Time, DateTime | Date for business dates (posting, due, shipment); DateTime for timestamps (when something happened, in UTC); Time rarely on its own. Duration for elapsed time. | Storing a date as text, ever |
Boolean | Yes/no states. Name as a state ("Requires Pallet"). | Three-state things (use an Enum with a blank value) |
Enum | Any fixed list of named values. Extensible by other apps. | Option, which is the legacy type and should not be used in new code |
Guid | Cross-system identifiers, correlation IDs | Human-facing numbers |
Blob, Media, MediaSet | Long text (Blob with a Text getter/setter), files, images. Stored out of the row; only loaded when asked for. | Anything you need to filter or display in a list |
RecordId, TableFilter | References to arbitrary records; stored filters (setup tables) | Ordinary foreign keys (use Code with a TableRelation) |
Two habits that follow: set TableRelation on every Code field that references another table, so lookups and validation come free; and never store a number, date or amount as text because "it is just for display". Someone will filter on it.
The question is "what is the longest value this field will ever legitimately hold?", not "what is the biggest number I can type". A postcode is 10. A company registration number is 20. A customer's name in the base application is Text[100]; an address line is Text[100]; a description is Text[100]; a document number is Code[20]; a dimension value is Code[20]. If your field holds the same kind of thing as a base field, use the base field's length, and name it the same way, so that values can move between them without truncation.
The reason this matters is that the length is not free. The table has two budgets:
Text[250] is 500 bytes of potential row, a Text[2048] is 4 KB. Variable-length columns are only as large as their contents, so empty fields cost little; but the platform and SQL both plan around the declared maximum, long declared rows can be pushed to overflow pages (which work, slowly), and Business Central now warns when a table's declared size approaches the limit. On the hot tables, a dozen careless Text[250] fields from three extensions are the difference between a row that fits on a page and one that does not.So: Text[20] if the longest value is 20. You can widen a field later without losing data (widening is an additive schema change); you cannot narrow one that has data in it without the obsolete-and-migrate dance. Start small and widen when the business proves it needs to.
"The length of the string is 37, but it must be less than or equal to 30 characters. Value: ..." is the platform refusing to put a longer value into a shorter field. It is a runtime error, it rolls back the transaction, and it happens exactly when a value crosses from one field into another: in a posting routine copying header to ledger, in TransferFields, in an integration mapping an API payload, in a report assembling a column, in a validate trigger building a description. The numeric version ("Overflow under type conversion of Integer to ...") is the same idea for numbers that do not fit the target type.
The root cause is always the same: the source field is longer than the target field, and nothing checked. The fix is never to widen the target to 250 and move on. It is to make the sizes agree, or to handle the difference explicitly.
Code[30] on Item, it is Code[30] on Sales Line, Sales Shipment Line, Item Ledger Entry and anywhere else it flows, with the same name. Then the value always fits and TransferFields can copy it. This is what the base application does with its own fields and it is why Description is Text[100] on every table it appears on.TargetRec."Short Description" := CopyStr(SourceRec.Description, 1, MaxStrLen(TargetRec."Short Description"));
MaxStrLen reads the target's declared length, so the line stays correct if the field is widened later. Never hard-code the number.TransferFields copies by field ID and type; it does not care about length. It matches fields with the same ID and compatible type between two tables and copies them; if a target is shorter, you get the overflow error at the TransferFields line. So when you design a pair of tables that will be copied between (a document and its posted version, a staging table and its target), give the shared fields the same IDs, types and lengths. If you cannot, copy those fields explicitly after TransferFields(SourceRec, true, true) with CopyStr.CopyStr to the target's MaxStrLen, and decide (and log) whether truncation is acceptable or an error for that field. A customer name silently cut at 100 is probably fine; a bank account number cut at 30 is not.BigInteger. Divisions produce Decimals; assigning one to an Integer is a conversion that can overflow or lose precision. Round explicitly.Text variable, then CopyStr into the field.Library codeunits have LibraryUtility.GenerateRandomText(MaxStrLen(...)) for exactly this. It finds the mismatch in the pipeline instead of at month-end.Three quick checks that find most of it. List every text and code field with its length; anything at 250 or 2048 that holds a short value is a candidate to narrow at the next schema change. Grep for := between two different record types' text fields and for TransferFields, and compare lengths on each side. And search the telemetry for the overflow error text (it is logged like any other error, see the telemetry post); the ones users are hitting are already there.
Type for what the data is, length for the longest legitimate value, the same field the same size everywhere it flows, and CopyStr with MaxStrLen at every boundary where that is not true. It is five minutes of thought per field at design time and it removes a whole category of production errors. It is also one of the first things the AL code check looks at, because it is one of the easiest things to get right.