CLI tool
facebook/duckling avatar
facebook/duckling

Duckling: the Haskell library that turns phrases like "tomorrow at eight" into data

Language, engine, and tooling for expressing, testing, and evaluating composable language rules on input strings.

4,323 stars739 forksHaskellNOASSERTION

At a glance

What is it?
Facebook open sourced Duckling as a composable rule engine for extracting dates, quantities and other values from free text. It is written in Haskell, built around rule files per language, and its release history has been quiet since 2021.
Who is it for?
Duckling is a good fit when the input is human phrasing of a machine value and you can afford a Haskell dependency: "three cups of sugar" and "the first Tuesday of October" are exactly the cases its rules were written for. It is the wrong choice when you need broad language coverage today, because coverage is uneven and the newest tagged release, v0.2.0.0, is from April 2021, with the last push on 2026-03-15.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Activity is slowing. The repository last received commits 7 months ago.
What is it written in?
Mainly Haskell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • facebook/duckling4,323 ★ · +0
Star history
Repository2026-10-072026-10-25
facebook/duckling4.3k4.3k

0 Stars in the last 18 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The problem Duckling solves is specific, not general NLP

Most text extraction systems hand you a model and hope the output lands somewhere usable. Duckling takes the opposite route: a small set of hand written rules per language, each rule pairing a pattern with a production function that converts matched tokens into a typed value. The result is predictable output for the cases the rules cover, and no output at all for the cases they do not, which is a different failure mode than a model returning a plausible wrong number.

The repository description calls it a language, engine, and tooling for expressing, testing, and evaluating composable language rules on input strings, and the README leads with the smallest possible demonstration of what that means. The string "the first Tuesday of October" comes back as a JSON object with a `value` and a `grain` of `day`. That single example is the whole thesis: resolve a relative, culturally specific expression into an ISO timestamp plus a stated precision, rather than returning the string or a fuzzy guess.

Because it is a Haskell library rather than a service, the parser can be embedded directly in an existing Haskell process. The README also points out the alternative it grew out of: wit.ai's built in entities, which expose similar extraction over HTTP. That is an honest framing of the choice, since Duckling is what you run yourself when the language coverage, the latency or the privacy of the request path matters more than having zero infrastructure.

Running the example server needs stack and PCRE headers

The requirements are short and specific. A Haskell environment is required, the README recommends stack, and both Linux and macOS need the PCRE development headers installed. On macOS the documented path is Homebrew:

bash
brew install pcre

If that does not resolve it, the README points at `brew doctor` rather than leaving you to guess. On Linux, the headers come from the system package manager, and the Dockerfile shows exactly which ones: `libpcre3-dev`, plus `build-essential` and `pkg-config` for the Haskell build itself.

Compiling and starting the bundled example server is two commands:

bash
stack build
stack exec duckling-example-exe

The first build downloads the dependency set, which for a project of this vintage is not fast. The `stack.yaml` at the repository root pins the resolver, and `duckling.cabal` declares the components, so both the GHC version and the package set come from the repository rather than from your global toolchain.

Parsing over HTTP with locale and dimension filters

The example binary runs an HTTP server on port 8000 and exposes a single `/parse` endpoint. The README's example request passes a locale and the text to parse:

bash
curl -XPOST http://0.0.0.0:8000/parse --data 'locale=en_GB&text=tomorrow at eight'

In the example application every dimension is enabled by default, which means a single string can yield several matches. The `dims` parameter narrows that, and the README documents the filtering syntax with two concrete cases:

bash
Identify credit card numbers only:
$ curl -XPOST http://0.0.0.0:8000/parse --data 'locale=en_US&text="4111-1111-1111-1111"&dims="["credit-card-number"]"'
If you want multiple dimensions, comma-separate them in the array:
$ curl -XPOST http://0.0.0.0:8000/parse --data 'locale=en_US&text="3 cups of sugar"&dims="["quantity","numeral"]"'

Requesting one dimension instead of all of them is not a micro optimisation. Dimensions overlap heavily by design, since "3 cups of sugar" is legitimately both a `Numeral` and a `Quantity`, and a consumer that only wants numbers would otherwise have to filter results itself. The `exe/` directory holds `ExampleMain.hs` as the integration reference and `CustomDimensionExample.hs` for the extension path.

Dimensions cover values, units and identifiers, unevenly by language

The README's dimension table is the fastest way to see what the ruleset actually covers: `AmountOfMoney`, `CreditCardNumber`, `Distance`, `Duration`, `Email`, `Numeral`, `Ordinal`, `PhoneNumber`, `Quantity`, `Temperature`, `Time`, `Url` and `Volume`. Each row pairs an input with its JSON shape, and the shapes differ in ways that matter to a consumer. `Duration` returns both the raw value and a `normalized` object, so "3 mins" becomes 3 minutes and 180 seconds at once. `Time` returns a `values` array alongside a flattened `value`, because a single phrase can resolve to more than one instant.

