URP
Pure Elixir client for the UNO Remote Protocol.
Talks directly to a soffice process over a TCP socket —
no Python, no wrappers, no sidecars.
Why?
Existing approaches to LibreOffice integration — unoserver, Gotenberg, Python UNO bindings — each add an intermediate layer with its own deployment complexity and failure modes.
URP speaks the binary protocol directly over TCP. Document conversion is the primary use case, but the same connection gives you access to soffice diagnostics — registered services, available export filters, document types, locale settings, and version info.
Installation
Add urp to your dependencies in mix.exs:
def deps do
[
{:urp, "~> 0.7"}
]
endPrerequisites
A running soffice process with a URP socket listener. Build from
benchmarks/Dockerfile.soffice-debian or use your own Debian/Ubuntu
image with LibreOffice installed:
docker build --tag soffice --file benchmarks/Dockerfile.soffice-debian benchmarks/
docker run --detach --name soffice --publish 2002:2002 sofficeNote
Any image with
sofficelistening on a TCP socket works — includinglibreofficedocker/alpine. See PERFORMANCE.md for trade-offs.
Usage
A default pool connects to localhost:2002 automatically.
Document conversion
{:ok, pdf_path} =
URP.convert("/path/to/input.docx",
filter: "writer_pdf_Export",
filter_data: [
UseLosslessCompression: false,
Quality: 90,
ReduceImageResolution: true,
MaxImageResolution: 150,
ExportBookmarks: true,
ExportFormFields: false
],
output: "/tmp/output.pdf"
)
Input can be a file path, {:binary, bytes}, or any Enumerable
(e.g. File.stream!/2). Output defaults to a temp file path; pass
an explicit path or output: :binary to get bytes in memory. See
filter names
for all formats (calc_pdf_Export, impress_pdf_Export, etc.) and
FilterData properties
for PDF export options.
Diagnostics
{:ok, "25.8.1.1"} = URP.version()
{:ok, services} = URP.services() # all registered UNO service names
{:ok, filters} = URP.filters() # available export filters
{:ok, types} = URP.types() # known document type names
{:ok, locale} = URP.locale() # soffice locale settingConfiguration
# config/runtime.exs
config :urp, :default,
host: "soffice",
port: 2002,
pool_size: 1Testing
Stub conversions in tests — no running soffice needed:
test "generates invoice PDF" do
URP.Test.stub(fn _input, _opts ->
{:ok, "/tmp/fake.pdf"}
end)
assert {:ok, _pdf} = MyApp.generate_invoice(order)
end
Stubs are per-process and propagate through $callers (Tasks, GenServers).
See URP.Test for details.
Performance
See PERFORMANCE.md for benchmarks against Gotenberg and container image recommendations.
Scope
Implements document conversion and read-only soffice introspection via UNO.
Export format is controlled by filter names
(writer_pdf_Export, calc_pdf_Export, impress_pdf_Export, Markdown, etc.).
Mutating UNO APIs (editing, formatting, macros) are not implemented.
Architecture
| Module | Role |
|---|---|
URP | Public API — convert, diagnostics, test stubs |
URP.Bridge | Mid-level — UNO operations (handshake, convert, diagnostics, streaming) |
URP.Stream | Bidirectional URP dispatch for XInputStream/XOutputStream |
URP.Protocol | Low-level — binary wire format (framing, encoding, reply parsing) |
References
- UNO Binary Protocol Spec
- binaryurp source — reader.cxx, writer.cxx, marshal.cxx
- specialfunctionids.hxx
- typeclass.h
- Export filter names
License
MIT — see LICENSE.
This is an independent implementation based on the public UNO protocol spec. LibreOffice source was consulted as documentation for protocol details not covered by the spec. No code was copied.