One way is to simply extend the Prolog engine to handle such constraints natively. Many basic constraints are implemented in this way. In fact, you can regard even (=)/2, i.e., unification, as one such constraint, and all Prolog systems implement it, typically as one of their virtual machine instructions. The same method naturally extends to other constraints like freeze/2 and (in)/2, which are often implemented natively. See the system descriptions of GNU Prolog, as one example for a system that handles the CLP(FD) constraint (in)/2 natively.
For application programmers, Prolog systems typically provide an interface in the form of attributed variables are metastructures, so that any programmer can implement a custom constraint solver on top of Prolog by attaching attributes to variables. For example, in SICStus Prolog, there is a hook called verify_attributes/3, and you can provide a custom user-defined specification that handles your own constraints.
In practice, the implementation of constraint solvers on top of Prolog typically involves a combination of both approaches, with some low-level constraints implemented natively (i.e., as part of the engine), and some higher-level constraints implemented via the general interface on the Prolog level.
There are of course also other ways to do it, such as writing a Prolog meta-interpreter that interprets your code as if the Prolog engine supported such extensions. This is likely among the slower ways to provide such extensions, while a native implementation is among the fastest, and using general interface predicates will likely be somewhere between these extremes.