The important caveat is the one the README states directly: most languages do not support all dimensions, and it asks for help closing the gap. Coverage is a per directory question, and the README points at `Duckling/Dimensions` for the language specific view. A reader evaluating this should check that directory for their own locale pair before anything else, because a rule present for `en_US` says very little about what happens with `en_AU` or with a language outside the set the repository has files for.

The `Time` dimension carries the heaviest lifting in the release notes, with recurring work on holidays and daylight saving boundaries, which is the kind of maintenance that quietly stops being correct if nobody is patching it.

Adding a rule means four files per language and a regenerated classifier

Extending an existing dimension in a given language touches about four files, and the README lists them precisely: the rules at `Duckling/<Dimension>/<Lang>/Rules.hs`, the corpus at `Duckling/<Dimension>/<Lang>/Corpus.hs`, the dimension registry at `Duckling/Dimensions/<Lang>.hs` if the language is not already covered by `Duckling/Dimensions/Common.hs`, and the language rule set at `Duckling/Rules/<Lang>.hs`. That is a small, legible contribution surface, and the corpus file is the part worth understanding, because it is what turns rule changes into a test suite.

The build step for contributors regenerates the classifiers and then runs the tests:

bash
stack build :duckling-regen-exe && stack exec duckling-regen-exe && stack test

The README is emphatic about the ordering, calling it important to regenerate after updating code and before running the suite. Generated classifiers sitting stale relative to the rules is a failure mode that would otherwise show up as puzzling test results rather than an obvious error.

Adding a language is a larger commitment with stated prerequisites: the code must be a valid ISO-639-1 code, the first dimension to implement is `Numeral`, and the README links a reference commit to copy. Adding a locale has its own rule, that there should be a real need for diverging rules between the locale and the language, plus a valid ISO 3166 alpha2 code. The split between language and locale is what lets one rule set serve `en_US`, `en_AU` and `en_GB` with only the genuinely different parts diverging.

A quiet release history is the main thing to weigh

The repository is not archived and the last push was on 2026-03-15. The tagged releases tell a different rhythm. There are three: v0.2.0.0 on 2021-04-16, v0.1.6.1 on 2018-06-09, and v0.1.6.0 on 2018-04-23. v0.2.0.0 was the substantial one, relicensing the project to BSD-3, migrating CI from Travis to GitHub, adding the `CreditCardNumber` dimension, correcting timezone offsets for BST and IST, extending holiday coverage for Ramadan, Eid, Purim and several named commemorations, and widening the supported GHC range to 8.8 through 9.0.1.

Those release notes are also the clearest evidence for what this project is made of. Most of the work, release after release, is data: holiday tables, timezone offsets, unit spellings, numeral systems. A rule engine of this shape is not finished when the code compiles, and the ratio of ruleset commits to core commits in these notes says so plainly.

The practical reading is that Duckling offers a well designed mechanism with a bounded, dated rule corpus. If your inputs are English, US or UK English phrases about time, money, distance and quantities, it will do exactly what the README shows. If your inputs span languages whose `Numeral` rule is not written yet, the correct next step is reading the `Duckling/Dimensions` tree rather than assuming coverage, and the licensing is BSD-3 as of v0.2.0.0, with the LICENSE file at the repository root.

Editorial conclusion

Duckling is a good fit when the input is human phrasing of a machine value and you can afford a Haskell dependency: "three cups of sugar" and "the first Tuesday of October" are exactly the cases its rules were written for. It is the wrong choice when you need broad language coverage today, because coverage is uneven and the newest tagged release, v0.2.0.0, is from April 2021, with the last push on 2026-03-15. Start with the example binary and a handful of curl calls against your own locale, and read `Duckling/Dimensions/<Lang>.hs` to see whether the dimensions you depend on exist for the languages you need before you commit.

Frequently asked questions

What is Duckling from Facebook used for?

It parses free text into structured data, turning expressions like "the first Tuesday of October" or "3 cups of sugar" into JSON objects with typed values, units and precision. It is a Haskell library rather than a hosted service, so it can be embedded directly or run behind the bundled example HTTP server.

Which languages does Duckling support?

Duckling supports many languages, but the README is explicit that most do not cover all dimensions yet. The first dimension to implement for a new language is `Numeral`, and per-language coverage is visible in the `Duckling/Dimensions` directory of the repository.

How do you run Duckling locally?

Install stack and the PCRE development headers, then run `stack build` followed by `stack exec duckling-example-exe`. That starts a basic HTTP server on port 8000 that accepts POST requests to `/parse` with a `locale` and the `text` to parse.

How do you add a new rule or dimension to Duckling?

For an existing dimension in an existing language, about four files change: the rules and corpus under `Duckling/<Dimension>/<Lang>/`, plus the dimension registry and language rule set. After changing rules you must regenerate the classifiers before running the suite, using `stack build :duckling-regen-exe && stack exec duckling-regen-exe && stack test`.

Official sources

  1. facebook/duckling on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/facebook-duckling.svg)](https://hysenlabs.com/projects/facebook-duckling)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/facebook-duckling.svg)](https://hysenlabs.com/projects/facebook-duckling)

Was this useful?