Cross-platform file and directory path manipulation, segment splitting, extension extraction, and context conversion using `package:path` and `package:file`. Use when writing, inspecting, joining, splitting, or refactoring file paths, directory names, or extensions, or replacing raw string path operations (`.split('/')`, `'$dir/$file'`, `.endsWith('.ext')`, `.replaceAll('\\', '/')`). Don't use for HTTP network URI routing, database query strings, or non-path string processing.
Permissions
Files
Cross-platform file and directory path manipulation, segment splitting, extension extraction, and context conversion using `package:path` and `package:file`. Use when writing, inspecting, joining, splitting, or refactoring file paths, directory names, or extensions, or replacing raw string path operations (`.split('/')`, `'$dir/$file'`, `.endsWith('.ext')`, `.replaceAll('\\', '/')`). Don't use for HTTP network URI routing, database query strings, or non-path string processing.
Version history
\), whereas macOS and Linux use forward slashes (/)..contains('foo/'), .startsWith('foo/'), or .split('/') silently fail on Windows native paths.'$dir/$file' injects forward slashes on Windows and produces duplicate slashes (//) when $dir ends with a trailing slash.Rule: Always decompose paths into segments using p.split(path) before inspecting directory hierarchy or segment names, and always join path components using p.join(...).
p.join)p.join(dir, 'sub', 'file.json') so package:path inserts OS-native separators (\ on Windows, / on POSIX) between every component.p.join(home, '.local', 'share', 'app', 'bin', 'config.json')) causes dart format to wrap across 6–8 vertical lines and destroys substring greppability (grep / code_search for .local/share/app/bin).p.join(home, '.local/share/app/bin/config.json')). This prevents duplicate-slash bugs (//) at variable boundaries while preserving single-line readability and exact string searchability.p.normalize vs. p.canonicalize)p.normalize(path) resolves . and .. segments purely lexically without consulting the filesystem or standardizing case.p.canonicalize(path).<path>:<line>-<col> or <path>:<line> are not pure file paths. Passing them directly to p.normalize or Uri.parse causes bugs (on Windows, Uri.parse mistakes C: for a URI scheme and :line for a port).:line-col suffix via regular expression (RegExp(r'^(.*?):(\d+(?:-\d+)?)$')) before passing the file path to package:path.Uri objects, always use p.toUri(path) and p.fromUri(uri) rather than Uri.parse(path) or manual string concatenation.p.join(dir, file)'$dir/$file' or 'a/$b'/ on Windows and creates duplicate
slashes (//) when $dir ends with a trailing separator.p.split(path).contains('foo')path.contains('foo/')foo\bar) and produces
false positives on partial substring names (e.g. barfoo/).p.split(path).first == 'foo' or p.isWithin('foo', path)path.startsWith('foo/')./foo/.p.extension(path) == '.wasm'path.endsWith('.wasm')foo.wasm/)
or non-extension suffixes.p.withoutExtension(path) and p.extension(path, 2)path.lastIndexOf('.') and manual substring slicing.gitignore) and
compound extensions (.js.map, .tar.gz).p.posix.joinAll(p.split(path)) or p.url.joinAll(p.split(path))path.replaceAll(r'\', '/')p.toUri(path) and p.fromUri(uri)Uri.parse(path) and uri.pathC:) and leaks
percent-encoding (e.g. %20 for spaces).String canonicalDirName(Directory d) => p.basename(p.normalize(d.absolute.path));p.basename(p.normalize(dir.absolute.path)) inline
across files.Avoid calling .replaceAll('\\', '/') or .replaceAll(r'\', '/') to convert
OS-native paths into POSIX paths (for Git, YAML, archive manifests) or URL
segments.
Rule: Split the relative native path using p.split(...), inspect segments
with Dart 3 list pattern matching, and join using p.posix.joinAll(...) or
p.url.joinAll(...). Always call p.relative(filePath, from: root) first so
leading root segments ('/' on POSIX or r'C:\' on Windows) do not interfere
with relative prefix patterns:
import 'package:path/path.dart' as p;
String computeWebAssetKey(String filePath, String projectRoot) {
final relative = p.relative(filePath, from: projectRoot);
final segments = p.split(relative);
return switch (segments) {
['assets', ...] => p.posix.joinAll(segments),
_ => p.posix.joinAll(['assets', ...segments]),
};
}
.gitignore pattern rules, .gitattributes,
and git-tracked symlinks strictly use POSIX forward slashes (/), even on
Windows.\) into .gitignore or git commands
causes Git to treat \ as an escape character rather than a directory
separator, silently breaking pattern matching..gitignore entries, repository manifests, or symlink
targets programmatically from native file paths, convert the relative native
path using p.posix.joinAll(p.split(relativePath)) or p.posix.join(...).package:file vs. Global p.*)In codebases that use package:file (e.g., CLI applications or services tested
with MemoryFileSystem), avoid calling top-level p.* functions on File or
Directory paths.
p.* functions bind to the host operating system running the test.MemoryFileSystem(style: FileSystemStyle.windows) on a Linux or macOS runner, global p.split(file.path) will split on / instead of \, breaking the test.Rule: Always use the Context attached to the FileSystem (file.fileSystem.path):
import 'package:file/file.dart';
List<String> listSubdirectoryNames(Directory dir) {
final pathContext = dir.fileSystem.path;
return dir
.listSync()
.whereType<Directory>()
.map((d) => pathContext.basename(d.path))
.toList();
}
Avoid manual .lastIndexOf('.') and .substring() arithmetic when extracting file extensions or inserting content hashes. p.extension natively supports multi-level extensions via its optional level parameter.
p.extension('main.dart.wasm', 2) returns '.dart.wasm' because it blindly captures the last two dot-separated segments. When hashing or stripping extensions on files that may have multi-dot stems (e.g., main.dart.wasm vs. main.dart.js.map), check whether p.extension(filename, 2) matches a known compound extension (or .endsWith('.map')) before falling back to single-level p.extension(filename):import 'package:path/path.dart' as p;
String insertContentHash(String filename, String hash) {
final compoundExt = p.extension(filename, 2);
// Only use the 2-level extension for true compound suffixes (e.g., '.js.map')
final ext = compoundExt.endsWith('.map')
? compoundExt
: p.extension(filename);
final stem = filename.substring(0, filename.length - ext.length);
return '$stem.$hash$ext';
}
'$dir/$file') with p.join(dir, file)..contains('dir/') and .startsWith('dir/') with p.split(path) segment checks or p.isWithin(parent, child)..replaceAll(r'\', '/') with p.posix.joinAll(p.split(path)) (or p.url.joinAll)..endsWith('.ext') on file paths with p.extension(path) == '.ext'.p.withoutExtension(path) and p.extension(path, [level]).package:file accesses fileSystem.path instead of global p.*..gitignore entries, and symlink targets use p.posix forward slashes.In these kits
More from @flutter
Works with
Claude, Codex, Cursor & more