{"record":{"id":"d69a34796abddc2a","repo":"netbirdio/netbird","slug":"invalid-record-name-format","errorCode":null,"errorMessage":"invalid record name format","messagePattern":"invalid record name format","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/zones/records/record.go","lineNumber":67,"sourceCode":"\t\tContent: r.Content,\n\t\tTtl:     r.TTL,\n\t}\n}\n\nfunc (r *Record) FromAPIRequest(req *api.DNSRecordRequest) {\n\tr.Name = req.Name\n\tr.Type = RecordType(req.Type)\n\tr.Content = req.Content\n\tr.TTL = req.Ttl\n}\n\nfunc (r *Record) Validate() error {\n\tif r.Name == \"\" {\n\t\treturn errors.New(\"record name is required\")\n\t}\n\n\tif !domain.IsValidDomain(r.Name) {\n\t\treturn errors.New(\"invalid record name format\")\n\t}\n\n\tif r.Type == \"\" {\n\t\treturn errors.New(\"record type is required\")\n\t}\n\n\tswitch r.Type {\n\tcase RecordTypeA:\n\t\tif err := validateIPv4(r.Content); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase RecordTypeAAAA:\n\t\tif err := validateIPv6(r.Content); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase RecordTypeCNAME:\n\t\tif !domain.IsValidDomainNoWildcard(r.Content) {\n\t\t\treturn errors.New(\"invalid CNAME target format\")","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/zones/records/record.go#L49-L85","documentation":"Validate() rejects a non-empty Name that fails shared/management/domain.IsValidDomain. That helper matches an ASCII-only regex: labels of letters/digits/hyphen (underscore only as the first character), at most 63 chars per label, an optional leading \"*.\" wildcard, and no trailing dot. It performs no punycode conversion, so unicode domains fail as-is.","triggerScenarios":"Name values like \"my host\" (space), \"app.\" (trailing dot), \"café.example.com\" (unicode), \"a..b\" (empty label), \"-bad.example.com\" (leading hyphen), or a single label longer than 63 characters.","commonSituations":"Pasting a unicode domain straight from a browser bar instead of its punycode form; FQDN normalization elsewhere in the pipeline leaving a trailing dot; wildcard names are fine here (IsValidDomain allows \"*.\"), so those pass this specific check.","solutions":["Strip any trailing dot from the name before submitting (\"app.\" -> \"app\").","Convert unicode domains to punycode before sending (e.g. domain.ValidateDomains does this: \"café.example.com\" -> \"xn--caf-dma.example.com\").","Keep each label 1-63 chars, starting and ending with a letter or digit."],"exampleFix":"// before\nr.Name = \"café.example.com\"\n// after\nr.Name = \"xn--caf-dma.example.com\"","handlingStrategy":"validation","validationCode":"if !domain.IsValidDomain(name) {\n    punycoded, err := domain.ValidateDomains([]string{name}) // converts unicode, then re-check\n    if err != nil {\n        return fmt.Errorf(\"record name %q is not a valid domain: %v\", name, err)\n    }\n    name = string(punycoded[0])\n}","typeGuard":"func isValidRecordName(name string) bool {\n    return domain.IsValidDomain(strings.TrimSuffix(name, \".\"))\n}","tryCatchPattern":"if err := rec.Validate(); err != nil {\n    if err.Error() == \"invalid record name format\" {\n        // surface a field-specific message next to the name input\n    }\n    return respondBadRequest(err)\n}","preventionTips":["Trim trailing dots from user-supplied names before submitting.","Convert unicode input to punycode at the UI layer so the API only ever sees ASCII.","Reuse shared/management/domain.IsValidDomain client-side for instant feedback instead of waiting for the round trip."],"tags":["dns","zones","validation","punycode"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}