Skip to main content
Refractor is in early development. The real impact on generated binaries may differ from expected results. Always test thoroughly.

What You’ll Build

In this guide, you’ll:
  • Create a configuration file for Refractor
  • Obfuscate a Dart application
  • Inspect the obfuscated output
  • Understand the symbol map for debugging

Prerequisites

Don’t have a project? Create one with dart create -t console my_app && cd my_app

Get Started

1

Navigate to your Dart project

Open your terminal and navigate to your Dart project directory:
cd path/to/your/dart/project
Your project should have a structure like:
my_app/
├── lib/
│   └── main.dart
├── pubspec.yaml
└── ...
2

Generate a configuration file

Create a refractor.yaml configuration file:
refractor init
Output:
✓ Created refractor.yaml
This generates a starter configuration with sensible defaults:
refractor.yaml
# Refractor configuration
# Modeled after analysis_options.yaml for Dart developer familiarity.
# See: https://github.com/yardexx/refractor

# Global tool settings.
refractor:
  # Output path for the symbol map (original → obfuscated name mapping).
  # symbol_map: build/refractor_map.json

  # Scope is fixed to the current project (pubspec package + current folder).

  # Library URI/path exclusions using glob patterns.
  # exclude:
  #   - "**/*.g.dart"
  #   - "**/*.freezed.dart"

# Pass configuration — enable/disable and configure each pass.
# Use `true` for defaults, `false` to disable, or a map for custom settings.
passes:
  rename:
    # preserve_main: true

  string_encrypt: true
  # string_encrypt:
  #   xor_key: 0x5A
  #   exclude_patterns:
  #     - "^https://"

  dead_code: false

# Verbose logging.
# verbose: false
You can customize the output path with refractor init --output config/refractor.yaml
3

Run your first build

Compile, obfuscate, and build your application:
refractor build
Output:
✓ Compiling lib/main.dart to kernel...
✓ Applying obfuscation passes...
  → rename
  → string_encrypt
✓ Building executable...
✓ Writing symbol map...
✓ Build complete: build/out.exe
By default, Refractor:
  • Compiles lib/main.dart
  • Outputs to build/out.exe (or .dill, .aot, .jit depending on target)
  • Applies only the passes enabled in refractor.yaml
4

Test your obfuscated binary

Run the generated executable:
./build/out.exe
The application should run normally, but the internal code is now obfuscated!

Understanding the Output

After running refractor build, you’ll see:
my_app/
├── build/
│   ├── out.exe           # Obfuscated executable
│   └── refractor_map.json  # Symbol map (if configured)
├── lib/
│   └── main.dart
├── refractor.yaml
└── pubspec.yaml

Symbol Map

The symbol map (refractor_map.json) contains the mapping of original names to obfuscated names. This is useful for debugging:
{
  "classes": {
    "MyClass": "a",
    "UserService": "b",
    "DataRepository": "c"
  },
  "methods": {
    "calculateTotal": "d",
    "fetchData": "e",
    "processItems": "f"
  },
  "fields": {
    "userName": "g",
    "userId": "h",
    "accountBalance": "i"
  }
}
Never commit the symbol map to version control if you want to maintain obfuscation security. Add it to .gitignore:
echo "build/refractor_map.json" >> .gitignore

Customizing the Build

Refractor supports multiple build targets and options:
refractor build --target exe --output build

Inspect Obfuscated Kernel

To verify the obfuscation, inspect the compiled kernel file:
refractor build --target kernel
refractor inspect build/out.dill
Output:
Library: package:my_app/main.dart
  Class: a (was: MyClass)
    Field: g (was: userName)
    Method: d (was: calculateTotal)
  Function: main
    ...
Add the --sdk flag to include Dart core libraries in the inspection output:
refractor inspect --sdk build/out.dill

Real-World Example

Let’s obfuscate a simple application with multiple obfuscation passes:
1

Create example app

lib/main.dart
import 'dart:async';
import 'dart:math';

void main(List<String> args) async {
  final app = Application(
    config: AppConfig(
      appName: 'ObfuscatorTest',
      version: 1,
      flags: {'experimental': true},
    ),
  );

  await app.start();

  final service = RandomService(seed: 42);
  print('Random value: ${service.next()}');
}

class Application<T extends BaseConfig> {
  final T config;
  Application({required this.config});

  Future<void> start() async {
    print('Starting ${config.appName} v${config.version}');
    await Future.delayed(const Duration(milliseconds: 100));
  }
}

abstract class BaseConfig {
  String get appName;
  int get version;
  Map<String, bool> get flags;
}

class AppConfig implements BaseConfig {
  final String appName;
  final int version;
  final Map<String, bool> flags;

  const AppConfig({
    required this.appName,
    required this.version,
    required this.flags,
  });
}

class RandomService {
  final Random _random;
  RandomService({int? seed}) : _random = Random(seed);
  int next() => _random.nextInt(100);
}
2

Configure obfuscation

refractor.yaml
refractor:
  symbol_map: build/symbols.json
  exclude:
    - "**/*.g.dart"

passes:
  rename:
    preserve_main: true

  string_encrypt:
    xor_key: 0x5A
    exclude_patterns:
      - "^https://"

  dead_code:
    max_insertions_per_procedure: 2
3

Build and run

refractor build --target exe
./build/out.exe
Output:
Starting ObfuscatorTest v1
Random value: 51
The application works identically, but:
  • Class/method/field names are shortened (e.g., Applicationa)
  • String literals are XOR-encrypted
  • Dead code branches are injected into methods

Next Steps

Commands

Explore all CLI commands and options

Configuration

Deep dive into refractor.yaml settings

Obfuscation Passes

Learn about rename, string encryption, and dead code

Build Targets

Understand exe, AOT, JIT, and kernel outputs

Build docs developers (and LLMs) love